From 639ff721e7ce69b5430eee4080887d21250b8108 Mon Sep 17 00:00:00 2001 From: Sirius Ivliev Date: Sat, 2 Nov 2024 12:56:41 +0100 Subject: [PATCH 01/20] OOB and disOOB implementation --- transport/oob/oob.go | 132 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 transport/oob/oob.go diff --git a/transport/oob/oob.go b/transport/oob/oob.go new file mode 100644 index 00000000..af0733f7 --- /dev/null +++ b/transport/oob/oob.go @@ -0,0 +1,132 @@ +package oob + +import ( + "context" + "errors" + "io" + "net" + "syscall" + + "github.com/Jigsaw-Code/outline-sdk/transport" +) + +type oobWriter struct { + writer io.Writer + oobPosition int64 + oobByte byte // Byte to send as OOB + disOOB bool // Flag to enable disOOB mode +} + +var _ io.Writer = (*oobWriter)(nil) + +type oobWriterReaderFrom struct { + *oobWriter + rf io.ReaderFrom +} + +var _ io.ReaderFrom = (*oobWriterReaderFrom)(nil) + +// NewOOBWriter creates an [io.Writer] that sends an OOB byte at the specified "oobPosition". +// If disOOB is enabled, it will apply the --disOOB strategy. +// "oobByte" specifies the value of the byte to send out-of-band. +func NewOOBWriter(writer io.Writer, oobPosition int64, oobByte byte, disOOB bool) io.Writer { + ow := &oobWriter{writer: writer, oobPosition: oobPosition, oobByte: oobByte, disOOB: disOOB} + if rf, ok := writer.(io.ReaderFrom); ok { + return &oobWriterReaderFrom{ow, rf} + } + return ow +} + +func (w *oobWriterReaderFrom) ReadFrom(source io.Reader) (int64, error) { + reader := io.MultiReader(io.LimitReader(source, w.oobPosition), source) + written, err := w.rf.ReadFrom(reader) + w.oobPosition -= written + return written, err +} + +func (w *oobWriter) Write(data []byte) (int, error) { + var written int + var err error + + if conn, ok := w.writer.(net.Conn); ok { + if w.oobPosition > 0 && w.oobPosition < int64(len(data)) { + // Write the first part with the regular writer + written, err = w.writer.Write(data[:w.oobPosition]) + if err != nil { + return written, err + } + + // Send the specified OOB byte using the new sendOOBByte method + err = w.sendOOBByte(conn) + if err != nil { + return written, err + } + data = data[written+1:] // Skip the OOB byte + } + } + // Write the remaining data + n, err := w.writer.Write(data) + written += n + return written, err +} + +// sendOOBByte sends the specified OOB byte over the provided connection. +// It sets the appropriate flags based on whether disOOB mode is enabled. +func (w *oobWriter) sendOOBByte(conn net.Conn) error { + // Attempt to convert to *net.TCPConn to access SyscallConn + tcpConn, ok := conn.(*net.TCPConn) + if !ok { + return errors.New("connection is not a TCP connection") + } + + oobData := []byte{w.oobByte} + var flags int + if w.disOOB { + flags = syscall.MSG_OOB | syscall.MSG_DONTROUTE // Additional flag for disOOB mode + } else { + flags = syscall.MSG_OOB + } + + // Use SyscallConn to access the underlying file descriptor safely + rawConn, err := tcpConn.SyscallConn() + if err != nil { + return err + } + + // Use Control to execute Sendto on the file descriptor + var sendErr error + err = rawConn.Control(func(fd uintptr) { + sendErr = syscall.Sendto(int(fd), oobData, flags, nil) + }) + if err != nil { + return err + } + return sendErr +} + +// oobDialer is a dialer that applies the OOB and disOOB strategies. +type oobDialer struct { + dialer transport.StreamDialer + oobPosition int64 + oobByte byte + disOOB bool +} + +// NewStreamDialerWithOOB creates a [transport.StreamDialer] that applies OOB byte sending at "oobPosition" and supports disOOB. +// "oobByte" specifies the value of the byte to send out-of-band. +func NewStreamDialerWithOOB(dialer transport.StreamDialer, oobPosition int64, oobByte byte, disOOB bool) (transport.StreamDialer, error) { + if dialer == nil { + return nil, errors.New("argument dialer must not be nil") + } + return &oobDialer{dialer: dialer, oobPosition: oobPosition, oobByte: oobByte, disOOB: disOOB}, nil +} + +// DialStream implements [transport.StreamDialer].DialStream with OOB and disOOB support. +func (d *oobDialer) DialStream(ctx context.Context, remoteAddr string) (transport.StreamConn, error) { + innerConn, err := d.dialer.DialStream(ctx, remoteAddr) + if err != nil { + return nil, err + } + // Wrap connection with OOB and/or disOOB writer based on configuration + return transport.WrapConn(innerConn, innerConn, NewOOBWriter(innerConn, d.oobPosition, d.oobByte, d.disOOB)), nil +} From 75fb6631635ecb820ed1c36d27a4b1211da04208 Mon Sep 17 00:00:00 2001 From: Sirius Ivliev Date: Sat, 2 Nov 2024 13:24:16 +0100 Subject: [PATCH 02/20] OOB and disOOB implementation --- transport/oob/oob.go | 7 ++++++- x/configurl/module.go | 2 ++ x/go.mod | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/transport/oob/oob.go b/transport/oob/oob.go index af0733f7..e231b924 100644 --- a/transport/oob/oob.go +++ b/transport/oob/oob.go @@ -3,6 +3,7 @@ package oob import ( "context" "errors" + "fmt" "io" "net" "syscall" @@ -57,11 +58,12 @@ func (w *oobWriter) Write(data []byte) (int, error) { } // Send the specified OOB byte using the new sendOOBByte method + _ = conn err = w.sendOOBByte(conn) if err != nil { return written, err } - data = data[written+1:] // Skip the OOB byte + data = data[written:] // Skip the OOB byte } } // Write the remaining data @@ -97,6 +99,9 @@ func (w *oobWriter) sendOOBByte(conn net.Conn) error { var sendErr error err = rawConn.Control(func(fd uintptr) { sendErr = syscall.Sendto(int(fd), oobData, flags, nil) + if sendErr != nil { + fmt.Errorf("failed to send OOB byte: %v", sendErr) + } }) if err != nil { return err diff --git a/x/configurl/module.go b/x/configurl/module.go index 83e14b89..9ba66d8d 100644 --- a/x/configurl/module.go +++ b/x/configurl/module.go @@ -65,6 +65,8 @@ func RegisterDefaultProviders(c *ProviderContainer) *ProviderContainer { registerWebsocketStreamDialer(&c.StreamDialers, "ws", c.StreamDialers.NewInstance) registerWebsocketPacketDialer(&c.PacketDialers, "ws", c.StreamDialers.NewInstance) + registerOOBStreamDialer(&c.StreamDialers, "oob", c.StreamDialers.NewInstance) + return c } diff --git a/x/go.mod b/x/go.mod index ef66a9d5..a55b23e9 100644 --- a/x/go.mod +++ b/x/go.mod @@ -81,3 +81,5 @@ require ( google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/Jigsaw-Code/outline-sdk => /Users/sirius/repos/outline-sdk/ From a50b0147752c1faab5ce236db93f7ca29180c4d8 Mon Sep 17 00:00:00 2001 From: Sirius Ivliev Date: Sat, 2 Nov 2024 13:31:21 +0100 Subject: [PATCH 03/20] OOB and disOOB implementation --- x/configurl/oob.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 x/configurl/oob.go diff --git a/x/configurl/oob.go b/x/configurl/oob.go new file mode 100644 index 00000000..3aac3a1b --- /dev/null +++ b/x/configurl/oob.go @@ -0,0 +1,42 @@ +package configurl + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/Jigsaw-Code/outline-sdk/transport" + "github.com/Jigsaw-Code/outline-sdk/transport/oob" +) + +func registerOOBStreamDialer(r TypeRegistry[transport.StreamDialer], typeID string, newSD BuildFunc[transport.StreamDialer]) { + r.RegisterType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { + sd, err := newSD(ctx, config.BaseConfig) + if err != nil { + return nil, err + } + params := config.URL.Opaque + + splitStr := strings.Split(params, ":") + if len(splitStr) != 3 { + return nil, fmt.Errorf("split config should be in oob::: format") + } + + position, err := strconv.Atoi(splitStr[0]) + if err != nil { + return nil, fmt.Errorf("position is not a number: %v. Split config should be in oob::: format", splitStr[0]) + } + + if len(splitStr[1]) != 1 { + return nil, fmt.Errorf("char should be a single character: %v. Split config should be in oob::: format", splitStr[1]) + } + char := splitStr[1][0] + + disOOB, err := strconv.ParseBool(splitStr[2]) + if err != nil { + return nil, fmt.Errorf("disOOB is not a boolean: %v. Split config should be in oob::: format", splitStr[2]) + } + return oob.NewStreamDialerWithOOB(sd, int64(position), char, disOOB) + }) +} From aaae1e51897b39c8d1e0a90c13594a856e1f99c7 Mon Sep 17 00:00:00 2001 From: Sirius Ivliev Date: Sat, 2 Nov 2024 16:51:36 +0100 Subject: [PATCH 04/20] OOB and disOOB implementation --- transport/oob/oob.go | 112 ++++++++++++++----------------------------- 1 file changed, 36 insertions(+), 76 deletions(-) diff --git a/transport/oob/oob.go b/transport/oob/oob.go index e231b924..2a572959 100644 --- a/transport/oob/oob.go +++ b/transport/oob/oob.go @@ -1,19 +1,19 @@ package oob import ( - "context" - "errors" - "fmt" "io" "net" + "sync" "syscall" - - "github.com/Jigsaw-Code/outline-sdk/transport" ) +var defaultTTL = 64 + type oobWriter struct { - writer io.Writer + conn *net.TCPConn + resetTTL sync.Once oobPosition int64 + fd int oobByte byte // Byte to send as OOB disOOB bool // Flag to enable disOOB mode } @@ -30,12 +30,8 @@ var _ io.ReaderFrom = (*oobWriterReaderFrom)(nil) // NewOOBWriter creates an [io.Writer] that sends an OOB byte at the specified "oobPosition". // If disOOB is enabled, it will apply the --disOOB strategy. // "oobByte" specifies the value of the byte to send out-of-band. -func NewOOBWriter(writer io.Writer, oobPosition int64, oobByte byte, disOOB bool) io.Writer { - ow := &oobWriter{writer: writer, oobPosition: oobPosition, oobByte: oobByte, disOOB: disOOB} - if rf, ok := writer.(io.ReaderFrom); ok { - return &oobWriterReaderFrom{ow, rf} - } - return ow +func NewOOBWriter(conn *net.TCPConn, fd int, oobPosition int64, oobByte byte, disOOB bool) io.Writer { + return &oobWriter{conn: conn, fd: fd, oobPosition: oobPosition, oobByte: oobByte, disOOB: disOOB} } func (w *oobWriterReaderFrom) ReadFrom(source io.Reader) (int64, error) { @@ -49,48 +45,42 @@ func (w *oobWriter) Write(data []byte) (int, error) { var written int var err error - if conn, ok := w.writer.(net.Conn); ok { - if w.oobPosition > 0 && w.oobPosition < int64(len(data)) { - // Write the first part with the regular writer - written, err = w.writer.Write(data[:w.oobPosition]) - if err != nil { - return written, err - } + if w.oobPosition > 0 && w.oobPosition < int64(len(data)) { + firstPart := data[:w.oobPosition+1] + secondPart := data[w.oobPosition:] + + // Split the data into two parts + tmp := secondPart[0] + secondPart[0] = w.oobByte + + err = w.send(firstPart, syscall.MSG_OOB) + if err != nil { + return written, err + } + written = int(w.oobPosition) + secondPart[0] = tmp - // Send the specified OOB byte using the new sendOOBByte method - _ = conn - err = w.sendOOBByte(conn) - if err != nil { - return written, err + w.resetTTL.Do(func() { + if w.disOOB { + err = syscall.SetsockoptInt(w.fd, syscall.IPPROTO_IP, syscall.IP_TTL, defaultTTL) } - data = data[written:] // Skip the OOB byte + }) + + if err != nil { + return written, err } + data = secondPart } + // Write the remaining data - n, err := w.writer.Write(data) - written += n + err = w.send(data, 0) + written += len(data) return written, err } -// sendOOBByte sends the specified OOB byte over the provided connection. -// It sets the appropriate flags based on whether disOOB mode is enabled. -func (w *oobWriter) sendOOBByte(conn net.Conn) error { - // Attempt to convert to *net.TCPConn to access SyscallConn - tcpConn, ok := conn.(*net.TCPConn) - if !ok { - return errors.New("connection is not a TCP connection") - } - - oobData := []byte{w.oobByte} - var flags int - if w.disOOB { - flags = syscall.MSG_OOB | syscall.MSG_DONTROUTE // Additional flag for disOOB mode - } else { - flags = syscall.MSG_OOB - } - +func (w *oobWriter) send(data []byte, flags int) error { // Use SyscallConn to access the underlying file descriptor safely - rawConn, err := tcpConn.SyscallConn() + rawConn, err := w.conn.SyscallConn() if err != nil { return err } @@ -98,40 +88,10 @@ func (w *oobWriter) sendOOBByte(conn net.Conn) error { // Use Control to execute Sendto on the file descriptor var sendErr error err = rawConn.Control(func(fd uintptr) { - sendErr = syscall.Sendto(int(fd), oobData, flags, nil) - if sendErr != nil { - fmt.Errorf("failed to send OOB byte: %v", sendErr) - } + sendErr = syscall.Sendto(int(w.fd), data, flags, nil) }) if err != nil { return err } return sendErr } - -// oobDialer is a dialer that applies the OOB and disOOB strategies. -type oobDialer struct { - dialer transport.StreamDialer - oobPosition int64 - oobByte byte - disOOB bool -} - -// NewStreamDialerWithOOB creates a [transport.StreamDialer] that applies OOB byte sending at "oobPosition" and supports disOOB. -// "oobByte" specifies the value of the byte to send out-of-band. -func NewStreamDialerWithOOB(dialer transport.StreamDialer, oobPosition int64, oobByte byte, disOOB bool) (transport.StreamDialer, error) { - if dialer == nil { - return nil, errors.New("argument dialer must not be nil") - } - return &oobDialer{dialer: dialer, oobPosition: oobPosition, oobByte: oobByte, disOOB: disOOB}, nil -} - -// DialStream implements [transport.StreamDialer].DialStream with OOB and disOOB support. -func (d *oobDialer) DialStream(ctx context.Context, remoteAddr string) (transport.StreamConn, error) { - innerConn, err := d.dialer.DialStream(ctx, remoteAddr) - if err != nil { - return nil, err - } - // Wrap connection with OOB and/or disOOB writer based on configuration - return transport.WrapConn(innerConn, innerConn, NewOOBWriter(innerConn, d.oobPosition, d.oobByte, d.disOOB)), nil -} From a25de7a4e4c85a2d219ff71350686913189b85ce Mon Sep 17 00:00:00 2001 From: Sirius Ivliev Date: Sat, 2 Nov 2024 17:11:11 +0100 Subject: [PATCH 05/20] OOB and disOOB implementation --- transport/oob/oob.go | 97 -------------------------------------------- x/configurl/oob.go | 2 +- 2 files changed, 1 insertion(+), 98 deletions(-) delete mode 100644 transport/oob/oob.go diff --git a/transport/oob/oob.go b/transport/oob/oob.go deleted file mode 100644 index 2a572959..00000000 --- a/transport/oob/oob.go +++ /dev/null @@ -1,97 +0,0 @@ -package oob - -import ( - "io" - "net" - "sync" - "syscall" -) - -var defaultTTL = 64 - -type oobWriter struct { - conn *net.TCPConn - resetTTL sync.Once - oobPosition int64 - fd int - oobByte byte // Byte to send as OOB - disOOB bool // Flag to enable disOOB mode -} - -var _ io.Writer = (*oobWriter)(nil) - -type oobWriterReaderFrom struct { - *oobWriter - rf io.ReaderFrom -} - -var _ io.ReaderFrom = (*oobWriterReaderFrom)(nil) - -// NewOOBWriter creates an [io.Writer] that sends an OOB byte at the specified "oobPosition". -// If disOOB is enabled, it will apply the --disOOB strategy. -// "oobByte" specifies the value of the byte to send out-of-band. -func NewOOBWriter(conn *net.TCPConn, fd int, oobPosition int64, oobByte byte, disOOB bool) io.Writer { - return &oobWriter{conn: conn, fd: fd, oobPosition: oobPosition, oobByte: oobByte, disOOB: disOOB} -} - -func (w *oobWriterReaderFrom) ReadFrom(source io.Reader) (int64, error) { - reader := io.MultiReader(io.LimitReader(source, w.oobPosition), source) - written, err := w.rf.ReadFrom(reader) - w.oobPosition -= written - return written, err -} - -func (w *oobWriter) Write(data []byte) (int, error) { - var written int - var err error - - if w.oobPosition > 0 && w.oobPosition < int64(len(data)) { - firstPart := data[:w.oobPosition+1] - secondPart := data[w.oobPosition:] - - // Split the data into two parts - tmp := secondPart[0] - secondPart[0] = w.oobByte - - err = w.send(firstPart, syscall.MSG_OOB) - if err != nil { - return written, err - } - written = int(w.oobPosition) - secondPart[0] = tmp - - w.resetTTL.Do(func() { - if w.disOOB { - err = syscall.SetsockoptInt(w.fd, syscall.IPPROTO_IP, syscall.IP_TTL, defaultTTL) - } - }) - - if err != nil { - return written, err - } - data = secondPart - } - - // Write the remaining data - err = w.send(data, 0) - written += len(data) - return written, err -} - -func (w *oobWriter) send(data []byte, flags int) error { - // Use SyscallConn to access the underlying file descriptor safely - rawConn, err := w.conn.SyscallConn() - if err != nil { - return err - } - - // Use Control to execute Sendto on the file descriptor - var sendErr error - err = rawConn.Control(func(fd uintptr) { - sendErr = syscall.Sendto(int(w.fd), data, flags, nil) - }) - if err != nil { - return err - } - return sendErr -} diff --git a/x/configurl/oob.go b/x/configurl/oob.go index 3aac3a1b..e381b131 100644 --- a/x/configurl/oob.go +++ b/x/configurl/oob.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/Jigsaw-Code/outline-sdk/transport" - "github.com/Jigsaw-Code/outline-sdk/transport/oob" + "github.com/Jigsaw-Code/outline-sdk/x/oob" ) func registerOOBStreamDialer(r TypeRegistry[transport.StreamDialer], typeID string, newSD BuildFunc[transport.StreamDialer]) { From 250a731ed21c04af7fe428fe206c7aa2eb862d33 Mon Sep 17 00:00:00 2001 From: Sirius Ivliev Date: Sat, 2 Nov 2024 17:16:45 +0100 Subject: [PATCH 06/20] OOB and disOOB implementation --- x/oob/dialer.go | 63 ++++++++++++++++++++++++++++++++ x/oob/oob.go | 97 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 x/oob/dialer.go create mode 100644 x/oob/oob.go diff --git a/x/oob/dialer.go b/x/oob/dialer.go new file mode 100644 index 00000000..fef5c69f --- /dev/null +++ b/x/oob/dialer.go @@ -0,0 +1,63 @@ +package oob + +import ( + "context" + "errors" + "fmt" + "github.com/Jigsaw-Code/outline-sdk/transport" + "net" + "syscall" +) + +// oobDialer is a dialer that applies the OOB and disOOB strategies. +type oobDialer struct { + dialer transport.StreamDialer + oobByte byte + oobPosition int64 + disOOB bool +} + +// NewStreamDialerWithOOB creates a [transport.StreamDialer] that applies OOB byte sending at "oobPosition" and supports disOOB. +// "oobByte" specifies the value of the byte to send out-of-band. +func NewStreamDialerWithOOB(dialer transport.StreamDialer, oobPosition int64, oobByte byte, disOOB bool) (transport.StreamDialer, error) { + if dialer == nil { + return nil, errors.New("argument dialer must not be nil") + } + return &oobDialer{dialer: dialer, oobPosition: oobPosition, oobByte: oobByte, disOOB: disOOB}, nil +} + +// DialStream implements [transport.StreamDialer].DialStream with OOB and disOOB support. +func (d *oobDialer) DialStream(ctx context.Context, remoteAddr string) (transport.StreamConn, error) { + innerConn, err := d.dialer.DialStream(ctx, remoteAddr) + if err != nil { + return nil, err + } + // this strategy only works when we set TCP as a strategy + tcpConn, ok := innerConn.(*net.TCPConn) + if !ok { + return nil, fmt.Errorf("split strategy only works with direct TCP connections") + } + + file, err := tcpConn.File() + if err != nil { + return nil, fmt.Errorf("split strategy was unable to get conn fd: %w", err) + } + + fd := int(file.Fd()) + + if d.disOOB { + err = tcpConn.SetNoDelay(true) + if err != nil { + return nil, fmt.Errorf("setting tcp NO_DELAY failed: %w", err) + } + + err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_TTL, 1) + if err != nil { + return nil, fmt.Errorf("setsockopt IPPROTO_IP/IP_TTL error: %w", err) + } + } + + dw := NewOOBWriter(tcpConn, fd, d.oobPosition, d.oobByte, d.disOOB) + + return transport.WrapConn(innerConn, innerConn, dw), nil +} diff --git a/x/oob/oob.go b/x/oob/oob.go new file mode 100644 index 00000000..2a572959 --- /dev/null +++ b/x/oob/oob.go @@ -0,0 +1,97 @@ +package oob + +import ( + "io" + "net" + "sync" + "syscall" +) + +var defaultTTL = 64 + +type oobWriter struct { + conn *net.TCPConn + resetTTL sync.Once + oobPosition int64 + fd int + oobByte byte // Byte to send as OOB + disOOB bool // Flag to enable disOOB mode +} + +var _ io.Writer = (*oobWriter)(nil) + +type oobWriterReaderFrom struct { + *oobWriter + rf io.ReaderFrom +} + +var _ io.ReaderFrom = (*oobWriterReaderFrom)(nil) + +// NewOOBWriter creates an [io.Writer] that sends an OOB byte at the specified "oobPosition". +// If disOOB is enabled, it will apply the --disOOB strategy. +// "oobByte" specifies the value of the byte to send out-of-band. +func NewOOBWriter(conn *net.TCPConn, fd int, oobPosition int64, oobByte byte, disOOB bool) io.Writer { + return &oobWriter{conn: conn, fd: fd, oobPosition: oobPosition, oobByte: oobByte, disOOB: disOOB} +} + +func (w *oobWriterReaderFrom) ReadFrom(source io.Reader) (int64, error) { + reader := io.MultiReader(io.LimitReader(source, w.oobPosition), source) + written, err := w.rf.ReadFrom(reader) + w.oobPosition -= written + return written, err +} + +func (w *oobWriter) Write(data []byte) (int, error) { + var written int + var err error + + if w.oobPosition > 0 && w.oobPosition < int64(len(data)) { + firstPart := data[:w.oobPosition+1] + secondPart := data[w.oobPosition:] + + // Split the data into two parts + tmp := secondPart[0] + secondPart[0] = w.oobByte + + err = w.send(firstPart, syscall.MSG_OOB) + if err != nil { + return written, err + } + written = int(w.oobPosition) + secondPart[0] = tmp + + w.resetTTL.Do(func() { + if w.disOOB { + err = syscall.SetsockoptInt(w.fd, syscall.IPPROTO_IP, syscall.IP_TTL, defaultTTL) + } + }) + + if err != nil { + return written, err + } + data = secondPart + } + + // Write the remaining data + err = w.send(data, 0) + written += len(data) + return written, err +} + +func (w *oobWriter) send(data []byte, flags int) error { + // Use SyscallConn to access the underlying file descriptor safely + rawConn, err := w.conn.SyscallConn() + if err != nil { + return err + } + + // Use Control to execute Sendto on the file descriptor + var sendErr error + err = rawConn.Control(func(fd uintptr) { + sendErr = syscall.Sendto(int(w.fd), data, flags, nil) + }) + if err != nil { + return err + } + return sendErr +} From ffb58dcff179c09de8265ad947a0de10aa57d4df Mon Sep 17 00:00:00 2001 From: Sirius Ivliev Date: Sat, 2 Nov 2024 17:25:15 +0100 Subject: [PATCH 07/20] OOB and disOOB implementation --- x/go.sum | 2 -- 1 file changed, 2 deletions(-) diff --git a/x/go.sum b/x/go.sum index 70bbc73a..9e0d46f9 100644 --- a/x/go.sum +++ b/x/go.sum @@ -6,8 +6,6 @@ github.com/AndreasBriese/bbloom v0.0.0-20170702084017-28f7e881ca57 h1:CVuXDbdzPW github.com/AndreasBriese/bbloom v0.0.0-20170702084017-28f7e881ca57/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/Jigsaw-Code/outline-sdk v0.0.17 h1:xkGsp3fs+5EbIZEeCEPI1rl0oyCrOLuO7YSK0gB75HE= -github.com/Jigsaw-Code/outline-sdk v0.0.17/go.mod h1:CFDKyGZA4zatKE4vMLe8TyQpZCyINOeRFbMAmYHxodw= github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e h1:NPfqIbzmijrl0VclX2t8eO5EPBhqe47LLGKpRrcVjXk= github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e/go.mod h1:ZdY5pBfat/WVzw3eXbIf7N1nZN0XD5H5+X8ZMDWbCs4= github.com/Psiphon-Labs/bolt v0.0.0-20200624191537-23cedaef7ad7 h1:Hx/NCZTnvoKZuIBwSmxE58KKoNLXIGG6hBJYN7pj9Ag= From 5cd75042376f06bc5fdd999ee14d5bf70f5b91e1 Mon Sep 17 00:00:00 2001 From: Sirius Ivliev Date: Sat, 2 Nov 2024 17:37:16 +0100 Subject: [PATCH 08/20] OOB and disOOB implementation --- x/oob/oob.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/oob/oob.go b/x/oob/oob.go index 2a572959..a2884469 100644 --- a/x/oob/oob.go +++ b/x/oob/oob.go @@ -62,7 +62,7 @@ func (w *oobWriter) Write(data []byte) (int, error) { w.resetTTL.Do(func() { if w.disOOB { - err = syscall.SetsockoptInt(w.fd, syscall.IPPROTO_IP, syscall.IP_TTL, defaultTTL) + err = syscall.SetsockoptInt(int(w.fd), syscall.IPPROTO_IP, syscall.IP_TTL, defaultTTL) } }) From a0e3a8c129faa6ddfde41aa6505a8c820b354b16 Mon Sep 17 00:00:00 2001 From: Sirius Ivliev Date: Sat, 2 Nov 2024 17:38:23 +0100 Subject: [PATCH 09/20] OOB and disOOB implementation --- x/oob/oob.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/oob/oob.go b/x/oob/oob.go index a2884469..66129072 100644 --- a/x/oob/oob.go +++ b/x/oob/oob.go @@ -88,7 +88,7 @@ func (w *oobWriter) send(data []byte, flags int) error { // Use Control to execute Sendto on the file descriptor var sendErr error err = rawConn.Control(func(fd uintptr) { - sendErr = syscall.Sendto(int(w.fd), data, flags, nil) + sendErr = syscall.Sendto(int(fd), data, flags, nil) }) if err != nil { return err From b65ad95aafd7fa436ac0fad9642eb419e398046c Mon Sep 17 00:00:00 2001 From: Sirius Ivliev Date: Sat, 2 Nov 2024 18:01:47 +0100 Subject: [PATCH 10/20] OOB and disOOB implementation --- x/oob/dialer.go | 3 ++- x/oob/oob.go | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/x/oob/dialer.go b/x/oob/dialer.go index fef5c69f..8a079bfb 100644 --- a/x/oob/dialer.go +++ b/x/oob/dialer.go @@ -4,9 +4,10 @@ import ( "context" "errors" "fmt" - "github.com/Jigsaw-Code/outline-sdk/transport" "net" "syscall" + + "github.com/Jigsaw-Code/outline-sdk/transport" ) // oobDialer is a dialer that applies the OOB and disOOB strategies. diff --git a/x/oob/oob.go b/x/oob/oob.go index 66129072..bbd76656 100644 --- a/x/oob/oob.go +++ b/x/oob/oob.go @@ -5,6 +5,7 @@ import ( "net" "sync" "syscall" + "time" ) var defaultTTL = 64 @@ -60,6 +61,8 @@ func (w *oobWriter) Write(data []byte) (int, error) { written = int(w.oobPosition) secondPart[0] = tmp + time.Sleep(200 * time.Millisecond) + w.resetTTL.Do(func() { if w.disOOB { err = syscall.SetsockoptInt(int(w.fd), syscall.IPPROTO_IP, syscall.IP_TTL, defaultTTL) From 9e0d954c888800e5bd36864bf39f6c942f30329c Mon Sep 17 00:00:00 2001 From: Sirius Ivliev Date: Sat, 2 Nov 2024 20:47:40 +0100 Subject: [PATCH 11/20] OOB and disOOB implementation --- x/oob/dialer.go | 10 ++++------ x/oob/oob.go | 22 +++++++++++++--------- x/oob/unix_ops.go | 26 ++++++++++++++++++++++++++ x/oob/windows_ops.go | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 15 deletions(-) create mode 100644 x/oob/unix_ops.go create mode 100644 x/oob/windows_ops.go diff --git a/x/oob/dialer.go b/x/oob/dialer.go index 8a079bfb..bca0fc07 100644 --- a/x/oob/dialer.go +++ b/x/oob/dialer.go @@ -36,23 +36,21 @@ func (d *oobDialer) DialStream(ctx context.Context, remoteAddr string) (transpor // this strategy only works when we set TCP as a strategy tcpConn, ok := innerConn.(*net.TCPConn) if !ok { - return nil, fmt.Errorf("split strategy only works with direct TCP connections") + return nil, fmt.Errorf("oob strategy only works with direct TCP connections") } - file, err := tcpConn.File() + fd, err := getSocketDescriptor(tcpConn) if err != nil { - return nil, fmt.Errorf("split strategy was unable to get conn fd: %w", err) + return nil, fmt.Errorf("oob strategy was unable to get conn fd: %w", err) } - fd := int(file.Fd()) - if d.disOOB { err = tcpConn.SetNoDelay(true) if err != nil { return nil, fmt.Errorf("setting tcp NO_DELAY failed: %w", err) } - err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_TTL, 1) + err = setsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_TTL, 1) if err != nil { return nil, fmt.Errorf("setsockopt IPPROTO_IP/IP_TTL error: %w", err) } diff --git a/x/oob/oob.go b/x/oob/oob.go index bbd76656..3620485a 100644 --- a/x/oob/oob.go +++ b/x/oob/oob.go @@ -1,6 +1,7 @@ package oob import ( + "fmt" "io" "net" "sync" @@ -14,7 +15,7 @@ type oobWriter struct { conn *net.TCPConn resetTTL sync.Once oobPosition int64 - fd int + fd SocketDescriptor oobByte byte // Byte to send as OOB disOOB bool // Flag to enable disOOB mode } @@ -31,7 +32,7 @@ var _ io.ReaderFrom = (*oobWriterReaderFrom)(nil) // NewOOBWriter creates an [io.Writer] that sends an OOB byte at the specified "oobPosition". // If disOOB is enabled, it will apply the --disOOB strategy. // "oobByte" specifies the value of the byte to send out-of-band. -func NewOOBWriter(conn *net.TCPConn, fd int, oobPosition int64, oobByte byte, disOOB bool) io.Writer { +func NewOOBWriter(conn *net.TCPConn, fd SocketDescriptor, oobPosition int64, oobByte byte, disOOB bool) io.Writer { return &oobWriter{conn: conn, fd: fd, oobPosition: oobPosition, oobByte: oobByte, disOOB: disOOB} } @@ -54,7 +55,7 @@ func (w *oobWriter) Write(data []byte) (int, error) { tmp := secondPart[0] secondPart[0] = w.oobByte - err = w.send(firstPart, syscall.MSG_OOB) + err = w.send(firstPart, 0x01) if err != nil { return written, err } @@ -65,12 +66,12 @@ func (w *oobWriter) Write(data []byte) (int, error) { w.resetTTL.Do(func() { if w.disOOB { - err = syscall.SetsockoptInt(int(w.fd), syscall.IPPROTO_IP, syscall.IP_TTL, defaultTTL) + err = setsockoptInt(w.fd, syscall.IPPROTO_IP, syscall.IP_TTL, defaultTTL) } }) if err != nil { - return written, err + return written, fmt.Errorf("setsockopt IPPROTO_IP/IP_TTL error: %w", err) } data = secondPart } @@ -85,16 +86,19 @@ func (w *oobWriter) send(data []byte, flags int) error { // Use SyscallConn to access the underlying file descriptor safely rawConn, err := w.conn.SyscallConn() if err != nil { - return err + return fmt.Errorf("oob strategy was unable to get raw conn: %w", err) } // Use Control to execute Sendto on the file descriptor var sendErr error err = rawConn.Control(func(fd uintptr) { - sendErr = syscall.Sendto(int(fd), data, flags, nil) + sendErr = sendTo(SocketDescriptor(fd), data, flags) }) if err != nil { - return err + return fmt.Errorf("oob strategy was unable to control socket: %w", err) + } + if sendErr != nil { + return fmt.Errorf("oob strategy was unable to send data: %w", sendErr) } - return sendErr + return nil } diff --git a/x/oob/unix_ops.go b/x/oob/unix_ops.go new file mode 100644 index 00000000..4b48e74d --- /dev/null +++ b/x/oob/unix_ops.go @@ -0,0 +1,26 @@ +//go:build linux || darwin + +package oob + +import ( + "net" + "syscall" +) + +type SocketDescriptor int + +func setsockoptInt(fd SocketDescriptor, level, opt int, value int) error { + return syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_TTL, defaultTTL) +} + +func sendTo(fd SocketDescriptor, data []byte, flags int) (err error) { + return syscall.Sendto(int(fd), data, flags, nil) +} + +func getSocketDescriptor(conn *net.TCPConn) (SocketDescriptor, error) { + file, err := conn.File() + if err != nil { + return 0, err + } + return SocketDescriptor(file.Fd()), nil +} diff --git a/x/oob/windows_ops.go b/x/oob/windows_ops.go new file mode 100644 index 00000000..47300bb6 --- /dev/null +++ b/x/oob/windows_ops.go @@ -0,0 +1,39 @@ +//go:build windows + +package oob + +import ( + "fmt" + "net" + "syscall" +) + +type SocketDescriptor uintptr + +func setsockoptInt(fd SocketDescriptor, level, opt int, value int) error { + return syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IP, syscall.IP_TTL, defaultTTL) +} + +func sendTo(fd SocketDescriptor, data []byte, flags int) (err error) { + var wsaBuf [1]syscall.WSABuf + wsaBuf[0].Len = uint32(len(data)) + wsaBuf[0].Buf = &data[0] + + bytesSent := uint32(0) + + return syscall.WSASend(syscall.Handle(fd), &wsaBuf[0], 1, &bytesSent, uint32(flags), nil, nil) +} + +func getSocketDescriptor(conn *net.TCPConn) (SocketDescriptor, error) { + rawConn, err := conn.SyscallConn() + if err != nil { + return SocketDescriptor(0), fmt.Errorf("oob strategy was unable to get raw conn: %w", err) + } + + var sysFd syscall.Handle + err = rawConn.Control(func(fd uintptr) { + sysFd = syscall.Handle(fd) + }) + + return SocketDescriptor(sysFd), err +} From d16f104434f87684e385bb469b55246a00564995 Mon Sep 17 00:00:00 2001 From: Sirius Ivliev Date: Sun, 3 Nov 2024 09:43:41 +0100 Subject: [PATCH 12/20] OOB and disOOB implementation --- x/oob/unix_ops.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x/oob/unix_ops.go b/x/oob/unix_ops.go index 4b48e74d..48735d1b 100644 --- a/x/oob/unix_ops.go +++ b/x/oob/unix_ops.go @@ -3,6 +3,7 @@ package oob import ( + "fmt" "net" "syscall" ) @@ -14,6 +15,7 @@ func setsockoptInt(fd SocketDescriptor, level, opt int, value int) error { } func sendTo(fd SocketDescriptor, data []byte, flags int) (err error) { + fmt.Printf("sendTo: %d, %v, %d\n", fd, data, flags) return syscall.Sendto(int(fd), data, flags, nil) } From 84b696d215da2625de0220bba465e652285c613e Mon Sep 17 00:00:00 2001 From: Sirius Ivliev Date: Sun, 3 Nov 2024 12:55:09 +0100 Subject: [PATCH 13/20] OOB and disOOB implementation --- x/configurl/oob.go | 18 ++++++++++++------ x/oob/dialer.go | 36 ++++++++++++++++-------------------- x/oob/ttl.go | 37 +++++++++++++++++++++++++++++++++++++ x/oob/unix_ops.go | 6 ------ x/oob/windows_ops.go | 2 +- x/oob/{oob.go => writer.go} | 29 +++++++++++++++++++++-------- 6 files changed, 87 insertions(+), 41 deletions(-) create mode 100644 x/oob/ttl.go rename x/oob/{oob.go => writer.go} (76%) diff --git a/x/configurl/oob.go b/x/configurl/oob.go index e381b131..70cb3a23 100644 --- a/x/configurl/oob.go +++ b/x/configurl/oob.go @@ -5,6 +5,7 @@ import ( "fmt" "strconv" "strings" + "time" "github.com/Jigsaw-Code/outline-sdk/transport" "github.com/Jigsaw-Code/outline-sdk/x/oob" @@ -19,24 +20,29 @@ func registerOOBStreamDialer(r TypeRegistry[transport.StreamDialer], typeID stri params := config.URL.Opaque splitStr := strings.Split(params, ":") - if len(splitStr) != 3 { - return nil, fmt.Errorf("split config should be in oob::: format") + if len(splitStr) != 4 { + return nil, fmt.Errorf("oob: config should be in oob:::: format") } position, err := strconv.Atoi(splitStr[0]) if err != nil { - return nil, fmt.Errorf("position is not a number: %v. Split config should be in oob::: format", splitStr[0]) + return nil, fmt.Errorf("oob: oob position is not a number: %v", splitStr[0]) } if len(splitStr[1]) != 1 { - return nil, fmt.Errorf("char should be a single character: %v. Split config should be in oob::: format", splitStr[1]) + return nil, fmt.Errorf("oob: oob byte should be a single character: %v", splitStr[1]) } char := splitStr[1][0] disOOB, err := strconv.ParseBool(splitStr[2]) if err != nil { - return nil, fmt.Errorf("disOOB is not a boolean: %v. Split config should be in oob::: format", splitStr[2]) + return nil, fmt.Errorf("oob: disOOB is not a boolean: %v", splitStr[2]) } - return oob.NewStreamDialerWithOOB(sd, int64(position), char, disOOB) + + delay, err := time.ParseDuration(splitStr[3]) + if err != nil { + return nil, fmt.Errorf("oob: delay is not a duration: %v", splitStr[3]) + } + return oob.NewStreamDialer(sd, int64(position), char, disOOB, delay) }) } diff --git a/x/oob/dialer.go b/x/oob/dialer.go index bca0fc07..68b40286 100644 --- a/x/oob/dialer.go +++ b/x/oob/dialer.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" "net" - "syscall" + "time" "github.com/Jigsaw-Code/outline-sdk/transport" ) @@ -16,15 +16,23 @@ type oobDialer struct { oobByte byte oobPosition int64 disOOB bool + delay time.Duration } -// NewStreamDialerWithOOB creates a [transport.StreamDialer] that applies OOB byte sending at "oobPosition" and supports disOOB. +// NewStreamDialer creates a [transport.StreamDialer] that applies OOB byte sending at "oobPosition" and supports disOOB. // "oobByte" specifies the value of the byte to send out-of-band. -func NewStreamDialerWithOOB(dialer transport.StreamDialer, oobPosition int64, oobByte byte, disOOB bool) (transport.StreamDialer, error) { +func NewStreamDialer(dialer transport.StreamDialer, + oobPosition int64, oobByte byte, disOOB bool, delay time.Duration) (transport.StreamDialer, error) { if dialer == nil { return nil, errors.New("argument dialer must not be nil") } - return &oobDialer{dialer: dialer, oobPosition: oobPosition, oobByte: oobByte, disOOB: disOOB}, nil + return &oobDialer{ + dialer: dialer, + oobPosition: oobPosition, + oobByte: oobByte, + disOOB: disOOB, + delay: delay, + }, nil } // DialStream implements [transport.StreamDialer].DialStream with OOB and disOOB support. @@ -36,27 +44,15 @@ func (d *oobDialer) DialStream(ctx context.Context, remoteAddr string) (transpor // this strategy only works when we set TCP as a strategy tcpConn, ok := innerConn.(*net.TCPConn) if !ok { - return nil, fmt.Errorf("oob strategy only works with direct TCP connections") + return nil, fmt.Errorf("oob: only works with direct TCP connections") } - fd, err := getSocketDescriptor(tcpConn) + sd, err := getSocketDescriptor(tcpConn) if err != nil { - return nil, fmt.Errorf("oob strategy was unable to get conn fd: %w", err) + return nil, fmt.Errorf("oob: unable to get conn sd: %w", err) } - if d.disOOB { - err = tcpConn.SetNoDelay(true) - if err != nil { - return nil, fmt.Errorf("setting tcp NO_DELAY failed: %w", err) - } - - err = setsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_TTL, 1) - if err != nil { - return nil, fmt.Errorf("setsockopt IPPROTO_IP/IP_TTL error: %w", err) - } - } - - dw := NewOOBWriter(tcpConn, fd, d.oobPosition, d.oobByte, d.disOOB) + dw := NewWriter(tcpConn, sd, d.oobPosition, d.oobByte, d.disOOB, d.delay) return transport.WrapConn(innerConn, innerConn, dw), nil } diff --git a/x/oob/ttl.go b/x/oob/ttl.go new file mode 100644 index 00000000..46a1b5c1 --- /dev/null +++ b/x/oob/ttl.go @@ -0,0 +1,37 @@ +package oob + +import ( + "fmt" + "net" + "net/netip" + + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +func setTtl(conn *net.TCPConn, ttl int) (oldTTL int, err error) { + addr, err := netip.ParseAddrPort(conn.RemoteAddr().String()) + if err != nil { + return 0, err + } + + switch { + case addr.Addr().Is4(): + conn := ipv4.NewConn(conn) + oldTTL, _ = conn.TTL() + err = conn.SetTTL(ttl) + case addr.Addr().Is6(): + conn := ipv6.NewConn(conn) + oldTTL, _ = conn.HopLimit() + err = conn.SetHopLimit(ttl) + } + if err != nil { + return 0, fmt.Errorf("failed to change TTL: %w", err) + } + + if oldTTL == 0 { + oldTTL = defaultTTL + } + + return +} diff --git a/x/oob/unix_ops.go b/x/oob/unix_ops.go index 48735d1b..957a9df8 100644 --- a/x/oob/unix_ops.go +++ b/x/oob/unix_ops.go @@ -3,19 +3,13 @@ package oob import ( - "fmt" "net" "syscall" ) type SocketDescriptor int -func setsockoptInt(fd SocketDescriptor, level, opt int, value int) error { - return syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_TTL, defaultTTL) -} - func sendTo(fd SocketDescriptor, data []byte, flags int) (err error) { - fmt.Printf("sendTo: %d, %v, %d\n", fd, data, flags) return syscall.Sendto(int(fd), data, flags, nil) } diff --git a/x/oob/windows_ops.go b/x/oob/windows_ops.go index 47300bb6..59cb19c4 100644 --- a/x/oob/windows_ops.go +++ b/x/oob/windows_ops.go @@ -11,7 +11,7 @@ import ( type SocketDescriptor uintptr func setsockoptInt(fd SocketDescriptor, level, opt int, value int) error { - return syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IP, syscall.IP_TTL, defaultTTL) + return syscall.SetsockoptInt(syscall.Handle(fd), level, opt, value) } func sendTo(fd SocketDescriptor, data []byte, flags int) (err error) { diff --git a/x/oob/oob.go b/x/oob/writer.go similarity index 76% rename from x/oob/oob.go rename to x/oob/writer.go index 3620485a..4d1e4215 100644 --- a/x/oob/oob.go +++ b/x/oob/writer.go @@ -5,7 +5,6 @@ import ( "io" "net" "sync" - "syscall" "time" ) @@ -15,9 +14,10 @@ type oobWriter struct { conn *net.TCPConn resetTTL sync.Once oobPosition int64 - fd SocketDescriptor + sd SocketDescriptor oobByte byte // Byte to send as OOB disOOB bool // Flag to enable disOOB mode + delay time.Duration } var _ io.Writer = (*oobWriter)(nil) @@ -29,11 +29,18 @@ type oobWriterReaderFrom struct { var _ io.ReaderFrom = (*oobWriterReaderFrom)(nil) -// NewOOBWriter creates an [io.Writer] that sends an OOB byte at the specified "oobPosition". +// NewWriter creates an [io.Writer] that sends an OOB byte at the specified "oobPosition". // If disOOB is enabled, it will apply the --disOOB strategy. // "oobByte" specifies the value of the byte to send out-of-band. -func NewOOBWriter(conn *net.TCPConn, fd SocketDescriptor, oobPosition int64, oobByte byte, disOOB bool) io.Writer { - return &oobWriter{conn: conn, fd: fd, oobPosition: oobPosition, oobByte: oobByte, disOOB: disOOB} +func NewWriter( + conn *net.TCPConn, + sd SocketDescriptor, + oobPosition int64, + oobByte byte, + disOOB bool, + delay time.Duration, +) io.Writer { + return &oobWriter{conn: conn, sd: sd, oobPosition: oobPosition, oobByte: oobByte, disOOB: disOOB} } func (w *oobWriterReaderFrom) ReadFrom(source io.Reader) (int64, error) { @@ -55,6 +62,13 @@ func (w *oobWriter) Write(data []byte) (int, error) { tmp := secondPart[0] secondPart[0] = w.oobByte + var oldTTL int + if w.disOOB { + oldTTL, err = setTtl(w.conn, 1) + } + if err != nil { + return written, fmt.Errorf("oob: setsockopt IPPROTO_IP/IP_TTL error: %w", err) + } err = w.send(firstPart, 0x01) if err != nil { return written, err @@ -66,12 +80,11 @@ func (w *oobWriter) Write(data []byte) (int, error) { w.resetTTL.Do(func() { if w.disOOB { - err = setsockoptInt(w.fd, syscall.IPPROTO_IP, syscall.IP_TTL, defaultTTL) + _, err = setTtl(w.conn, oldTTL) } }) - if err != nil { - return written, fmt.Errorf("setsockopt IPPROTO_IP/IP_TTL error: %w", err) + return written, fmt.Errorf("oob: setsockopt IPPROTO_IP/IP_TTL error: %w", err) } data = secondPart } From 2e298a85e91ac3e718301e651c84f2a66316d8c9 Mon Sep 17 00:00:00 2001 From: Sirius Ivliev Date: Sun, 3 Nov 2024 13:01:03 +0100 Subject: [PATCH 14/20] OOB and disOOB implementation --- x/oob/writer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/oob/writer.go b/x/oob/writer.go index 4d1e4215..862feff3 100644 --- a/x/oob/writer.go +++ b/x/oob/writer.go @@ -76,13 +76,13 @@ func (w *oobWriter) Write(data []byte) (int, error) { written = int(w.oobPosition) secondPart[0] = tmp - time.Sleep(200 * time.Millisecond) - w.resetTTL.Do(func() { if w.disOOB { _, err = setTtl(w.conn, oldTTL) } }) + + time.Sleep(w.delay) if err != nil { return written, fmt.Errorf("oob: setsockopt IPPROTO_IP/IP_TTL error: %w", err) } From 5a3532d026fdad38fcd198d03903eeec0b74492e Mon Sep 17 00:00:00 2001 From: Sirius Ivliev Date: Sun, 3 Nov 2024 13:02:38 +0100 Subject: [PATCH 15/20] OOB and disOOB implementation --- x/oob/writer.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/x/oob/writer.go b/x/oob/writer.go index 862feff3..7d1e72a0 100644 --- a/x/oob/writer.go +++ b/x/oob/writer.go @@ -76,16 +76,16 @@ func (w *oobWriter) Write(data []byte) (int, error) { written = int(w.oobPosition) secondPart[0] = tmp - w.resetTTL.Do(func() { - if w.disOOB { + if w.disOOB { + w.resetTTL.Do(func() { _, err = setTtl(w.conn, oldTTL) + }) + if err != nil { + return written, fmt.Errorf("oob: setsockopt IPPROTO_IP/IP_TTL error: %w", err) } - }) + } time.Sleep(w.delay) - if err != nil { - return written, fmt.Errorf("oob: setsockopt IPPROTO_IP/IP_TTL error: %w", err) - } data = secondPart } From c8f3af76c85dce3611da55f3ff0eb91fcaa2e831 Mon Sep 17 00:00:00 2001 From: Sirius Ivliev Date: Sun, 3 Nov 2024 13:09:58 +0100 Subject: [PATCH 16/20] OOB and disOOB implementation --- x/oob/ttl.go | 2 ++ x/oob/writer.go | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x/oob/ttl.go b/x/oob/ttl.go index 46a1b5c1..cc77410d 100644 --- a/x/oob/ttl.go +++ b/x/oob/ttl.go @@ -9,6 +9,8 @@ import ( "golang.org/x/net/ipv6" ) +var defaultTTL = 64 + func setTtl(conn *net.TCPConn, ttl int) (oldTTL int, err error) { addr, err := netip.ParseAddrPort(conn.RemoteAddr().String()) if err != nil { diff --git a/x/oob/writer.go b/x/oob/writer.go index 7d1e72a0..2bd2d723 100644 --- a/x/oob/writer.go +++ b/x/oob/writer.go @@ -8,8 +8,6 @@ import ( "time" ) -var defaultTTL = 64 - type oobWriter struct { conn *net.TCPConn resetTTL sync.Once @@ -40,7 +38,7 @@ func NewWriter( disOOB bool, delay time.Duration, ) io.Writer { - return &oobWriter{conn: conn, sd: sd, oobPosition: oobPosition, oobByte: oobByte, disOOB: disOOB} + return &oobWriter{conn: conn, sd: sd, oobPosition: oobPosition, oobByte: oobByte, disOOB: disOOB, delay: delay} } func (w *oobWriterReaderFrom) ReadFrom(source io.Reader) (int64, error) { From 175eda6ce53fdc0f3c6f7251426f1b46d74ed795 Mon Sep 17 00:00:00 2001 From: Sirius Ivliev Date: Sun, 3 Nov 2024 13:11:27 +0100 Subject: [PATCH 17/20] OOB and disOOB implementation --- x/oob/writer.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/x/oob/writer.go b/x/oob/writer.go index 2bd2d723..78f9d782 100644 --- a/x/oob/writer.go +++ b/x/oob/writer.go @@ -11,6 +11,7 @@ import ( type oobWriter struct { conn *net.TCPConn resetTTL sync.Once + setTTL sync.Once oobPosition int64 sd SocketDescriptor oobByte byte // Byte to send as OOB @@ -62,11 +63,14 @@ func (w *oobWriter) Write(data []byte) (int, error) { var oldTTL int if w.disOOB { - oldTTL, err = setTtl(w.conn, 1) - } - if err != nil { - return written, fmt.Errorf("oob: setsockopt IPPROTO_IP/IP_TTL error: %w", err) + w.setTTL.Do(func() { + oldTTL, err = setTtl(w.conn, 1) + }) + if err != nil { + return written, fmt.Errorf("oob: setsockopt IPPROTO_IP/IP_TTL error: %w", err) + } } + err = w.send(firstPart, 0x01) if err != nil { return written, err From afde64861b8c309fb16cd7dd9472247163b4ff95 Mon Sep 17 00:00:00 2001 From: Sirius Ivliev Date: Wed, 13 Nov 2024 15:24:49 +0100 Subject: [PATCH 18/20] review fixes --- x/go.mod | 12 ++++++------ x/go.sum | 14 ++++++++++++-- x/oob/dialer.go | 8 +++++--- x/oob/ttl.go | 39 --------------------------------------- x/oob/unix_ops.go | 5 ++++- x/oob/windows_ops.go | 6 ++---- x/oob/writer.go | 42 ++++++++++++++++++++++-------------------- 7 files changed, 51 insertions(+), 75 deletions(-) delete mode 100644 x/oob/ttl.go diff --git a/x/go.mod b/x/go.mod index b7c10da7..f639df52 100644 --- a/x/go.mod +++ b/x/go.mod @@ -13,9 +13,9 @@ require ( github.com/stretchr/testify v1.9.0 github.com/vishvananda/netlink v1.1.0 golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b - golang.org/x/net v0.28.0 - golang.org/x/sys v0.23.0 - golang.org/x/term v0.23.0 + golang.org/x/net v0.31.0 + golang.org/x/sys v0.27.0 + golang.org/x/term v0.26.0 ) require ( @@ -72,11 +72,11 @@ require ( github.com/wader/filtertransport v0.0.0-20200316221534-bdd9e61eee78 // indirect gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0 // indirect go.uber.org/mock v0.4.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.29.0 // indirect golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/mod v0.17.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/text v0.20.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/x/go.sum b/x/go.sum index a7238e9a..096d7ae7 100644 --- a/x/go.sum +++ b/x/go.sum @@ -6,8 +6,6 @@ github.com/AndreasBriese/bbloom v0.0.0-20170702084017-28f7e881ca57 h1:CVuXDbdzPW github.com/AndreasBriese/bbloom v0.0.0-20170702084017-28f7e881ca57/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/Jigsaw-Code/outline-sdk v0.0.18-0.20241106233708-faffebb12629 h1:sHi1X4vwtNNBUDCbxynGXe7cM/inwTbavowHziaxlbk= -github.com/Jigsaw-Code/outline-sdk v0.0.18-0.20241106233708-faffebb12629/go.mod h1:CFDKyGZA4zatKE4vMLe8TyQpZCyINOeRFbMAmYHxodw= github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e h1:NPfqIbzmijrl0VclX2t8eO5EPBhqe47LLGKpRrcVjXk= github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e/go.mod h1:ZdY5pBfat/WVzw3eXbIf7N1nZN0XD5H5+X8ZMDWbCs4= github.com/Psiphon-Labs/bolt v0.0.0-20200624191537-23cedaef7ad7 h1:Hx/NCZTnvoKZuIBwSmxE58KKoNLXIGG6hBJYN7pj9Ag= @@ -207,6 +205,8 @@ golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b h1:WX7nnnLfCEXg+FmdYZPai2XuP3VqCP1HZVMST0n9DF0= @@ -226,12 +226,16 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -249,6 +253,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -258,6 +264,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -266,6 +274,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/x/oob/dialer.go b/x/oob/dialer.go index 68b40286..5960b9f6 100644 --- a/x/oob/dialer.go +++ b/x/oob/dialer.go @@ -8,11 +8,13 @@ import ( "time" "github.com/Jigsaw-Code/outline-sdk/transport" + "github.com/Jigsaw-Code/outline-sdk/x/sockopt" ) // oobDialer is a dialer that applies the OOB and disOOB strategies. type oobDialer struct { dialer transport.StreamDialer + opts sockopt.TCPOptions oobByte byte oobPosition int64 disOOB bool @@ -47,12 +49,12 @@ func (d *oobDialer) DialStream(ctx context.Context, remoteAddr string) (transpor return nil, fmt.Errorf("oob: only works with direct TCP connections") } - sd, err := getSocketDescriptor(tcpConn) + opts, err := sockopt.NewTCPOptions(tcpConn) if err != nil { - return nil, fmt.Errorf("oob: unable to get conn sd: %w", err) + return nil, fmt.Errorf("oob: unable to get TCP options: %w", err) } - dw := NewWriter(tcpConn, sd, d.oobPosition, d.oobByte, d.disOOB, d.delay) + dw := NewWriter(tcpConn, opts, d.oobPosition, d.oobByte, d.disOOB, d.delay) return transport.WrapConn(innerConn, innerConn, dw), nil } diff --git a/x/oob/ttl.go b/x/oob/ttl.go deleted file mode 100644 index cc77410d..00000000 --- a/x/oob/ttl.go +++ /dev/null @@ -1,39 +0,0 @@ -package oob - -import ( - "fmt" - "net" - "net/netip" - - "golang.org/x/net/ipv4" - "golang.org/x/net/ipv6" -) - -var defaultTTL = 64 - -func setTtl(conn *net.TCPConn, ttl int) (oldTTL int, err error) { - addr, err := netip.ParseAddrPort(conn.RemoteAddr().String()) - if err != nil { - return 0, err - } - - switch { - case addr.Addr().Is4(): - conn := ipv4.NewConn(conn) - oldTTL, _ = conn.TTL() - err = conn.SetTTL(ttl) - case addr.Addr().Is6(): - conn := ipv6.NewConn(conn) - oldTTL, _ = conn.HopLimit() - err = conn.SetHopLimit(ttl) - } - if err != nil { - return 0, fmt.Errorf("failed to change TTL: %w", err) - } - - if oldTTL == 0 { - oldTTL = defaultTTL - } - - return -} diff --git a/x/oob/unix_ops.go b/x/oob/unix_ops.go index 957a9df8..d3fcabff 100644 --- a/x/oob/unix_ops.go +++ b/x/oob/unix_ops.go @@ -3,14 +3,17 @@ package oob import ( + "golang.org/x/sys/unix" "net" "syscall" ) +const MSG_OOB = unix.MSG_OOB + type SocketDescriptor int func sendTo(fd SocketDescriptor, data []byte, flags int) (err error) { - return syscall.Sendto(int(fd), data, flags, nil) + return syscall.Sendmsg(int(fd), data, nil, nil, flags) } func getSocketDescriptor(conn *net.TCPConn) (SocketDescriptor, error) { diff --git a/x/oob/windows_ops.go b/x/oob/windows_ops.go index 59cb19c4..0a9eddfe 100644 --- a/x/oob/windows_ops.go +++ b/x/oob/windows_ops.go @@ -8,11 +8,9 @@ import ( "syscall" ) -type SocketDescriptor uintptr +const MSG_OOB = windows.MSG_OOB -func setsockoptInt(fd SocketDescriptor, level, opt int, value int) error { - return syscall.SetsockoptInt(syscall.Handle(fd), level, opt, value) -} +type SocketDescriptor uintptr func sendTo(fd SocketDescriptor, data []byte, flags int) (err error) { var wsaBuf [1]syscall.WSABuf diff --git a/x/oob/writer.go b/x/oob/writer.go index 78f9d782..ea340491 100644 --- a/x/oob/writer.go +++ b/x/oob/writer.go @@ -2,6 +2,7 @@ package oob import ( "fmt" + "github.com/Jigsaw-Code/outline-sdk/x/sockopt" "io" "net" "sync" @@ -10,6 +11,7 @@ import ( type oobWriter struct { conn *net.TCPConn + opts sockopt.TCPOptions resetTTL sync.Once setTTL sync.Once oobPosition int64 @@ -26,34 +28,26 @@ type oobWriterReaderFrom struct { rf io.ReaderFrom } -var _ io.ReaderFrom = (*oobWriterReaderFrom)(nil) - // NewWriter creates an [io.Writer] that sends an OOB byte at the specified "oobPosition". // If disOOB is enabled, it will apply the --disOOB strategy. // "oobByte" specifies the value of the byte to send out-of-band. func NewWriter( conn *net.TCPConn, - sd SocketDescriptor, + opts sockopt.TCPOptions, oobPosition int64, oobByte byte, disOOB bool, delay time.Duration, ) io.Writer { - return &oobWriter{conn: conn, sd: sd, oobPosition: oobPosition, oobByte: oobByte, disOOB: disOOB, delay: delay} -} - -func (w *oobWriterReaderFrom) ReadFrom(source io.Reader) (int64, error) { - reader := io.MultiReader(io.LimitReader(source, w.oobPosition), source) - written, err := w.rf.ReadFrom(reader) - w.oobPosition -= written - return written, err + return &oobWriter{conn: conn, opts: opts, oobPosition: oobPosition, oobByte: oobByte, disOOB: disOOB, delay: delay} } func (w *oobWriter) Write(data []byte) (int, error) { var written int var err error - if w.oobPosition > 0 && w.oobPosition < int64(len(data)) { + fmt.Println("oobWriter, total: ", len(data)) + if w.oobPosition > 0 && w.oobPosition < int64(len(data))-1 { firstPart := data[:w.oobPosition+1] secondPart := data[w.oobPosition:] @@ -64,36 +58,44 @@ func (w *oobWriter) Write(data []byte) (int, error) { var oldTTL int if w.disOOB { w.setTTL.Do(func() { - oldTTL, err = setTtl(w.conn, 1) + oldTTL, err = w.opts.HopLimit() + if err != nil { + return + } + err = w.opts.SetHopLimit(0) }) if err != nil { - return written, fmt.Errorf("oob: setsockopt IPPROTO_IP/IP_TTL error: %w", err) + return written, fmt.Errorf("oob: new hop limit set error: %w", err) } } - err = w.send(firstPart, 0x01) + err = w.send(firstPart, MSG_OOB) if err != nil { return written, err } written = int(w.oobPosition) secondPart[0] = tmp + fmt.Printf("oobWriter: firstPart len: %d\n", len(firstPart)) + if w.disOOB { w.resetTTL.Do(func() { - _, err = setTtl(w.conn, oldTTL) + err = w.opts.SetHopLimit(oldTTL) }) if err != nil { - return written, fmt.Errorf("oob: setsockopt IPPROTO_IP/IP_TTL error: %w", err) + return written, fmt.Errorf("oob: old hop limit set error: %w", err) } } - time.Sleep(w.delay) data = secondPart + + time.Sleep(w.delay) } // Write the remaining data - err = w.send(data, 0) - written += len(data) + n, err := w.conn.Write(data) + written += n + fmt.Printf("oobWriter: second part len: %d\n", len(data)) return written, err } From 82bceb53f170eb146b1ead584a6564b86a0291c0 Mon Sep 17 00:00:00 2001 From: Sirius Ivliev Date: Wed, 13 Nov 2024 16:12:41 +0100 Subject: [PATCH 19/20] add test --- x/oob/oob_test.go | 129 ++++++++++++++++++++++++++++++++++++++++++++++ x/oob/writer.go | 8 ++- 2 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 x/oob/oob_test.go diff --git a/x/oob/oob_test.go b/x/oob/oob_test.go new file mode 100644 index 00000000..5f02e875 --- /dev/null +++ b/x/oob/oob_test.go @@ -0,0 +1,129 @@ +package oob + +import ( + "bufio" + "context" + "net" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "github.com/Jigsaw-Code/outline-sdk/transport" +) + +const ( + msg = "Hello OOB!\n" + msgLen = len(msg) +) + +// OOBDialerTestSuite - test suite for testing oobDialer and oobWriter +type OOBDialerTestSuite struct { + suite.Suite + server net.Listener + dataChan chan []byte + serverAddr string +} + +// SetupSuite - runs once before all tests +func (suite *OOBDialerTestSuite) SetupSuite() { + // Start TCP server + listener, dataChan := startTestServer(suite.T()) + suite.server = listener + suite.dataChan = dataChan + suite.serverAddr = listener.Addr().String() +} + +// TearDownSuite - runs once after all tests +func (suite *OOBDialerTestSuite) TearDownSuite() { + suite.server.Close() + close(suite.dataChan) +} + +// startTestServer - starts a test server and returns listener and data channel + +func startTestServer(t *testing.T) (net.Listener, chan []byte) { + listener, err := net.Listen("tcp", "localhost:0") + require.NoError(t, err, "Failed to create server") + + dataChan := make(chan []byte, 10) + go func() { + for { + conn, err := listener.Accept() + if err != nil { + return + } + + go func(conn net.Conn) { + defer conn.Close() + + scanner := bufio.NewScanner(conn) + for scanner.Scan() { + line := scanner.Bytes() + dataChan <- append([]byte{}, line...) + break + } + + if err := scanner.Err(); err != nil { + t.Logf("Error reading data: %v", err) + } + }(conn) + } + }() + + return listener, dataChan +} + +// TestDialStreamWithDifferentParameters - test data transmission with different parameters +func (suite *OOBDialerTestSuite) TestDialStreamWithDifferentParameters() { + tests := []struct { + oobPosition int64 + oobByte byte + disOOB bool + delay time.Duration + }{ + {oobPosition: 0, oobByte: 0x01, disOOB: false, delay: 100 * time.Millisecond}, + {oobPosition: 0, oobByte: 0x01, disOOB: true, delay: 100 * time.Millisecond}, + + {oobPosition: 2, oobByte: 0x02, disOOB: true, delay: 200 * time.Millisecond}, + {oobPosition: 2, oobByte: 0x02, disOOB: false, delay: 200 * time.Millisecond}, + + {oobPosition: int64(msgLen) - 2, oobByte: 0x02, disOOB: true, delay: 200 * time.Millisecond}, + {oobPosition: int64(msgLen) - 2, oobByte: 0x02, disOOB: false, delay: 200 * time.Millisecond}, + + {oobPosition: int64(msgLen) - 1, oobByte: 0x02, disOOB: true, delay: 200 * time.Millisecond}, + {oobPosition: int64(msgLen) - 1, oobByte: 0x02, disOOB: false, delay: 200 * time.Millisecond}, + } + + for _, tt := range tests { + suite.Run("Testing with different parameters", func() { + ctx := context.Background() + + dialer := &transport.TCPDialer{ + Dialer: net.Dialer{}, + } + oobDialer, err := NewStreamDialer(dialer, tt.oobPosition, tt.oobByte, tt.disOOB, tt.delay) + + conn, err := oobDialer.DialStream(ctx, suite.serverAddr) + + require.NoError(suite.T(), err) + + // Send test message + message := []byte("Hello OOB!\n") + n, err := conn.Write(message) + require.NoError(suite.T(), err) + assert.Equal(suite.T(), len(message), n) + + // Check that the server received the message + receivedData := <-suite.dataChan + assert.Equal(suite.T(), string(message[0:len(message)-1]), string(receivedData)) + }) + } +} + +// TestOOBDialerSuite - main test suite +func TestOOBDialerSuite(t *testing.T) { + suite.Run(t, new(OOBDialerTestSuite)) +} diff --git a/x/oob/writer.go b/x/oob/writer.go index ea340491..863bf885 100644 --- a/x/oob/writer.go +++ b/x/oob/writer.go @@ -2,11 +2,12 @@ package oob import ( "fmt" - "github.com/Jigsaw-Code/outline-sdk/x/sockopt" "io" "net" "sync" "time" + + "github.com/Jigsaw-Code/outline-sdk/x/sockopt" ) type oobWriter struct { @@ -46,7 +47,6 @@ func (w *oobWriter) Write(data []byte) (int, error) { var written int var err error - fmt.Println("oobWriter, total: ", len(data)) if w.oobPosition > 0 && w.oobPosition < int64(len(data))-1 { firstPart := data[:w.oobPosition+1] secondPart := data[w.oobPosition:] @@ -76,8 +76,6 @@ func (w *oobWriter) Write(data []byte) (int, error) { written = int(w.oobPosition) secondPart[0] = tmp - fmt.Printf("oobWriter: firstPart len: %d\n", len(firstPart)) - if w.disOOB { w.resetTTL.Do(func() { err = w.opts.SetHopLimit(oldTTL) @@ -95,7 +93,7 @@ func (w *oobWriter) Write(data []byte) (int, error) { // Write the remaining data n, err := w.conn.Write(data) written += n - fmt.Printf("oobWriter: second part len: %d\n", len(data)) + return written, err } From 5b7b48681e4954512622e88b0f1fdecb61b0aa21 Mon Sep 17 00:00:00 2001 From: Sirius Ivliev Date: Wed, 13 Nov 2024 16:23:21 +0100 Subject: [PATCH 20/20] add CA --- x/oob/writer.go | 1 - 1 file changed, 1 deletion(-) diff --git a/x/oob/writer.go b/x/oob/writer.go index 863bf885..9d30dd06 100644 --- a/x/oob/writer.go +++ b/x/oob/writer.go @@ -90,7 +90,6 @@ func (w *oobWriter) Write(data []byte) (int, error) { time.Sleep(w.delay) } - // Write the remaining data n, err := w.conn.Write(data) written += n