Skip to content

Commit

Permalink
Review fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
PeterZhizhin committed Nov 12, 2024
1 parent 36def4b commit 5d6424a
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 33 deletions.
23 changes: 23 additions & 0 deletions x/configurl/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,35 @@ For more details, refer to [github.com/Jigsaw-Code/outline-sdk/transport/tlsfrag
tlsfrag:[LENGTH]
Disorder transport (streams only, package [github.com/Jigsaw-Code/outline-sdk/x/disorder])
The disorder strategy sends TCP packets out of order by manipulating the
socket's Time To Live (TTL) or Hop Limit. It temporarily sets the TTL to a low
value, causing specific packets to be dropped early in the network (like at the
first router). These dropped packets are then re-transmitted later by the TCP
stack, resulting in the receiver getting packets out of order. This can help
bypass network filters that rely on inspecting the initial packets of a TCP
connection.
disorder:[PACKET_NUMBER]
PACKET_NUMBER: The number of writes before the disorder action occurs. The
disorder action triggers when the number of writes equals PACKET_NUMBER. If set
to 0 (default), the disorder happens on the first write. If set to 1, it happens
on the second write, and so on.
# Examples
Packet splitting - To split outgoing streams on bytes 2 and 123, you can use:
split:2|split:123
Disorder transport - Send some of the packets out of order
disorder:0|split:123
Split at position 123, then send packet 0 of 123 bytes (from splitting) out of order. The network filter will first receive packet 1, only then packet 0.
Evading DNS and SNI blocking - You can use Cloudflare's DNS-over-HTTPS to protect against DNS disruption.
The DoH resolver cloudflare-dns.com is accessible from any cloudflare.net IP, so you can specify the address to avoid blocking
of the resolver itself. This can be combines with a TCP split or TLS Record Fragmentation to bypass SNI-based blocking:
Expand Down
10 changes: 4 additions & 6 deletions x/disorder/stream_dialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ func NewStreamDialer(dialer transport.StreamDialer, disorderPacketN int) (transp
if dialer == nil {
return nil, errors.New("argument dialer must not be nil")
}
if disorderPacketN < 0 {
return nil, fmt.Errorf("disorder argument must be >= 0, got %d", disorderPacketN)
}
return &disorderDialer{dialer: dialer, disorderPacketN: disorderPacketN}, nil
}

Expand All @@ -62,12 +65,7 @@ func (d *disorderDialer) DialStream(ctx context.Context, remoteAddr string) (tra
return nil, err
}

defaultHopLimit, err := tcpOptions.HopLimit()
if err != nil {
return nil, fmt.Errorf("disorder strategy: failed to get base connection HopLimit: %w", err)
}

dw := NewWriter(innerConn, tcpOptions, d.disorderPacketN, defaultHopLimit)
dw := NewWriter(innerConn, tcpOptions, d.disorderPacketN)

return transport.WrapConn(innerConn, innerConn, dw), nil
}
54 changes: 27 additions & 27 deletions x/disorder/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,55 +22,55 @@ import (
)

type disorderWriter struct {
conn io.Writer
tcpOptions sockopt.TCPOptions
runAtPacketN int
defaultHopLimit int
writeCalls int
conn io.Writer
tcpOptions sockopt.TCPOptions
writesToDisorder int
}

var _ io.Writer = (*disorderWriter)(nil)

// Setting number of hops to 1 will lead to data to get lost on host
var disorderHopN = 1

func NewWriter(conn io.Writer, tcpOptions sockopt.TCPOptions, runAtPacketN int, defaultHopLimit int) io.Writer {
func NewWriter(conn io.Writer, tcpOptions sockopt.TCPOptions, runAtPacketN int) io.Writer {
// TODO: Support ReadFrom.
return &disorderWriter{
conn: conn,
tcpOptions: tcpOptions,
runAtPacketN: runAtPacketN,
defaultHopLimit: defaultHopLimit,
writeCalls: 0,
conn: conn,
tcpOptions: tcpOptions,
writesToDisorder: runAtPacketN,
}
}

func (w *disorderWriter) Write(data []byte) (written int, err error) {
shouldDoDisorder := w.writeCalls == w.runAtPacketN
if shouldDoDisorder {
if w.writesToDisorder == 0 {
defaultHopLimit, err := w.tcpOptions.HopLimit()
if err != nil {
return 0, fmt.Errorf("failed to get the hop limit: %w", err)
}

err = w.tcpOptions.SetHopLimit(disorderHopN)
if err != nil {
return 0, fmt.Errorf("failed to set the hop limit to %d: %w", disorderHopN, err)
}

// The packet will get lost at the first send, since the hop limit is too low
defer func() {
// The packet with low hop limit was sent
// Make next calls send data normally
//
// The packet with the low hop limit will get resent by the kernel later
// The network filters will receive data out of order
err = w.tcpOptions.SetHopLimit(defaultHopLimit)
if err != nil {
err = fmt.Errorf("failed to set the hop limit error %d: %w", defaultHopLimit, err)
}
}()
}

// The packet will get lost at the first send, since the hop limit is too low
n, err := w.conn.Write(data)

// TODO: Wait for queued data to be sent by the kernel to the socket

if shouldDoDisorder {
// The packet with low hop limit was sent
// Make next calls send data normally
//
// The packet with the low hop limit will get resent by the kernel later
// The network filters will receive data out of order
err = w.tcpOptions.SetHopLimit(w.defaultHopLimit)
if err != nil {
return n, fmt.Errorf("failed to set the hop limit error %d: %w", w.defaultHopLimit, err)
}
}

w.writeCalls += 1
w.writesToDisorder -= 1
return n, err
}

0 comments on commit 5d6424a

Please sign in to comment.