From a0ebb9b5cb580d56283c744b9642bc8fb0bc4819 Mon Sep 17 00:00:00 2001 From: Weii Wang Date: Tue, 2 Apr 2024 12:09:15 +0000 Subject: [PATCH] temp --- aproxy.go | 119 +----------------- conn.go | 52 -------- conn_test.go | 32 ----- relay.go | 54 +++++++++ stream.go | 306 +++++++++++++++++++++++++++++++++++++++++++++++ stream_test.go | 146 ++++++++++++++++++++++ syscall_linux.go | 2 +- 7 files changed, 508 insertions(+), 203 deletions(-) delete mode 100644 conn_test.go create mode 100644 relay.go create mode 100644 stream.go create mode 100644 stream_test.go diff --git a/aproxy.go b/aproxy.go index c507b41..9c2e794 100644 --- a/aproxy.go +++ b/aproxy.go @@ -3,7 +3,6 @@ package main import ( "bufio" "context" - "encoding/binary" "errors" "flag" "fmt" @@ -13,126 +12,10 @@ import ( "os" "os/signal" "strings" - - "golang.org/x/crypto/cryptobyte" ) var version = "1.0.0" -// PrereadSNI pre-reads the Server Name Indication (SNI) from a TLS connection. -func PrereadSNI(conn *PrereadConn) (_ string, err error) { - defer conn.EndPreread() - defer func() { - if err != nil { - err = fmt.Errorf("failed to preread TLS client hello: %w", err) - } - }() - typeVersionLen := make([]byte, 5) - n, err := conn.Read(typeVersionLen) - if n != 5 { - return "", errors.New("too short") - } - if err != nil { - return "", err - } - if typeVersionLen[0] != 22 { - return "", errors.New("not a TCP handshake") - } - msgLen := binary.BigEndian.Uint16(typeVersionLen[3:]) - buf := make([]byte, msgLen+5) - n, err = conn.Read(buf[5:]) - if n != int(msgLen) { - return "", errors.New("too short") - } - if err != nil { - return "", err - } - copy(buf[:5], typeVersionLen) - return extractSNI(buf) -} - -func extractSNI(data []byte) (string, error) { - s := cryptobyte.String(data) - var ( - version uint16 - random []byte - sessionId []byte - ) - - if !s.Skip(9) || - !s.ReadUint16(&version) || !s.ReadBytes(&random, 32) || - !s.ReadUint8LengthPrefixed((*cryptobyte.String)(&sessionId)) { - return "", fmt.Errorf("failed to parse TLS client hello version, random or session id") - } - - var cipherSuitesData cryptobyte.String - if !s.ReadUint16LengthPrefixed(&cipherSuitesData) { - return "", fmt.Errorf("failed to parse TLS client hello cipher suites") - } - - var cipherSuites []uint16 - for !cipherSuitesData.Empty() { - var suite uint16 - if !cipherSuitesData.ReadUint16(&suite) { - return "", fmt.Errorf("failed to parse TLS client hello cipher suites") - } - cipherSuites = append(cipherSuites, suite) - } - - var compressionMethods []byte - if !s.ReadUint8LengthPrefixed((*cryptobyte.String)(&compressionMethods)) { - return "", fmt.Errorf("failed to parse TLS client hello compression methods") - } - - if s.Empty() { - // ClientHello is optionally followed by extension data - return "", fmt.Errorf("no extension data in TLS client hello") - } - - var extensions cryptobyte.String - if !s.ReadUint16LengthPrefixed(&extensions) || !s.Empty() { - return "", fmt.Errorf("failed to parse TLS client hello extensions") - } - - finalServerName := "" - for !extensions.Empty() { - var extension uint16 - var extData cryptobyte.String - if !extensions.ReadUint16(&extension) || - !extensions.ReadUint16LengthPrefixed(&extData) { - return "", fmt.Errorf("failed to parse TLS client hello extension") - } - if extension != 0 { - continue - } - var nameList cryptobyte.String - if !extData.ReadUint16LengthPrefixed(&nameList) || nameList.Empty() { - return "", fmt.Errorf("failed to parse server name extension") - } - - for !nameList.Empty() { - var nameType uint8 - var serverName cryptobyte.String - if !nameList.ReadUint8(&nameType) || - !nameList.ReadUint16LengthPrefixed(&serverName) || - serverName.Empty() { - return "", fmt.Errorf("failed to parse server name indication extension") - } - if nameType != 0 { - continue - } - if len(finalServerName) != 0 { - return "", fmt.Errorf("multiple names of the same name_type are prohibited in server name extension") - } - finalServerName = string(serverName) - if strings.HasSuffix(finalServerName, ".") { - return "", fmt.Errorf("SNI name ends with a trailing dot") - } - } - } - return finalServerName, nil -} - // PrereadHTTPHost pre-reads the HTTP Host header from an HTTP connection. func PrereadHTTPHost(conn *PrereadConn) (_ string, err error) { defer func() { @@ -236,7 +119,7 @@ func main() { for { conn, err := listener.Accept() if err != nil { - logger.ErrorContext(ctx, "failed to accept connection", "error", err) + logger.ErrorContext(ctx, "failed to accept connection", "error", err) continue } go HandleConn(conn, proxy) diff --git a/conn.go b/conn.go index 30bedeb..77ad062 100644 --- a/conn.go +++ b/conn.go @@ -2,60 +2,8 @@ package main import ( "net" - "sync" ) -// PrereadConn is a wrapper around net.Conn that supports pre-reading from the underlying connection. -// Any Read before the EndPreread can be undone and read again by calling the EndPreread function. -type PrereadConn struct { - ended bool - buf []byte - mu sync.Mutex - conn net.Conn -} - -// EndPreread ends the pre-reading phase. Any Read before will be undone and data in the stream can be read again. -// EndPreread can be only called once. -func (c *PrereadConn) EndPreread() { - c.mu.Lock() - defer c.mu.Unlock() - if c.ended { - panic("call EndPreread after preread has ended or hasn't started") - } - c.ended = true -} - -// Read reads from the underlying connection. Read during the pre-reading phase can be undone by EndPreread. -func (c *PrereadConn) Read(p []byte) (n int, err error) { - c.mu.Lock() - defer c.mu.Unlock() - if c.ended { - n = copy(p, c.buf) - bufLen := len(c.buf) - c.buf = c.buf[n:] - if n == len(p) || (bufLen > 0 && bufLen == n) { - return n, nil - } - rn, err := c.conn.Read(p[n:]) - return rn + n, err - } else { - n, err = c.conn.Read(p) - c.buf = append(c.buf, p[:n]...) - return n, err - } -} - -// Write writes data to the underlying connection. -func (c *PrereadConn) Write(p []byte) (n int, err error) { - return c.conn.Write(p) -} - -// NewPrereadConn wraps the network connection and return a *PrereadConn. -// It's recommended to not touch the original connection after wrapped. -func NewPrereadConn(conn net.Conn) *PrereadConn { - return &PrereadConn{conn: conn} -} - // ConsignedConn wraps the PrereadConn and provides some slots to attach information related to the connection. type ConsignedConn struct { *PrereadConn diff --git a/conn_test.go b/conn_test.go deleted file mode 100644 index bd39d80..0000000 --- a/conn_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "io" - "net" - "testing" -) - -func TestPrereadConn(t *testing.T) { - remote, local := net.Pipe() - go remote.Write([]byte("hello, world")) - preread := &PrereadConn{conn: local} - buf := make([]byte, 5) - _, err := preread.Read(buf) - if err != nil { - t.Fatalf("Read failed during preread: %s", err) - } - buf = make([]byte, 3) - _, err = preread.Read(buf) - if err != nil { - t.Fatalf("Read failed during preread: %s", err) - } - preread.EndPreread() - buf2 := make([]byte, 12) - _, err = io.ReadFull(preread, buf2) - if err != nil { - t.Fatalf("Read failed after preread: %s", err) - } - if string(buf2) != "hello, world" { - t.Fatalf("preread altered the read state: got %s", string(buf2)) - } -} diff --git a/relay.go b/relay.go new file mode 100644 index 0000000..e6dd718 --- /dev/null +++ b/relay.go @@ -0,0 +1,54 @@ +package main + +import ( + "errors" + "io" + "net" + "time" +) + +type tcpForwarder struct { + Fwmark uint32 + ReadTimeout time.Duration + WriteTimeout time.Duration +} + +func (f *tcpForwarder) copyBuffer(dst net.Conn, src net.Conn) (written int64, err error) { + buf := make([]byte, 32*1024) + for { + err = src.SetReadDeadline(time.Now().Add(f.ReadTimeout)) + if err != nil { + break + } + nr, er := src.Read(buf) + if nr > 0 { + err = src.SetWriteDeadline(time.Now().Add(f.ReadTimeout)) + if err != nil { + break + } + nw, ew := dst.Write(buf[0:nr]) + if nw < 0 || nr < nw { + nw = 0 + if ew == nil { + ew = errors.New("invalid write result") + } + } + written += int64(nw) + if ew != nil { + err = ew + break + } + if nr != nw { + err = io.ErrShortWrite + break + } + } + if er != nil { + if er != io.EOF { + err = er + } + break + } + } + return written, err +} diff --git a/stream.go b/stream.go new file mode 100644 index 0000000..1508b9e --- /dev/null +++ b/stream.go @@ -0,0 +1,306 @@ +package main + +import ( + "bufio" + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "net/http" + "strconv" + "strings" + "sync" + "syscall" + + "golang.org/x/crypto/cryptobyte" +) + +// Stream represents the incoming connections to aproxy. +type Stream interface { + Host() string + Src() *net.TCPAddr + Dst() *net.TCPAddr + OriginalDst() *net.TCPAddr + io.ReadWriteCloser +} + +type ConnInfo struct { + src *net.TCPAddr + dst *net.TCPAddr + originalDst *net.TCPAddr +} + +func (i *ConnInfo) Src() *net.TCPAddr { + return i.src +} + +func (i *ConnInfo) Dst() *net.TCPAddr { + return i.dst +} + +func (i *ConnInfo) OriginalDst() *net.TCPAddr { + return i.originalDst +} + +// GetConnInfo retrieve information from the TCP connection. +func GetConnInfo(conn *net.TCPConn) (info *ConnInfo, err error) { + originalDst, err := GetSocketIPv4OriginalDst(conn) + var errno syscall.Errno + // errno 92: connection didn't go through NAT on this machine + if err != nil && !errors.As(err, &errno) && errno != 92 { + return nil, fmt.Errorf("getsockopt SO_ORIGINAL_DST failed: %s", err) + } + return &ConnInfo{ + src: conn.RemoteAddr().(*net.TCPAddr), + dst: conn.LocalAddr().(*net.TCPAddr), + originalDst: originalDst, + }, nil +} + +// PrereadConn is a wrapper around net.Conn that supports pre-reading from the underlying connection. +// Any Read before the EndPreread can be undone and read again by calling the EndPreread function. +type PrereadConn struct { + ended bool + buf []byte + mu sync.Mutex + conn net.Conn +} + +// EndPreread ends the pre-reading phase. Any Read before will be undone and data in the stream can be read again. +// EndPreread can be only called once. +func (c *PrereadConn) EndPreread() { + c.mu.Lock() + defer c.mu.Unlock() + if c.ended { + panic("call EndPreread after preread has ended or hasn't started") + } + c.ended = true +} + +// Read reads from the underlying connection. Read during the pre-reading phase can be undone by EndPreread. +func (c *PrereadConn) Read(p []byte) (n int, err error) { + c.mu.Lock() + defer c.mu.Unlock() + if c.ended { + n = copy(p, c.buf) + bufLen := len(c.buf) + c.buf = c.buf[n:] + if n == len(p) || (bufLen > 0 && bufLen == n) { + return n, nil + } + rn, err := c.conn.Read(p[n:]) + return rn + n, err + } else { + n, err = c.conn.Read(p) + c.buf = append(c.buf, p[:n]...) + return n, err + } +} + +// Write writes data to the underlying connection. +func (c *PrereadConn) Write(p []byte) (n int, err error) { + return c.conn.Write(p) +} + +// Close closes the underlying connection. +func (c *PrereadConn) Close() error { + return c.conn.Close() +} + +// NewPrereadConn wraps the network connection and return a *PrereadConn. +// It's recommended to not touch the original connection after wrapped. +func NewPrereadConn(conn net.Conn) *PrereadConn { + return &PrereadConn{conn: conn} +} + +// addPort adds the port from connection info to host if host doesn't have one +func addPort(host string, info *ConnInfo) (string, error) { + _, _, err := net.SplitHostPort(host) + if err != nil { + if strings.Contains(err.Error(), "missing port in address") { + if info.OriginalDst() != nil { + return net.JoinHostPort(host, strconv.Itoa(info.OriginalDst().Port)), nil + } + + return net.JoinHostPort(host, strconv.Itoa(info.Dst().Port)), nil + } + return "", err + } + return host, nil +} + +type HttpStream struct { + *PrereadConn + host string + *ConnInfo +} + +func (s *HttpStream) Host() string { + return s.host +} + +func NewHttpStream(conn net.Conn, info *ConnInfo) (s *HttpStream, err error) { + preread := NewPrereadConn(conn) + defer func() { + if err != nil { + err = fmt.Errorf("failed to preread HTTP request: %w", err) + } + }() + defer preread.EndPreread() + req, err := http.ReadRequest(bufio.NewReader(preread)) + if err != nil { + return nil, err + } + host := req.Host + if host != "" { + host, err = addPort(host, info) + if err != nil { + return nil, fmt.Errorf("failed to parse HTTP Host %#v: %w", host, err) + } + } + return &HttpStream{PrereadConn: preread, host: host, ConnInfo: info}, nil +} + +// PrereadSNI pre-reads the Server Name Indication (SNI) from a TLS connection. +func PrereadSNI(conn *PrereadConn) (_ string, err error) { + defer conn.EndPreread() + defer func() { + if err != nil { + err = fmt.Errorf("failed to preread TLS client hello: %w", err) + } + }() + typeVersionLen := make([]byte, 5) + n, err := conn.Read(typeVersionLen) + if n != 5 { + return "", errors.New("too short") + } + if err != nil { + return "", err + } + if typeVersionLen[0] != 22 { + return "", errors.New("not a TCP handshake") + } + msgLen := binary.BigEndian.Uint16(typeVersionLen[3:]) + buf := make([]byte, msgLen+5) + n, err = conn.Read(buf[5:]) + if n != int(msgLen) { + return "", errors.New("too short") + } + if err != nil { + return "", err + } + copy(buf[:5], typeVersionLen) + return extractSNI(buf) +} + +func extractSNI(data []byte) (string, error) { + s := cryptobyte.String(data) + var ( + version uint16 + random []byte + sessionId []byte + ) + + if !s.Skip(9) || + !s.ReadUint16(&version) || !s.ReadBytes(&random, 32) || + !s.ReadUint8LengthPrefixed((*cryptobyte.String)(&sessionId)) { + return "", fmt.Errorf("failed to parse TLS client hello version, random or session id") + } + + var cipherSuitesData cryptobyte.String + if !s.ReadUint16LengthPrefixed(&cipherSuitesData) { + return "", fmt.Errorf("failed to parse TLS client hello cipher suites") + } + + var cipherSuites []uint16 + for !cipherSuitesData.Empty() { + var suite uint16 + if !cipherSuitesData.ReadUint16(&suite) { + return "", fmt.Errorf("failed to parse TLS client hello cipher suites") + } + cipherSuites = append(cipherSuites, suite) + } + + var compressionMethods []byte + if !s.ReadUint8LengthPrefixed((*cryptobyte.String)(&compressionMethods)) { + return "", fmt.Errorf("failed to parse TLS client hello compression methods") + } + + if s.Empty() { + // ClientHello is optionally followed by extension data + return "", fmt.Errorf("no extension data in TLS client hello") + } + + var extensions cryptobyte.String + if !s.ReadUint16LengthPrefixed(&extensions) || !s.Empty() { + return "", fmt.Errorf("failed to parse TLS client hello extensions") + } + + finalServerName := "" + for !extensions.Empty() { + var extension uint16 + var extData cryptobyte.String + if !extensions.ReadUint16(&extension) || + !extensions.ReadUint16LengthPrefixed(&extData) { + return "", fmt.Errorf("failed to parse TLS client hello extension") + } + if extension != 0 { + continue + } + var nameList cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&nameList) || nameList.Empty() { + return "", fmt.Errorf("failed to parse server name extension") + } + + for !nameList.Empty() { + var nameType uint8 + var serverName cryptobyte.String + if !nameList.ReadUint8(&nameType) || + !nameList.ReadUint16LengthPrefixed(&serverName) || + serverName.Empty() { + return "", fmt.Errorf("failed to parse server name indication extension") + } + if nameType != 0 { + continue + } + if len(finalServerName) != 0 { + return "", fmt.Errorf("multiple names of the same name_type are prohibited in server name extension") + } + finalServerName = string(serverName) + if strings.HasSuffix(finalServerName, ".") { + return "", fmt.Errorf("SNI name ends with a trailing dot") + } + } + } + return finalServerName, nil +} + +type TlsStream struct { + *PrereadConn + host string + *ConnInfo +} + +func (s *TlsStream) Host() string { + return s.host +} + +func NewTlsStream(conn net.Conn, info *ConnInfo) (*TlsStream, error) { + preread := NewPrereadConn(conn) + sni, err := PrereadSNI(preread) + if err != nil { + return nil, err + } + if sni != "" { + sni, err = addPort(sni, info) + if err != nil { + return nil, fmt.Errorf("failed to parse SNI %#v as host: %w", sni, err) + } + } + return &TlsStream{ + PrereadConn: preread, + host: sni, + ConnInfo: info, + }, nil +} diff --git a/stream_test.go b/stream_test.go new file mode 100644 index 0000000..005bdc0 --- /dev/null +++ b/stream_test.go @@ -0,0 +1,146 @@ +package main + +import ( + "bytes" + "encoding/hex" + "io" + "net" + "testing" +) + +func TestPrereadConn(t *testing.T) { + remote, local := net.Pipe() + go remote.Write([]byte("hello, world")) + preread := &PrereadConn{conn: local} + buf := make([]byte, 5) + _, err := preread.Read(buf) + if err != nil { + t.Fatalf("Read failed during preread: %s", err) + } + buf = make([]byte, 3) + _, err = preread.Read(buf) + if err != nil { + t.Fatalf("Read failed during preread: %s", err) + } + preread.EndPreread() + buf2 := make([]byte, 12) + _, err = io.ReadFull(preread, buf2) + if err != nil { + t.Fatalf("Read failed after preread: %s", err) + } + if string(buf2) != "hello, world" { + t.Fatalf("preread altered the read state: got %s", string(buf2)) + } +} + +func TestNewHttpStream(t *testing.T) { + remote, local := net.Pipe() + payload := []byte("GET / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 0\r\n\r\n") + go remote.Write(payload) + s, err := NewHttpStream(local, &ConnInfo{ + src: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8443}, + dst: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 12345}, + originalDst: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 80}, + }) + if err != nil { + t.Fatalf("NewHttpStream failed: %s", err) + } + if s.Host() != "example.com:80" { + t.Fatalf("incorrect host in HttpStream, expect: \"example.com:80\", got: %#v", s.Host()) + } + buf := make([]byte, len(payload)) + _, err = io.ReadFull(s, buf) + if err != nil { + t.Fatalf("HttpStream.Read failed: %s", err) + } + if !bytes.Equal(payload, buf) { + t.Fatalf("HttpStream.Read failed, expect: %#v, got: %#v", string(payload), string(buf)) + } +} + +func TestNewHttpStreamNonDefaultPort(t *testing.T) { + remote, local := net.Pipe() + payload := []byte("GET / HTTP/1.1\r\nHost: example.com:8080\r\nContent-Length: 0\r\n\r\n") + go remote.Write(payload) + s, err := NewHttpStream(local, &ConnInfo{ + src: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8443}, + dst: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 12345}, + originalDst: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8080}, + }) + if err != nil { + t.Fatalf("NewHttpStream failed: %s", err) + } + if s.Host() != "example.com:8080" { + t.Fatalf("incorrect host in HttpStream, expect: \"example.com:8080\", got: %#v", s.Host()) + } +} + +func TestNewTlsStream(t *testing.T) { + remote, local := net.Pipe() + // data obtained from https://gitlab.com/wireshark/wireshark/-/blob/master/test/captures/tls12-aes256gcm.pcap + clientHello, _ := hex.DecodeString( + "160301004f0100004b0303588e60d1d96bad5f1fcf0b8818466257d73385bdaaed0ac4bfd7228a6da059ad00000200a901000020" + + "0005000501000000000000000e000c0000096c6f63616c686f7374ff01000100") + go remote.Write(clientHello) + s, err := NewTlsStream(local, &ConnInfo{ + src: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8443}, + dst: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 12345}, + originalDst: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 443}, + }) + if err != nil { + t.Fatalf("NewTlsStream failed: %s", err) + } + if s.Host() != "localhost:443" { + t.Fatalf("incorrect host in TlsStream, expect: \"localhost:443\", got: %#v", s.Host()) + } + buf := make([]byte, len(clientHello)) + _, err = io.ReadFull(s, buf) + if err != nil { + t.Fatalf("TlsStream.Read failed: %s", err) + } + if !bytes.Equal(clientHello, buf) { + t.Fatalf("TlsStream.Read failed, expect: %#v, got: %#v", string(clientHello), string(buf)) + } +} + +func TestNewTlsStreamWithoutSNI(t *testing.T) { + remote, local := net.Pipe() + clientHello, _ := hex.DecodeString("160301012801000124030315a03a6cbea1ff32d0fb9af5d6d94988e212b6bcf15a3e672ed" + + "7d31f6d946edd20f8879d969a75d1da26560c92a942f13458a0cd2a96e690c0fa628ff6357119de0062130313021301cca9cca8ccaa" + + "c030c02cc028c024c014c00a009f006b0039ff8500c400880081009d003d003500c00084c02fc02bc027c023c013c009009e0067003" + + "300be0045009c003c002f00ba0041c011c00700050004c012c0080016000a00ff01000079002b000908030403030302030100330026" + + "0024001d00203754ae4e94f3a5fb69709af119b982db1322c5da9299f7ce0da661a05f06ce35000b00020100000a000a0008001d001" + + "700180019000d00180016080606010603080505010503080404010403020102030010000e000c02683208687474702f312e31") + go remote.Write(clientHello) + s, err := NewTlsStream(local, &ConnInfo{ + src: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8443}, + dst: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 12345}, + originalDst: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 443}, + }) + if err != nil { + t.Fatalf("NewTlsStream failed: %s", err) + } + if s.Host() != "" { + t.Fatalf("incorrect host in TlsStream, expect: \"\", got: %#v", s.Host()) + } +} + +func TestNewTlsStreamNonDefaultPort(t *testing.T) { + remote, local := net.Pipe() + // data obtained from https://gitlab.com/wireshark/wireshark/-/blob/master/test/captures/tls12-aes256gcm.pcap + clientHello, _ := hex.DecodeString( + "160301004f0100004b0303588e60d1d96bad5f1fcf0b8818466257d73385bdaaed0ac4bfd7228a6da059ad00000200a901000020" + + "0005000501000000000000000e000c0000096c6f63616c686f7374ff01000100") + go remote.Write(clientHello) + s, err := NewTlsStream(local, &ConnInfo{ + src: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8443}, + dst: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 12345}, + originalDst: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 1443}, + }) + if err != nil { + t.Fatalf("NewTlsStream failed: %s", err) + } + if s.Host() != "localhost:1443" { + t.Fatalf("incorrect host in TlsStream, expect: \"localhost:1443\", got: %#v", s.Host()) + } +} diff --git a/syscall_linux.go b/syscall_linux.go index e57ed39..6f6649f 100644 --- a/syscall_linux.go +++ b/syscall_linux.go @@ -29,7 +29,7 @@ func GetSocketIPv4OriginalDst(conn *net.TCPConn) (*net.TCPAddr, error) { 0, ) if e != 0 { - return nil, fmt.Errorf("getsockopt SO_ORIGINAL_DST failed: errno %d", e) + return nil, e } return &net.TCPAddr{ IP: sockaddr[4:8],