Skip to content

Commit

Permalink
dialers/desync: retry recvmsg once after a 5ms delay
Browse files Browse the repository at this point in the history
  • Loading branch information
ignoramous committed Aug 12, 2024
1 parent 1a403a4 commit 1c5eb5a
Showing 1 changed file with 32 additions and 23 deletions.
55 changes: 32 additions & 23 deletions intra/dialers/split_and_desync.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
package dialers

/*
Combine direct_split with TCB (Transmission Control Block) Desynchronization Attack
Inspired by byedpi
*/

import (
"io"
"math/rand"
Expand Down Expand Up @@ -37,12 +32,14 @@ const (
// TODO: invalidate cache on network changes.
var ttlcache = core.NewDefaultSieve[netip.Addr, int]()

// Combines direct split with TCB Desynchronization Attack
// Inspired by byedpi: github.com/hufrea/byedpi/blob/82e5229df00/desync.c#L69-L123
type overwriteSplitter struct {
conn *net.TCPConn
used atomic.Bool
ttl int
ip6 bool
payload []byte // must be smaller than 1st written packet
conn *net.TCPConn // underlying connection
used atomic.Bool // ensures desync only runs once
ttl int // desync TTL
ip6 bool // IPv6
payload []byte // must be smaller than 1st written packet
// note: Normal ClientHello generated by browsers is 517 bytes. If kyber is enabled, the ClientHello can be larger.
}

Expand Down Expand Up @@ -214,22 +211,29 @@ func desyncWithTraceroute(d *protect.RDial, ipp netip.AddrPort) (*overwriteSplit
core.Recycle(bptr)
}()

split1 := &overwriteSplitter{
oc := &overwriteSplitter{
conn: tcpConn,
ttl: DESYNC_NOOP_TTL,
payload: []byte(Http1_1String),
ip6: isIPv6,
}

// skip desync if no measurement is done
defer split1.used.Store(!measureTTL)

processed := false
// after TCP handshake, check received ICMP messages, if measureTTL is true.
for i := 0; i < DESYNC_MAX_TTL-1 && measureTTL; i += DESYNC_DELTA_TTL {
for i := 0; i < desync_max_ttl-1 && measureTTL; i += desync_delta_ttl {
_, cmsgN, _, from, err := unix.Recvmsg(udpFD, msgBuf[:], cmsgBuf[:], unix.MSG_ERRQUEUE)
if err != nil {
log.V("split-desync: recvmsg %v failed: %v", ipp, err)
break // udpConn must be nonblocking
log.V("split-desync: recvmsg %v, processed? %t, err: %v", ipp, processed, err)
if processed { // recvmsg processed at least once
break // uc must be nonblocking
} else {
processed = true
i -= desync_delta_ttl
time.Sleep(5 * time.Millisecond)
continue
}
} else {
processed = true
}

cmsgs, err := unix.ParseSocketControlMessage(cmsgBuf[:cmsgN])
Expand All @@ -242,26 +246,29 @@ func desyncWithTraceroute(d *protect.RDial, ipp netip.AddrPort) (*overwriteSplit
if exceedsHopLimit(cmsgs) {
fromPort := from.(*unix.SockaddrInet6).Port
ttl := fromPort - basePort
if ttl > DESYNC_MAX_TTL {
if ttl > desync_max_ttl {
break
}
split1.ttl = max(split1.ttl, ttl)
oc.ttl = max(oc.ttl, ttl)
}
} else {
if exceedsTTL(cmsgs) {
fromPort := from.(*unix.SockaddrInet4).Port
ttl := fromPort - basePort
if ttl > DESYNC_MAX_TTL {
if ttl > desync_max_ttl {
break
}
split1.ttl = max(split1.ttl, ttl)
oc.ttl = max(oc.ttl, ttl)
}
}
}

log.D("split-desync: done: %v, ok? %t, ttl: %d", ipp, measureTTL, split1.ttl)
// skip desync if no measurement is done
oc.used.Store(!processed)

log.D("split-desync: done: %v, ok? %t, ttl: %d", ipp, processed, oc.ttl)

return split1, nil
return oc, nil
}

func desyncWithFixedTtl(d *protect.RDial, ipp netip.AddrPort, initialTTL int) (*overwriteSplitter, error) {
Expand Down Expand Up @@ -444,5 +451,7 @@ func (s *overwriteSplitter) ReadFrom(reader io.Reader) (bytes int64, err error)

b, err := s.conn.ReadFrom(reader)
bytes += b
log.V("split-desync: readfrom: done; sz: %d; err: %v", bytes, err)

return
}

1 comment on commit 1c5eb5a

@ignoramous
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#81

Please sign in to comment.