Compare commits
5 Commits
e311a94e98
...
cfcbbb0962
Author | SHA1 | Date |
---|---|---|
nervuri | cfcbbb0962 | |
nervuri | 0a124def84 | |
nervuri | 2eedb5537f | |
nervuri | 72b3259428 | |
nervuri | c611d46b4f |
|
@ -27,8 +27,7 @@ func dropPrivileges(userToSwitchTo string) {
|
||||||
// Check supplementary groups.
|
// Check supplementary groups.
|
||||||
groups, err := syscall.Getgroups()
|
groups, err := syscall.Getgroups()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fatalError(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
for _, groupID := range groups {
|
for _, groupID := range groups {
|
||||||
if groupID == 0 {
|
if groupID == 0 {
|
||||||
|
@ -43,60 +42,53 @@ func dropPrivileges(userToSwitchTo string) {
|
||||||
fmt.Println("When running as root, use the -u option to switch to an unprivileged user.")
|
fmt.Println("When running as root, use the -u option to switch to an unprivileged user.")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
} else if rootPrimaryGroup || rootSupplementaryGroup {
|
} else if rootPrimaryGroup || rootSupplementaryGroup {
|
||||||
fmt.Println("The user running the program is in the root group;")
|
fatalError("The user running the program is in the root group;\n" +
|
||||||
fmt.Println("use the -u option to switch to an unprivileged user.")
|
"use the -u option to switch to an unprivileged user.")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else { // userToSwitchTo != ""
|
} else { // userToSwitchTo != ""
|
||||||
|
|
||||||
// Get user and group IDs for the user we want to switch to.
|
// Get user and group IDs for the user we want to switch to.
|
||||||
userInfo, err := user.Lookup(userToSwitchTo)
|
userInfo, err := user.Lookup(userToSwitchTo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fatalError(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
// Convert group id and user id from string to int.
|
// Convert group id and user id from string to int.
|
||||||
gid, err := strconv.Atoi(userInfo.Gid)
|
gid, err := strconv.Atoi(userInfo.Gid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fatalError(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
uid, err := strconv.Atoi(userInfo.Uid)
|
uid, err := strconv.Atoi(userInfo.Uid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fatalError(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
// If the user we want to switch to has root privileges, stop execution.
|
// If the user we want to switch to has root privileges, stop execution.
|
||||||
if uid == 0 || gid == 0 {
|
if uid == 0 || gid == 0 {
|
||||||
fmt.Println("Running as root is not allowed.")
|
fatalError("Running as root is not allowed.")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
// Unset supplementary group IDs.
|
// Unset supplementary group IDs.
|
||||||
err = syscall.Setgroups([]int{})
|
err = syscall.Setgroups([]int{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to unset supplementary group IDs: " + err.Error())
|
fmt.Fprintln(os.Stderr,
|
||||||
|
"Failed to unset supplementary group IDs: "+err.Error())
|
||||||
if rootSupplementaryGroup {
|
if rootSupplementaryGroup {
|
||||||
fmt.Println("Failed to drop root privileges. Exiting...")
|
fatalError("Failed to drop root privileges. Exiting...")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Set group ID (real and effective).
|
// Set group ID (real and effective).
|
||||||
err = syscall.Setgid(gid)
|
err = syscall.Setgid(gid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to set group ID: " + err.Error())
|
fmt.Fprintln(os.Stderr, "Failed to set group ID: "+err.Error())
|
||||||
if rootPrimaryGroup {
|
if rootPrimaryGroup {
|
||||||
fmt.Println("Failed to drop root privileges. Exiting...")
|
fatalError("Failed to drop root privileges. Exiting...")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Set user ID (real and effective).
|
// Set user ID (real and effective).
|
||||||
err = syscall.Setuid(uid)
|
err = syscall.Setuid(uid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to set user ID: " + err.Error())
|
fmt.Fprintln(os.Stderr, "Failed to set user ID: "+err.Error())
|
||||||
if rootUser {
|
if rootUser {
|
||||||
fmt.Println("Failed to drop root privileges. Exiting...")
|
fatalError("Failed to drop root privileges. Exiting...")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# TLS Client Hello Mirror
|
||||||
|
|
||||||
|
=> /json/v1 Your browser's TLS Client Hello, reflected as JSON
|
||||||
|
|
||||||
|
This test:
|
||||||
|
|
||||||
|
* reflects the complete Client Hello message, preserving the order in which TLS parameters and extensions are sent;
|
||||||
|
* can be used to check for TLS privacy pitfalls (session resumption, TLS fingerprinting, system time exposure);
|
||||||
|
* supports multiple protocols (currently HTTP and Gemini);
|
||||||
|
* is free as in freedom and trivial to self-host.
|
||||||
|
|
||||||
|
JSON only, for now. The API is largely stable - fields may be added, but existing fields will not be modified or removed. IANA-assigned codes for TLS parameters and extensions are available at:
|
||||||
|
|
||||||
|
=> https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml TLS parameters
|
||||||
|
=> https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml TLS extensions
|
||||||
|
|
||||||
|
Note that these lists do not include draft extensions and GREASE values. Missing values will be documented here as the project evolves.
|
||||||
|
|
||||||
|
_____________________
|
||||||
|
=> https://nervuri.net/ Author: nervuri
|
||||||
|
=> https://tildegit.org/nervuri/client-hello-mirror Source (contributions welcome)
|
||||||
|
=> https://www.gnu.org/licenses/agpl-3.0.en.html License: AGPL-3.0-or-later
|
|
@ -0,0 +1,62 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="theme-color" content="#000">
|
||||||
|
<meta name="referrer" content="no-referrer">
|
||||||
|
<title>TLS Client Hello Mirror</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
::selection {
|
||||||
|
color: #FFF;
|
||||||
|
background-color: #070;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
color: #DDD;
|
||||||
|
background-color: #000;
|
||||||
|
margin: 1em auto;
|
||||||
|
max-width: 38em;
|
||||||
|
padding: 0 .62em;
|
||||||
|
font: 1.1em/1.62 sans-serif;
|
||||||
|
}
|
||||||
|
@media print{
|
||||||
|
body{
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a:link {color:#EEE;}
|
||||||
|
a:visited {color:#EEE;}
|
||||||
|
a:hover {color:#FFF;}
|
||||||
|
a:active {color:#FFF;}
|
||||||
|
</style>
|
||||||
|
<main>
|
||||||
|
<center>
|
||||||
|
<h1>TLS Client Hello Mirror</h1>
|
||||||
|
</center>
|
||||||
|
<h3><a href="/json/v1">Your browser's TLS Client Hello, reflected as JSON</a></h3>
|
||||||
|
<p>This test:
|
||||||
|
<ul>
|
||||||
|
<li>reflects the complete Client Hello message, preserving the order in which TLS parameters and extensions are sent;</li>
|
||||||
|
<li>can be used to check for TLS privacy pitfalls (<a href="https://svs.informatik.uni-hamburg.de/publications/2018/2018-12-06-Sy-ACSAC-Tracking_Users_across_the_Web_via_TLS_Session_Resumption.pdf">session resumption</a>, <a href="https://tlsfingerprint.io/">TLS fingerprinting</a>, <a href="https://datatracker.ietf.org/doc/html/draft-mathewson-no-gmtunixtime">system time exposure</a>);</li>
|
||||||
|
<li>supports both HTTP and <a href="https://gemini.circumlunar.space/">Gemini</a>;</li>
|
||||||
|
<li>is <a href="https://www.gnu.org/philosophy/free-sw.en.html">free as in freedom</a> and trivial to self-host.</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
<p>JSON only, for now, but a UI is on <a href="https://tildegit.org/nervuri/client-hello-mirror#roadmap">the roadmap</a>.</p>
|
||||||
|
<p>The API is largely stable - fields may be added, but existing fields will not be modified or removed. IANA-assigned codes for TLS parameters and extensions are documented at:
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml">TLS parameters</a></li>
|
||||||
|
<li><a href="https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml">TLS extensions</a></li>
|
||||||
|
</ul>
|
||||||
|
Note that these lists do not include draft extensions and <a href="https://datatracker.ietf.org/doc/html/rfc8701">GREASE</a> values. Missing values will be documented here as the project evolves.
|
||||||
|
</p>
|
||||||
|
</main>
|
||||||
|
<hr>
|
||||||
|
<footer>
|
||||||
|
Author: <a href="https://nervuri.net/">nervuri</a><br>
|
||||||
|
<a href="https://tildegit.org/nervuri/client-hello-mirror">Source</a> (contributions welcome)<br>
|
||||||
|
License: <a href="https://www.gnu.org/licenses/agpl-3.0.en.html">AGPL-3.0-or-later</a>
|
||||||
|
</footer>
|
||||||
|
</html>
|
140
server.go
140
server.go
|
@ -6,18 +6,22 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
_ "embed"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const readTimeout = 10 // seconds
|
||||||
|
const writeTimeout = 10 // seconds
|
||||||
|
|
||||||
type tlsConnectionInfo struct {
|
type tlsConnectionInfo struct {
|
||||||
TlsVersion uint16 `json:"tls_version"`
|
TlsVersion uint16 `json:"tls_version"`
|
||||||
CipherSuite uint16 `json:"cipher_suite"`
|
CipherSuite uint16 `json:"cipher_suite"`
|
||||||
|
@ -25,6 +29,9 @@ type tlsConnectionInfo struct {
|
||||||
NegotiatedProtocol string `json:"alpn_negotiated_protocol"` // ALPN
|
NegotiatedProtocol string `json:"alpn_negotiated_protocol"` // ALPN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Connection wrapper that enables exposing the Client Hello to the
|
||||||
|
// request handler.
|
||||||
|
// See https://github.com/FiloSottile/mostly-harmless/tree/main/talks/asyncnet
|
||||||
type prefixConn struct {
|
type prefixConn struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
io.Reader
|
io.Reader
|
||||||
|
@ -34,91 +41,19 @@ func (c prefixConn) Read(p []byte) (int, error) {
|
||||||
return c.Reader.Read(p)
|
return c.Reader.Read(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
const html = `<!DOCTYPE html>
|
// Output to stderr and exit with error code 1.
|
||||||
<html lang="en">
|
// Like log.Fatal, but without the date&time prefix.
|
||||||
<meta charset="utf-8">
|
// Used before starting the server loop.
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
func fatalError(err ...any) {
|
||||||
<meta name="theme-color" content="#000">
|
logger := log.New(os.Stderr, "", 0)
|
||||||
<meta name="referrer" content="no-referrer">
|
logger.Fatal(err...)
|
||||||
<title>TLS Client Hello Mirror</title>
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
color-scheme: dark;
|
|
||||||
}
|
}
|
||||||
::selection {
|
|
||||||
color: #FFF;
|
|
||||||
background-color: #070;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
color: #DDD;
|
|
||||||
background-color: #000;
|
|
||||||
margin: 1em auto;
|
|
||||||
max-width: 38em;
|
|
||||||
padding: 0 .62em;
|
|
||||||
font: 1.1em/1.62 sans-serif;
|
|
||||||
}
|
|
||||||
@media print{
|
|
||||||
body{
|
|
||||||
max-width: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
a:link {color:#EEE;}
|
|
||||||
a:visited {color:#EEE;}
|
|
||||||
a:hover {color:#FFF;}
|
|
||||||
a:active {color:#FFF;}
|
|
||||||
</style>
|
|
||||||
<main>
|
|
||||||
<center>
|
|
||||||
<h1>TLS Client Hello Mirror</h1>
|
|
||||||
</center>
|
|
||||||
<h3><a href="/json/v1">Your browser's TLS Client Hello, reflected as JSON</a></h3>
|
|
||||||
<p>This test:
|
|
||||||
<ul>
|
|
||||||
<li>reflects the complete Client Hello message, preserving the order in which TLS parameters and extensions are sent;</li>
|
|
||||||
<li>can be used to check for TLS privacy pitfalls (<a href="https://svs.informatik.uni-hamburg.de/publications/2018/2018-12-06-Sy-ACSAC-Tracking_Users_across_the_Web_via_TLS_Session_Resumption.pdf">session resumption</a>, <a href="https://tlsfingerprint.io/">TLS fingerprinting</a>, <a href="https://datatracker.ietf.org/doc/html/draft-mathewson-no-gmtunixtime">system time exposure</a>);</li>
|
|
||||||
<li>supports both HTTP and <a href="https://gemini.circumlunar.space/">Gemini</a>;</li>
|
|
||||||
<li>is <a href="https://www.gnu.org/philosophy/free-sw.en.html">free as in freedom</a> and trivial to self-host.</li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
<p>JSON only, for now, but a UI is on <a href="https://tildegit.org/nervuri/client-hello-mirror#roadmap">the roadmap</a>.</p>
|
|
||||||
<p>The API is largely stable - fields may be added, but existing fields will not be modified or removed. IANA-assigned codes for TLS parameters and extensions are documented at:
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml">TLS parameters</a></li>
|
|
||||||
<li><a href="https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml">TLS extensions</a></li>
|
|
||||||
</ul>
|
|
||||||
Note that these lists do not include draft extensions and <a href="https://datatracker.ietf.org/doc/html/rfc8701">GREASE</a> values. Missing values will be documented here as the project evolves.
|
|
||||||
</p>
|
|
||||||
</main>
|
|
||||||
<hr>
|
|
||||||
<footer>
|
|
||||||
Author: <a href="https://nervuri.net/">nervuri</a><br>
|
|
||||||
<a href="https://tildegit.org/nervuri/client-hello-mirror">Source</a> (contributions welcome)<br>
|
|
||||||
License: <a href="https://www.gnu.org/licenses/agpl-3.0.en.html">AGPL-3.0-or-later</a>
|
|
||||||
</footer>
|
|
||||||
</html>`
|
|
||||||
|
|
||||||
const gemtext = `# TLS Client Hello Mirror
|
//go:embed index.html
|
||||||
|
var html string
|
||||||
|
|
||||||
=> /json/v1 Your browser's TLS Client Hello, reflected as JSON
|
//go:embed index.gmi
|
||||||
|
var gemtext string
|
||||||
This test:
|
|
||||||
|
|
||||||
* reflects the complete Client Hello message, preserving the order in which TLS parameters and extensions are sent;
|
|
||||||
* can be used to check for TLS privacy pitfalls (session resumption, TLS fingerprinting, system time exposure);
|
|
||||||
* supports multiple protocols (currently HTTP and Gemini);
|
|
||||||
* is free as in freedom and trivial to self-host.
|
|
||||||
|
|
||||||
JSON only, for now. The API is largely stable - fields may be added, but existing fields will not be modified or removed. IANA-assigned codes for TLS parameters and extensions are available at:
|
|
||||||
|
|
||||||
=> https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml TLS parameters
|
|
||||||
=> https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml TLS extensions
|
|
||||||
|
|
||||||
Note that these lists do not include draft extensions and GREASE values. Missing values will be documented here as the project evolves.
|
|
||||||
|
|
||||||
_____________________
|
|
||||||
=> https://nervuri.net/ Author: nervuri
|
|
||||||
=> https://tildegit.org/nervuri/client-hello-mirror Source (contributions welcome)
|
|
||||||
=> https://www.gnu.org/licenses/agpl-3.0.en.html License: AGPL-3.0-or-later`
|
|
||||||
|
|
||||||
// Copy the Client Hello message before starting the TLS handshake.
|
// Copy the Client Hello message before starting the TLS handshake.
|
||||||
func peek(conn net.Conn, tlsConfig *tls.Config) {
|
func peek(conn net.Conn, tlsConfig *tls.Config) {
|
||||||
|
@ -144,21 +79,24 @@ func peek(conn net.Conn, tlsConfig *tls.Config) {
|
||||||
}
|
}
|
||||||
rawClientHello := buf.Bytes()
|
rawClientHello := buf.Bytes()
|
||||||
|
|
||||||
|
// "Put back" the Client Hello bytes we just read, so that they can be
|
||||||
|
// used in the TLS handshake. Concatenate the read bytes with the
|
||||||
|
// unread bytes using a MultiReader, inside a connection wrapper.
|
||||||
pConn := prefixConn{
|
pConn := prefixConn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
Reader: io.MultiReader(&buf, conn),
|
Reader: io.MultiReader(&buf, conn),
|
||||||
}
|
}
|
||||||
|
|
||||||
server := tls.Server(pConn, tlsConfig)
|
tlsConnection := tls.Server(pConn, tlsConfig)
|
||||||
err = server.Handshake()
|
err = tlsConnection.Handshake()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tlsHandler(server, rawClientHello)
|
requestHandler(tlsConnection, rawClientHello)
|
||||||
}
|
}
|
||||||
|
|
||||||
func tlsHandler(conn *tls.Conn, rawClientHello []byte) {
|
func requestHandler(conn *tls.Conn, rawClientHello []byte) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
scanner := bufio.NewScanner(conn)
|
scanner := bufio.NewScanner(conn)
|
||||||
|
@ -169,7 +107,6 @@ func tlsHandler(conn *tls.Conn, rawClientHello []byte) {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//log.Println(line)
|
|
||||||
|
|
||||||
var protocol string
|
var protocol string
|
||||||
var path string // requested page
|
var path string // requested page
|
||||||
|
@ -275,28 +212,25 @@ func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
hostAndPort = flag.Arg(0)
|
hostAndPort = flag.Arg(0)
|
||||||
if certFile == "" || keyFile == "" || hostAndPort == "" {
|
if certFile == "" || keyFile == "" || hostAndPort == "" {
|
||||||
fmt.Println("usage: client-hello-mirror -c cert.pem -k key.pem [-u user] host:port")
|
fatalError("usage: client-hello-mirror -c cert.pem -k key.pem [-u user] host:port")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load cert
|
// Load cert
|
||||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
fatalError(err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
// TLS config
|
// TLS config
|
||||||
tlsConfig := tls.Config{
|
tlsConfig := tls.Config{
|
||||||
Certificates: []tls.Certificate{cert},
|
Certificates: []tls.Certificate{cert},
|
||||||
//MaxVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS10,
|
||||||
NextProtos: []string{"http/1.1"},
|
NextProtos: []string{"http/1.1"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for connections
|
// Listen for connections
|
||||||
ln, err := net.Listen("tcp", hostAndPort)
|
ln, err := net.Listen("tcp", hostAndPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
fatalError(err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
defer ln.Close()
|
defer ln.Close()
|
||||||
|
|
||||||
|
@ -305,12 +239,24 @@ func main() {
|
||||||
log.Println("Server started")
|
log.Println("Server started")
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
// Wait for a connection.
|
||||||
conn, err := ln.Accept()
|
conn, err := ln.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error accepting: ", err.Error())
|
log.Println("Error accepting: ", err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
conn.SetReadDeadline(time.Now().Add(5 * time.Second)) // timeout
|
// Set connection timeouts.
|
||||||
|
err = conn.SetReadDeadline(time.Now().Add(readTimeout * time.Second))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("SetReadDeadline error: ", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = conn.SetWriteDeadline(time.Now().Add(writeTimeout * time.Second))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("SetWriteDeadline error: ", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Process the request.
|
||||||
go peek(conn, &tlsConfig)
|
go peek(conn, &tlsConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue