From cfbeb4d91883d617b375d357e668d5fd770b3ec8 Mon Sep 17 00:00:00 2001 From: Sander Bruens Date: Mon, 29 Apr 2024 14:47:50 -0400 Subject: [PATCH 1/8] Create a proxy package to separate the proxy handling. --- service/proxy/proxy.go | 15 +++++++++++++++ service/tcp.go | 8 ++------ 2 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 service/proxy/proxy.go diff --git a/service/proxy/proxy.go b/service/proxy/proxy.go new file mode 100644 index 00000000..dfb36219 --- /dev/null +++ b/service/proxy/proxy.go @@ -0,0 +1,15 @@ +package proxy + +import ( + "io" + + "github.com/shadowsocks/go-shadowsocks2/socks" +) + +func ParseShadowsocks(reader io.Reader) (string, error) { + tgtAddr, err := socks.ReadAddr(reader) + if err != nil { + return "", err + } + return tgtAddr.String(), nil +} diff --git a/service/tcp.go b/service/tcp.go index 9761d8f3..dc86cfca 100644 --- a/service/tcp.go +++ b/service/tcp.go @@ -31,8 +31,8 @@ import ( "github.com/Jigsaw-Code/outline-ss-server/ipinfo" onet "github.com/Jigsaw-Code/outline-ss-server/net" "github.com/Jigsaw-Code/outline-ss-server/service/metrics" + "github.com/Jigsaw-Code/outline-ss-server/service/proxy" logging "github.com/op/go-logging" - "github.com/shadowsocks/go-shadowsocks2/socks" ) // TCPMetrics is used to report metrics on TCP connections. @@ -282,11 +282,7 @@ func getProxyRequest(clientConn transport.StreamConn) (string, error) { // case 1, 3 or 4: Shadowsocks (address type) // case 5: SOCKS5 (protocol version) // case "C": HTTP CONNECT (first char of method) - tgtAddr, err := socks.ReadAddr(clientConn) - if err != nil { - return "", err - } - return tgtAddr.String(), nil + return proxy.ParseShadowsocks(clientConn) } func proxyConnection(ctx context.Context, dialer transport.StreamDialer, tgtAddr string, clientConn transport.StreamConn) *onet.ConnectionError { From bd24148e100ccdbcb565e4ef2a3319e8fa0d9df0 Mon Sep 17 00:00:00 2001 From: Sander Bruens Date: Mon, 29 Apr 2024 14:58:07 -0400 Subject: [PATCH 2/8] Add a buffered connection type. --- service/tcp.go | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/service/tcp.go b/service/tcp.go index dc86cfca..b41b391e 100644 --- a/service/tcp.go +++ b/service/tcp.go @@ -15,7 +15,7 @@ package service import ( - "bytes" + "bufio" "container/list" "context" "errors" @@ -46,6 +46,23 @@ type TCPMetrics interface { AddTCPProbe(status, drainResult string, port int, clientProxyBytes int64) } +type bufferedConn struct { + r *bufio.Reader + net.Conn +} + +func newBufferedConn(c net.Conn) bufferedConn { + return bufferedConn{bufio.NewReader(c), c} +} + +func (b bufferedConn) Peek(n int) ([]byte, error) { + return b.r.Peek(n) +} + +func (b bufferedConn) Read(p []byte) (int, error) { + return b.r.Read(p) +} + func remoteIP(conn net.Conn) net.IP { addr := conn.RemoteAddr() if addr == nil { @@ -76,12 +93,12 @@ func debugTCP(cipherID, template string, val interface{}) { // required = saltSize + 2 + cipher.TagSize, the number of bytes needed to authenticate the connection. const bytesForKeyFinding = 50 -func findAccessKey(clientReader io.Reader, clientIP net.IP, cipherList CipherList) (*CipherEntry, io.Reader, []byte, time.Duration, error) { +func findAccessKey(bufferedConn bufferedConn, clientIP net.IP, cipherList CipherList) (*CipherEntry, []byte, time.Duration, error) { // We snapshot the list because it may be modified while we use it. ciphers := cipherList.SnapshotForClientIP(clientIP) - firstBytes := make([]byte, bytesForKeyFinding) - if n, err := io.ReadFull(clientReader, firstBytes); err != nil { - return nil, clientReader, nil, 0, fmt.Errorf("reading header failed after %d bytes: %w", n, err) + firstBytes, err := bufferedConn.Peek(bytesForKeyFinding) + if err != nil { + return nil, nil, 0, fmt.Errorf("reading header failed: %w", err) } findStartTime := time.Now() @@ -89,13 +106,13 @@ func findAccessKey(clientReader io.Reader, clientIP net.IP, cipherList CipherLis timeToCipher := time.Since(findStartTime) if entry == nil { // TODO: Ban and log client IPs with too many failures too quick to protect against DoS. - return nil, clientReader, nil, timeToCipher, fmt.Errorf("could not find valid TCP cipher") + return nil, nil, timeToCipher, fmt.Errorf("could not find valid TCP cipher") } // Move the active cipher to the front, so that the search is quicker next time. cipherList.MarkUsedByClientIP(elt, clientIP) salt := firstBytes[:entry.CryptoKey.SaltSize()] - return entry, io.MultiReader(bytes.NewReader(firstBytes), clientReader), salt, timeToCipher, nil + return entry, salt, timeToCipher, nil } // Implements a trial decryption search. This assumes that all ciphers are AEAD. @@ -128,8 +145,9 @@ type ShadowsocksTCPMetrics interface { // TODO(fortuna): Offer alternative transports. func NewShadowsocksStreamAuthenticator(ciphers CipherList, replayCache *ReplayCache, metrics ShadowsocksTCPMetrics) StreamAuthenticateFunc { return func(clientConn transport.StreamConn) (string, transport.StreamConn, *onet.ConnectionError) { + bufferedConn := newBufferedConn(clientConn) // Find the cipher and acess key id. - cipherEntry, clientReader, clientSalt, timeToCipher, keyErr := findAccessKey(clientConn, remoteIP(clientConn), ciphers) + cipherEntry, clientSalt, timeToCipher, keyErr := findAccessKey(bufferedConn, remoteIP(clientConn), ciphers) metrics.AddTCPCipherSearch(keyErr == nil, timeToCipher) if keyErr != nil { const status = "ERR_CIPHER" @@ -153,8 +171,8 @@ func NewShadowsocksStreamAuthenticator(ciphers CipherList, replayCache *ReplayCa return id, nil, onet.NewConnectionError(status, "Replay detected", nil) } - ssr := shadowsocks.NewReader(clientReader, cipherEntry.CryptoKey) - ssw := shadowsocks.NewWriter(clientConn, cipherEntry.CryptoKey) + ssr := shadowsocks.NewReader(bufferedConn, cipherEntry.CryptoKey) + ssw := shadowsocks.NewWriter(bufferedConn, cipherEntry.CryptoKey) ssw.SetSaltGenerator(cipherEntry.SaltGenerator) return id, transport.WrapConn(clientConn, ssr, ssw), nil } From d538c462284d7c71b34b7e405708c42d3e8ae553 Mon Sep 17 00:00:00 2001 From: Sander Bruens Date: Mon, 29 Apr 2024 15:12:53 -0400 Subject: [PATCH 3/8] Determine how to handle request based on first byte. --- service/tcp.go | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/service/tcp.go b/service/tcp.go index b41b391e..7bdf716a 100644 --- a/service/tcp.go +++ b/service/tcp.go @@ -33,6 +33,7 @@ import ( "github.com/Jigsaw-Code/outline-ss-server/service/metrics" "github.com/Jigsaw-Code/outline-ss-server/service/proxy" logging "github.com/op/go-logging" + "github.com/shadowsocks/go-shadowsocks2/socks" ) // TCPMetrics is used to report metrics on TCP connections. @@ -295,12 +296,28 @@ func (h *tcpHandler) Handle(ctx context.Context, clientConn transport.StreamConn logger.Debugf("Done with status %v, duration %v", status, connDuration) } -func getProxyRequest(clientConn transport.StreamConn) (string, error) { - // TODO(fortuna): Use Shadowsocks proxy, HTTP CONNECT or SOCKS5 based on first byte: - // case 1, 3 or 4: Shadowsocks (address type) +func getProxyRequest(bufferedConn bufferedConn) (string, error) { + firstByte, err := bufferedConn.Peek(1) + if err != nil { + return "", fmt.Errorf("reading header failed: %w", err) + } + + // TODO(fortuna): Add support for HTTP CONNECT or SOCKS5: // case 5: SOCKS5 (protocol version) // case "C": HTTP CONNECT (first char of method) - return proxy.ParseShadowsocks(clientConn) + switch firstByte[0] { + + // Shadowsocks address types follow the SOCKS5 address format: + // See https://shadowsocks.org/doc/what-is-shadowsocks.html#addressing. + case socks.AtypIPv4, socks.AtypDomainName, socks.AtypIPv6: + logger.Debug("Proxy protocol detected: Shadowsocks") + return proxy.ParseShadowsocks(bufferedConn) + + default: + logger.Warningf("Unknown proxy protocol (first byte: % x)", firstByte) + return "", fmt.Errorf("unsupported proxy protocol") + + } } func proxyConnection(ctx context.Context, dialer transport.StreamDialer, tgtAddr string, clientConn transport.StreamConn) *onet.ConnectionError { @@ -360,8 +377,9 @@ func (h *tcpHandler) handleConnection(ctx context.Context, listenerPort int, cli } h.m.AddAuthenticatedTCPConnection(outerConn.RemoteAddr(), id) + bufferedConn := newBufferedConn(innerConn) // Read target address and dial it. - tgtAddr, err := getProxyRequest(innerConn) + tgtAddr, err := getProxyRequest(bufferedConn) // Clear the deadline for the target address outerConn.SetReadDeadline(time.Time{}) if err != nil { From 15124152d0829b788d4391a79d1ad2f225792433 Mon Sep 17 00:00:00 2001 From: Sander Bruens Date: Mon, 29 Apr 2024 15:42:51 -0400 Subject: [PATCH 4/8] Add support for SOCKS5 and HTTP CONNECT. --- net/buffered_conn.go | 37 +++++++++++++++++++++++++++++++ service/proxy/proxy.go | 38 ++++++++++++++++++++++++++++++++ service/tcp.go | 50 ++++++++++++++++-------------------------- 3 files changed, 94 insertions(+), 31 deletions(-) create mode 100644 net/buffered_conn.go diff --git a/net/buffered_conn.go b/net/buffered_conn.go new file mode 100644 index 00000000..feb61988 --- /dev/null +++ b/net/buffered_conn.go @@ -0,0 +1,37 @@ +// Copyright 2024 Jigsaw Operations LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package net + +import ( + "bufio" + "net" +) + +type BufferedConn struct { + R *bufio.Reader + net.Conn +} + +func NewBufferedConn(c net.Conn) BufferedConn { + return BufferedConn{bufio.NewReader(c), c} +} + +func (b BufferedConn) Peek(n int) ([]byte, error) { + return b.R.Peek(n) +} + +func (b BufferedConn) Read(p []byte) (int, error) { + return b.R.Read(p) +} diff --git a/service/proxy/proxy.go b/service/proxy/proxy.go index dfb36219..fb84f5d2 100644 --- a/service/proxy/proxy.go +++ b/service/proxy/proxy.go @@ -1,8 +1,24 @@ +// Copyright 2024 Jigsaw Operations LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package proxy import ( "io" + "net/http" + onet "github.com/Jigsaw-Code/outline-ss-server/net" "github.com/shadowsocks/go-shadowsocks2/socks" ) @@ -13,3 +29,25 @@ func ParseShadowsocks(reader io.Reader) (string, error) { } return tgtAddr.String(), nil } + +func ParseSocks(rw io.ReadWriter) (string, error) { + tgtAddr, err := socks.Handshake(rw) + if err != nil { + return "", err + } + + return tgtAddr.String(), nil +} + +func ParseHttp(conn onet.BufferedConn) (string, error) { + req, err := http.ReadRequest(conn.R) + if err != nil { + return "", err + } + + conn.Write([]byte("HTTP/1.1 200 Connection Established\r\n" + + "Proxy-agent: Outline-Proxy\r\n" + + "\r\n")) + target := req.RequestURI + return target, nil +} diff --git a/service/tcp.go b/service/tcp.go index 7bdf716a..46bf2232 100644 --- a/service/tcp.go +++ b/service/tcp.go @@ -47,23 +47,6 @@ type TCPMetrics interface { AddTCPProbe(status, drainResult string, port int, clientProxyBytes int64) } -type bufferedConn struct { - r *bufio.Reader - net.Conn -} - -func newBufferedConn(c net.Conn) bufferedConn { - return bufferedConn{bufio.NewReader(c), c} -} - -func (b bufferedConn) Peek(n int) ([]byte, error) { - return b.r.Peek(n) -} - -func (b bufferedConn) Read(p []byte) (int, error) { - return b.r.Read(p) -} - func remoteIP(conn net.Conn) net.IP { addr := conn.RemoteAddr() if addr == nil { @@ -94,10 +77,10 @@ func debugTCP(cipherID, template string, val interface{}) { // required = saltSize + 2 + cipher.TagSize, the number of bytes needed to authenticate the connection. const bytesForKeyFinding = 50 -func findAccessKey(bufferedConn bufferedConn, clientIP net.IP, cipherList CipherList) (*CipherEntry, []byte, time.Duration, error) { +func findAccessKey(bufReader *bufio.Reader, clientIP net.IP, cipherList CipherList) (*CipherEntry, []byte, time.Duration, error) { // We snapshot the list because it may be modified while we use it. ciphers := cipherList.SnapshotForClientIP(clientIP) - firstBytes, err := bufferedConn.Peek(bytesForKeyFinding) + firstBytes, err := bufReader.Peek(bytesForKeyFinding) if err != nil { return nil, nil, 0, fmt.Errorf("reading header failed: %w", err) } @@ -146,9 +129,9 @@ type ShadowsocksTCPMetrics interface { // TODO(fortuna): Offer alternative transports. func NewShadowsocksStreamAuthenticator(ciphers CipherList, replayCache *ReplayCache, metrics ShadowsocksTCPMetrics) StreamAuthenticateFunc { return func(clientConn transport.StreamConn) (string, transport.StreamConn, *onet.ConnectionError) { - bufferedConn := newBufferedConn(clientConn) + bufConn := onet.NewBufferedConn(clientConn) // Find the cipher and acess key id. - cipherEntry, clientSalt, timeToCipher, keyErr := findAccessKey(bufferedConn, remoteIP(clientConn), ciphers) + cipherEntry, clientSalt, timeToCipher, keyErr := findAccessKey(bufConn.R, remoteIP(bufConn), ciphers) metrics.AddTCPCipherSearch(keyErr == nil, timeToCipher) if keyErr != nil { const status = "ERR_CIPHER" @@ -172,8 +155,8 @@ func NewShadowsocksStreamAuthenticator(ciphers CipherList, replayCache *ReplayCa return id, nil, onet.NewConnectionError(status, "Replay detected", nil) } - ssr := shadowsocks.NewReader(bufferedConn, cipherEntry.CryptoKey) - ssw := shadowsocks.NewWriter(bufferedConn, cipherEntry.CryptoKey) + ssr := shadowsocks.NewReader(bufConn, cipherEntry.CryptoKey) + ssw := shadowsocks.NewWriter(bufConn, cipherEntry.CryptoKey) ssw.SetSaltGenerator(cipherEntry.SaltGenerator) return id, transport.WrapConn(clientConn, ssr, ssw), nil } @@ -296,22 +279,27 @@ func (h *tcpHandler) Handle(ctx context.Context, clientConn transport.StreamConn logger.Debugf("Done with status %v, duration %v", status, connDuration) } -func getProxyRequest(bufferedConn bufferedConn) (string, error) { - firstByte, err := bufferedConn.Peek(1) +func getProxyRequest(bufConn onet.BufferedConn) (string, error) { + firstByte, err := bufConn.Peek(1) if err != nil { return "", fmt.Errorf("reading header failed: %w", err) } - // TODO(fortuna): Add support for HTTP CONNECT or SOCKS5: - // case 5: SOCKS5 (protocol version) - // case "C": HTTP CONNECT (first char of method) switch firstByte[0] { // Shadowsocks address types follow the SOCKS5 address format: // See https://shadowsocks.org/doc/what-is-shadowsocks.html#addressing. case socks.AtypIPv4, socks.AtypDomainName, socks.AtypIPv6: logger.Debug("Proxy protocol detected: Shadowsocks") - return proxy.ParseShadowsocks(bufferedConn) + return proxy.ParseShadowsocks(bufConn) + + case 0x05: + logger.Debug("Proxy protocol detected: SOCKS5") + return proxy.ParseSocks(bufConn) + + case 0x43: + logger.Debug("Proxy protocol detected: HTTP CONNECT") + return proxy.ParseHttp(bufConn) default: logger.Warningf("Unknown proxy protocol (first byte: % x)", firstByte) @@ -377,9 +365,9 @@ func (h *tcpHandler) handleConnection(ctx context.Context, listenerPort int, cli } h.m.AddAuthenticatedTCPConnection(outerConn.RemoteAddr(), id) - bufferedConn := newBufferedConn(innerConn) + bufConn := onet.NewBufferedConn(innerConn) // Read target address and dial it. - tgtAddr, err := getProxyRequest(bufferedConn) + tgtAddr, err := getProxyRequest(bufConn) // Clear the deadline for the target address outerConn.SetReadDeadline(time.Time{}) if err != nil { From 6cd622736addbf8434a693801981acbe72a6c3bc Mon Sep 17 00:00:00 2001 From: Sander Bruens Date: Wed, 1 May 2024 12:31:15 -0400 Subject: [PATCH 5/8] Fix styling of initialism. --- service/proxy/proxy.go | 2 +- service/tcp.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/service/proxy/proxy.go b/service/proxy/proxy.go index fb84f5d2..68938e72 100644 --- a/service/proxy/proxy.go +++ b/service/proxy/proxy.go @@ -39,7 +39,7 @@ func ParseSocks(rw io.ReadWriter) (string, error) { return tgtAddr.String(), nil } -func ParseHttp(conn onet.BufferedConn) (string, error) { +func ParseHTTP(conn onet.BufferedConn) (string, error) { req, err := http.ReadRequest(conn.R) if err != nil { return "", err diff --git a/service/tcp.go b/service/tcp.go index 46bf2232..f454de73 100644 --- a/service/tcp.go +++ b/service/tcp.go @@ -299,7 +299,7 @@ func getProxyRequest(bufConn onet.BufferedConn) (string, error) { case 0x43: logger.Debug("Proxy protocol detected: HTTP CONNECT") - return proxy.ParseHttp(bufConn) + return proxy.ParseHTTP(bufConn) default: logger.Warningf("Unknown proxy protocol (first byte: % x)", firstByte) From a67f5dbef127d5c27d589f773a15f03d3cdbc7ed Mon Sep 17 00:00:00 2001 From: Sander Bruens Date: Wed, 1 May 2024 12:48:29 -0400 Subject: [PATCH 6/8] Clean up `bufConn` type. --- net/{buffered_conn.go => bufio.go} | 18 ++++++++++-------- service/proxy/proxy.go | 4 ++-- service/tcp.go | 8 ++++---- 3 files changed, 16 insertions(+), 14 deletions(-) rename net/{buffered_conn.go => bufio.go} (62%) diff --git a/net/buffered_conn.go b/net/bufio.go similarity index 62% rename from net/buffered_conn.go rename to net/bufio.go index feb61988..530f6c8e 100644 --- a/net/buffered_conn.go +++ b/net/bufio.go @@ -19,19 +19,21 @@ import ( "net" ) -type BufferedConn struct { - R *bufio.Reader +// BufConn wraps an original [net.Conn] and a [bufio.Reader] to allow reads +// without losing bytes in the buffer. +type BufConn struct { + *bufio.Reader net.Conn } -func NewBufferedConn(c net.Conn) BufferedConn { - return BufferedConn{bufio.NewReader(c), c} +func NewBufConn(conn net.Conn) BufConn { + return BufConn{Reader: bufio.NewReader(conn), Conn: conn} } -func (b BufferedConn) Peek(n int) ([]byte, error) { - return b.R.Peek(n) +func (c BufConn) Peek(n int) ([]byte, error) { + return c.Reader.Peek(n) } -func (b BufferedConn) Read(p []byte) (int, error) { - return b.R.Read(p) +func (c BufConn) Read(p []byte) (int, error) { + return c.Reader.Read(p) } diff --git a/service/proxy/proxy.go b/service/proxy/proxy.go index 68938e72..6db97517 100644 --- a/service/proxy/proxy.go +++ b/service/proxy/proxy.go @@ -39,8 +39,8 @@ func ParseSocks(rw io.ReadWriter) (string, error) { return tgtAddr.String(), nil } -func ParseHTTP(conn onet.BufferedConn) (string, error) { - req, err := http.ReadRequest(conn.R) +func ParseHTTP(conn onet.BufConn) (string, error) { + req, err := http.ReadRequest(conn.Reader) if err != nil { return "", err } diff --git a/service/tcp.go b/service/tcp.go index f454de73..623b8fc4 100644 --- a/service/tcp.go +++ b/service/tcp.go @@ -129,9 +129,9 @@ type ShadowsocksTCPMetrics interface { // TODO(fortuna): Offer alternative transports. func NewShadowsocksStreamAuthenticator(ciphers CipherList, replayCache *ReplayCache, metrics ShadowsocksTCPMetrics) StreamAuthenticateFunc { return func(clientConn transport.StreamConn) (string, transport.StreamConn, *onet.ConnectionError) { - bufConn := onet.NewBufferedConn(clientConn) + bufConn := onet.NewBufConn(clientConn) // Find the cipher and acess key id. - cipherEntry, clientSalt, timeToCipher, keyErr := findAccessKey(bufConn.R, remoteIP(bufConn), ciphers) + cipherEntry, clientSalt, timeToCipher, keyErr := findAccessKey(bufConn.Reader, remoteIP(bufConn), ciphers) metrics.AddTCPCipherSearch(keyErr == nil, timeToCipher) if keyErr != nil { const status = "ERR_CIPHER" @@ -279,7 +279,7 @@ func (h *tcpHandler) Handle(ctx context.Context, clientConn transport.StreamConn logger.Debugf("Done with status %v, duration %v", status, connDuration) } -func getProxyRequest(bufConn onet.BufferedConn) (string, error) { +func getProxyRequest(bufConn onet.BufConn) (string, error) { firstByte, err := bufConn.Peek(1) if err != nil { return "", fmt.Errorf("reading header failed: %w", err) @@ -365,7 +365,7 @@ func (h *tcpHandler) handleConnection(ctx context.Context, listenerPort int, cli } h.m.AddAuthenticatedTCPConnection(outerConn.RemoteAddr(), id) - bufConn := onet.NewBufferedConn(innerConn) + bufConn := onet.NewBufConn(innerConn) // Read target address and dial it. tgtAddr, err := getProxyRequest(bufConn) // Clear the deadline for the target address From aa50580702cc79137fed4deb80dddef00710d26f Mon Sep 17 00:00:00 2001 From: Sander Bruens Date: Wed, 1 May 2024 13:17:09 -0400 Subject: [PATCH 7/8] Fix cipher test. --- service/tcp_test.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/service/tcp_test.go b/service/tcp_test.go index e3742806..2c78a711 100644 --- a/service/tcp_test.go +++ b/service/tcp_test.go @@ -15,6 +15,7 @@ package service import ( + "bufio" "bytes" "errors" "fmt" @@ -101,7 +102,7 @@ func BenchmarkTCPFindCipherFail(b *testing.B) { } clientIP := clientConn.RemoteAddr().(*net.TCPAddr).IP b.StartTimer() - findAccessKey(clientConn, clientIP, cipherList) + findAccessKey(bufio.NewReader(clientConn), clientIP, cipherList) b.StopTimer() } } @@ -199,17 +200,14 @@ func BenchmarkTCPFindCipherRepeat(b *testing.B) { cipherNumber := byte(n % numCiphers) reader, writer := io.Pipe() clientIP := net.IPv4(192, 0, 2, cipherNumber) - addr := &net.TCPAddr{IP: clientIP, Port: 54321} - c := conn{clientAddr: addr, reader: reader, writer: writer} cipher := cipherEntries[cipherNumber].CryptoKey go shadowsocks.NewWriter(writer, cipher).Write(makeTestPayload(50)) b.StartTimer() - _, _, _, _, err := findAccessKey(&c, clientIP, cipherList) + _, _, _, err := findAccessKey(bufio.NewReader(reader), clientIP, cipherList) b.StopTimer() if err != nil { b.Error(err) } - c.Close() } } From d628bb17b62aba98c23f1857604e50464439e287 Mon Sep 17 00:00:00 2001 From: Sander Bruens Date: Wed, 1 May 2024 17:14:15 -0400 Subject: [PATCH 8/8] Add some more comments around how we identify the proxy protocol. --- service/tcp.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/service/tcp.go b/service/tcp.go index 623b8fc4..643f011f 100644 --- a/service/tcp.go +++ b/service/tcp.go @@ -280,6 +280,7 @@ func (h *tcpHandler) Handle(ctx context.Context, clientConn transport.StreamConn } func getProxyRequest(bufConn onet.BufConn) (string, error) { + // We try to identify the used proxy protocols based on the first byte received. firstByte, err := bufConn.Peek(1) if err != nil { return "", fmt.Errorf("reading header failed: %w", err) @@ -287,16 +288,19 @@ func getProxyRequest(bufConn onet.BufConn) (string, error) { switch firstByte[0] { - // Shadowsocks address types follow the SOCKS5 address format: - // See https://shadowsocks.org/doc/what-is-shadowsocks.html#addressing. + // Shadowsocks: The first character represents the address type. Note that Shadowsocks address types + // follow the SOCKS5 address format. See https://shadowsocks.org/doc/what-is-shadowsocks.html#addressing. case socks.AtypIPv4, socks.AtypDomainName, socks.AtypIPv6: logger.Debug("Proxy protocol detected: Shadowsocks") return proxy.ParseShadowsocks(bufConn) + // SOCKS5: The first character represents the protocol version (05). See + // https://datatracker.ietf.org/doc/html/rfc1928#autoid-4. case 0x05: logger.Debug("Proxy protocol detected: SOCKS5") return proxy.ParseSocks(bufConn) + // HTTP CONNECT: The first character of the "CONNECT" method ("C"). case 0x43: logger.Debug("Proxy protocol detected: HTTP CONNECT") return proxy.ParseHTTP(bufConn)