diff --git a/go.mod b/go.mod index 04c5eb812..e53f6e49f 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,6 @@ require ( golang.zx2c4.com/wireguard/windows v1.1.1 google.golang.org/grpc v1.84.0 google.golang.org/protobuf v1.36.12 - gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 h12.io/socks v1.0.3 layeh.com/gopher-luar v1.0.11 lukechampine.com/blake3 v1.4.1 @@ -42,7 +41,6 @@ require ( require ( github.com/andybalholm/brotli v1.2.3 // indirect - github.com/google/btree v1.1.2 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/huin/goupnp v1.2.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect @@ -58,8 +56,8 @@ require ( github.com/wlynxg/anet v0.0.5 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect golang.org/x/text v0.43.0 // indirect - golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.51.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect ) diff --git a/proxy/masque/client.go b/proxy/masque/client.go index fc6999c3f..4987948a2 100644 --- a/proxy/masque/client.go +++ b/proxy/masque/client.go @@ -327,7 +327,7 @@ func newTunnel(conn stat.Connection, local []netip.Addr, remoteDNS []netip.Addr, dns = remoteDNS } - dev, tnet, _, err := wireguard.CreateNetTUN(local, dns, masque.MinPacketSize, true) + dev, tnet, err := wireguard.CreateNetTUN(local, dns, masque.MinPacketSize, nil) if err != nil { return nil, err } diff --git a/proxy/masque/server.go b/proxy/masque/server.go index f9cc2ff37..db8660d6c 100644 --- a/proxy/masque/server.go +++ b/proxy/masque/server.go @@ -22,6 +22,7 @@ import ( "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/features/routing" + xtun "github.com/xtls/xray-core/proxy/tun" "github.com/xtls/xray-core/proxy/wireguard" "github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport/internet" @@ -166,17 +167,11 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) { if mtu == 0 { mtu = masque.MinPacketSize } - dev, _, gstack, err := wireguard.CreateNetTUN(local, nil, mtu, false) - if err != nil { - return nil, err - } - s := &Server{ validator: users, dispatcher: v.GetFeature(routing.DispatcherType()).(routing.Dispatcher), ctx: core.ToBackgroundDetachedContext(ctx), mtu: mtu, - dev: dev, pools: pools, local: local, tunnels: make(map[netip.Addr]*serverTunnel), @@ -187,7 +182,11 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) { if content := session.ContentFromContext(ctx); content != nil { s.sniffing = content.SniffingRequest } - wireguard.CreateForwarder(gstack, s.handleConnection) + dev, _, err := wireguard.CreateNetTUN(local, nil, mtu, s) + if err != nil { + return nil, err + } + s.dev = dev return s, nil } @@ -500,7 +499,8 @@ func packetDestination(packet []byte) (netip.Addr, bool) { return netip.Addr{}, false } -func (s *Server) handleConnection(conn net.Conn, dest net.Destination) { +// HandleConnection implements tun.ConnectionHandler +func (s *Server) HandleConnection(conn net.Conn, dest net.Destination) { defer conn.Close() source := net.DestinationFromAddr(conn.RemoteAddr()) addr, _ := netip.AddrFromSlice(source.Address.IP()) @@ -534,9 +534,13 @@ func (s *Server) handleConnection(conn net.Conn, dest net.Destination) { }) errors.LogInfo(ctx, "processing from ", source, " to ", dest) + writer := buf.NewWriter(conn) + if dest.Network == net.Network_TCP { + writer = &xtun.TCPWriter{Writer: writer} + } link := &transport.Link{ Reader: &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)}, - Writer: buf.NewWriter(conn), + Writer: writer, } if err := s.dispatcher.DispatchLink(ctx, dest, link); err != nil { errors.LogError(ctx, errors.New("connection closed").Base(err)) diff --git a/proxy/masque/server_test.go b/proxy/masque/server_test.go index 9b526f764..e10b6847f 100644 --- a/proxy/masque/server_test.go +++ b/proxy/masque/server_test.go @@ -7,13 +7,17 @@ import ( "net/netip" "os" "sync" + "sync/atomic" "testing" "time" "github.com/stretchr/testify/require" + "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol" + "github.com/xtls/xray-core/features/routing" + "github.com/xtls/xray-core/transport" "golang.zx2c4.com/wireguard/tun" ) @@ -326,3 +330,56 @@ func TestPacketDestination(t *testing.T) { require.False(t, ok) } } + +// stackConn is a TCP connection as the stack hands it over: it is its own buf.Writer. +type stackConn struct { + net.Conn + closed atomic.Int32 +} + +func (c *stackConn) Read([]byte) (int, error) { return 0, io.EOF } + +func (c *stackConn) WriteMultiBuffer(mb buf.MultiBuffer) error { + buf.ReleaseMulti(mb) + return nil +} + +func (c *stackConn) Close() error { + c.closed.Add(1) + return nil +} + +func (c *stackConn) RemoteAddr() net.Addr { + return &net.TCPAddr{IP: net.IP{10, 13, 0, 2}, Port: 1234} +} + +// closingDispatcher does what an outbound does when the other side has no more to send. +type closingDispatcher struct { + routing.Dispatcher + conn *stackConn + closed int32 +} + +func (d *closingDispatcher) DispatchLink(_ context.Context, _ net.Destination, link *transport.Link) error { + common.Close(link.Writer) + d.closed = d.conn.closed.Load() + return nil +} + +func TestServerKeepsConnectionOpenWhenWriterCloses(t *testing.T) { + conn := &stackConn{} + dispatcher := &closingDispatcher{conn: conn} + s := &Server{ + ctx: context.Background(), + dispatcher: dispatcher, + tunnels: map[netip.Addr]*serverTunnel{netip.MustParseAddr("10.13.0.2"): newServerTunnel(nil, &protocol.MemoryUser{})}, + } + s.HandleConnection(conn, net.TCPDestination(net.ParseAddress("192.0.2.1"), 443)) + + if dispatcher.closed != 0 { + t.Fatal("the connection was closed with its writer, while the client may still be sending") + } + if got := conn.closed.Load(); got != 1 { + t.Fatalf("connection closed %d times, want 1", got) + } +} diff --git a/proxy/tun/handler.go b/proxy/tun/handler.go index f566c84ad..b878227d9 100644 --- a/proxy/tun/handler.go +++ b/proxy/tun/handler.go @@ -56,20 +56,21 @@ func (w *tunUDPStatsWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { return nil } -// tunTCPWriter is the writing side of a TCP connection for the dispatcher. It has no Close: the outbound closes -// its writer when the other side has no more to send, and the system may still be sending -type tunTCPWriter struct { - writer buf.Writer - counter stats.Counter +// TCPWriter is the writing side of a TCP connection of the stack for the dispatcher. It has no Close: the outbound +// closes its writer when the other side has no more to send, and the system may still be sending +type TCPWriter struct { + Writer buf.Writer + // Counter counts what is written, if there is one + Counter stats.Counter } -func (w *tunTCPWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { +func (w *TCPWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { size := mb.Len() - if err := w.writer.WriteMultiBuffer(mb); err != nil { + if err := w.Writer.WriteMultiBuffer(mb); err != nil { return err } - if w.counter != nil { - w.counter.Add(int64(size)) + if w.Counter != nil { + w.Counter.Add(int64(size)) } return nil } @@ -243,7 +244,7 @@ func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) { reader := &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn), Counter: t.uplinkCounter} writer := buf.NewWriter(conn) if destination.Network != net.Network_UDP { - writer = &tunTCPWriter{writer: writer, counter: t.downlinkCounter} + writer = &TCPWriter{Writer: writer, Counter: t.downlinkCounter} } else if t.downlinkCounter != nil { writer = &tunUDPStatsWriter{writer: writer, counter: t.downlinkCounter} } diff --git a/proxy/tun/icmp/packet.go b/proxy/tun/icmp/packet.go deleted file mode 100644 index 126e9d88c..000000000 --- a/proxy/tun/icmp/packet.go +++ /dev/null @@ -1,106 +0,0 @@ -package icmp - -import ( - "github.com/xtls/xray-core/common/errors" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/checksum" - "gvisor.dev/gvisor/pkg/tcpip/header" -) - -func ProtocolLabel(netProto tcpip.NetworkProtocolNumber) string { - switch netProto { - case header.IPv4ProtocolNumber: - return "ipv4" - case header.IPv6ProtocolNumber: - return "ipv6" - default: - return "unknown" - } -} - -func ParseEchoRequest(netProto tcpip.NetworkProtocolNumber, message []byte) (uint16, uint16, bool) { - switch netProto { - case header.IPv4ProtocolNumber: - if len(message) < header.ICMPv4MinimumSize { - return 0, 0, false - } - icmpHdr := header.ICMPv4(message) - if icmpHdr.Type() != header.ICMPv4Echo || icmpHdr.Code() != header.ICMPv4UnusedCode { - return 0, 0, false - } - return icmpHdr.Ident(), icmpHdr.Sequence(), true - case header.IPv6ProtocolNumber: - if len(message) < header.ICMPv6MinimumSize { - return 0, 0, false - } - icmpHdr := header.ICMPv6(message) - if icmpHdr.Type() != header.ICMPv6EchoRequest || icmpHdr.Code() != header.ICMPv6UnusedCode { - return 0, 0, false - } - return icmpHdr.Ident(), icmpHdr.Sequence(), true - default: - return 0, 0, false - } -} - -func RewriteChecksum(netProto tcpip.NetworkProtocolNumber, message []byte, srcIP, dstIP tcpip.Address) error { - switch netProto { - case header.IPv4ProtocolNumber: - if len(message) < header.ICMPv4MinimumSize { - return errors.New("invalid icmpv4 packet") - } - icmpHdr := header.ICMPv4(message) - icmpHdr.SetChecksum(0) - icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksum.Checksum(icmpHdr.Payload(), 0))) - return nil - case header.IPv6ProtocolNumber: - if len(message) < header.ICMPv6MinimumSize { - return errors.New("invalid icmpv6 packet") - } - icmpHdr := header.ICMPv6(message) - icmpHdr.SetChecksum(0) - icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{ - Header: icmpHdr[:header.ICMPv6MinimumSize], - Src: srcIP, - Dst: dstIP, - PayloadCsum: checksum.Checksum(icmpHdr.Payload(), 0), - PayloadLen: len(icmpHdr.Payload()), - })) - return nil - default: - return errors.New("unsupported icmp network protocol") - } -} - -func BuildLocalEchoReply(netProto tcpip.NetworkProtocolNumber, request []byte, srcIP, dstIP tcpip.Address) ([]byte, error) { - reply := append([]byte(nil), request...) - - switch netProto { - case header.IPv4ProtocolNumber: - if len(reply) < header.ICMPv4MinimumSize { - return nil, errors.New("invalid icmpv4 echo packet") - } - icmpHdr := header.ICMPv4(reply) - if icmpHdr.Type() != header.ICMPv4Echo || icmpHdr.Code() != header.ICMPv4UnusedCode { - return nil, errors.New("not an icmpv4 echo request") - } - reply[0] = byte(header.ICMPv4EchoReply) - case header.IPv6ProtocolNumber: - if len(reply) < header.ICMPv6MinimumSize { - return nil, errors.New("invalid icmpv6 echo packet") - } - icmpHdr := header.ICMPv6(reply) - if icmpHdr.Type() != header.ICMPv6EchoRequest || icmpHdr.Code() != header.ICMPv6UnusedCode { - return nil, errors.New("not an icmpv6 echo request") - } - reply[0] = byte(header.ICMPv6EchoReply) - default: - return nil, errors.New("unsupported icmp network protocol") - } - - if err := RewriteChecksum(netProto, reply, srcIP, dstIP); err != nil { - return nil, err - } - - return reply, nil -} diff --git a/proxy/tun/icmp/packet_test.go b/proxy/tun/icmp/packet_test.go deleted file mode 100644 index bb7598683..000000000 --- a/proxy/tun/icmp/packet_test.go +++ /dev/null @@ -1,174 +0,0 @@ -package icmp - -import ( - "testing" - - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/checksum" - "gvisor.dev/gvisor/pkg/tcpip/header" -) - -func TestParseEchoRequest(t *testing.T) { - t.Run("ipv4 echo", func(t *testing.T) { - var zero tcpip.Address - packet := []byte{ - byte(header.ICMPv4Echo), 0, - 0, 0, - 0x12, 0x34, - 0x56, 0x78, - 0xaa, 0xbb, - } - if err := RewriteChecksum(header.IPv4ProtocolNumber, packet, zero, zero); err != nil { - t.Fatal(err) - } - - ident, sequence, ok := ParseEchoRequest(header.IPv4ProtocolNumber, packet) - if !ok { - t.Fatal("expected ipv4 echo request to parse") - } - if ident != 0x1234 || sequence != 0x5678 { - t.Fatalf("unexpected ident/sequence: %x/%x", ident, sequence) - } - }) - - t.Run("ipv6 echo", func(t *testing.T) { - packet := []byte{ - byte(header.ICMPv6EchoRequest), 0, - 0, 0, - 0xab, 0xcd, - 0xef, 0x01, - 0xaa, 0xbb, - } - src := tcpip.AddrFromSlice([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) - dst := tcpip.AddrFromSlice([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}) - if err := RewriteChecksum(header.IPv6ProtocolNumber, packet, src, dst); err != nil { - t.Fatal(err) - } - - ident, sequence, ok := ParseEchoRequest(header.IPv6ProtocolNumber, packet) - if !ok { - t.Fatal("expected ipv6 echo request to parse") - } - if ident != 0xabcd || sequence != 0xef01 { - t.Fatalf("unexpected ident/sequence: %x/%x", ident, sequence) - } - }) -} - -func TestRewriteChecksum(t *testing.T) { - t.Run("ipv4", func(t *testing.T) { - var zero tcpip.Address - packet := []byte{ - byte(header.ICMPv4Echo), 0, - 0xff, 0xff, - 0x12, 0x34, - 0x56, 0x78, - 0xaa, 0xbb, 0xcc, - } - if err := RewriteChecksum(header.IPv4ProtocolNumber, packet, zero, zero); err != nil { - t.Fatal(err) - } - - icmpHdr := header.ICMPv4(packet) - if got, want := icmpHdr.Checksum(), header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksumPayloadV4(icmpHdr.Payload())); got != want { - t.Fatalf("unexpected ipv4 checksum: got %x want %x", got, want) - } - }) - - t.Run("ipv6", func(t *testing.T) { - packet := []byte{ - byte(header.ICMPv6EchoReply), 0, - 0xff, 0xff, - 0x12, 0x34, - 0x56, 0x78, - 0xaa, 0xbb, 0xcc, - } - src := tcpip.AddrFromSlice([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) - dst := tcpip.AddrFromSlice([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}) - if err := RewriteChecksum(header.IPv6ProtocolNumber, packet, src, dst); err != nil { - t.Fatal(err) - } - - icmpHdr := header.ICMPv6(packet) - want := header.ICMPv6Checksum(header.ICMPv6ChecksumParams{ - Header: icmpHdr[:header.ICMPv6MinimumSize], - Src: src, - Dst: dst, - PayloadLen: len(icmpHdr.Payload()), - PayloadCsum: checksumPayloadV6(icmpHdr.Payload()), - }) - if got := icmpHdr.Checksum(); got != want { - t.Fatalf("unexpected ipv6 checksum: got %x want %x", got, want) - } - }) -} - -func TestBuildLocalEchoReply(t *testing.T) { - t.Run("ipv4", func(t *testing.T) { - request := []byte{ - byte(header.ICMPv4Echo), 0, - 0, 0, - 0x12, 0x34, - 0x56, 0x78, - 0xaa, 0xbb, 0xcc, - } - src := tcpip.Address{} - dst := tcpip.Address{} - if err := RewriteChecksum(header.IPv4ProtocolNumber, request, src, dst); err != nil { - t.Fatal(err) - } - - reply, err := BuildLocalEchoReply(header.IPv4ProtocolNumber, request, dst, src) - if err != nil { - t.Fatal(err) - } - if request[0] != byte(header.ICMPv4Echo) { - t.Fatal("request mutated") - } - icmpHdr := header.ICMPv4(reply) - if icmpHdr.Type() != header.ICMPv4EchoReply || icmpHdr.Code() != header.ICMPv4UnusedCode { - t.Fatalf("unexpected ipv4 reply type/code: %d/%d", icmpHdr.Type(), icmpHdr.Code()) - } - if icmpHdr.Ident() != 0x1234 || icmpHdr.Sequence() != 0x5678 { - t.Fatalf("unexpected ipv4 ident/sequence: %x/%x", icmpHdr.Ident(), icmpHdr.Sequence()) - } - }) - - t.Run("ipv6", func(t *testing.T) { - request := []byte{ - byte(header.ICMPv6EchoRequest), 0, - 0, 0, - 0xab, 0xcd, - 0xef, 0x01, - 0xaa, 0xbb, 0xcc, - } - src := tcpip.AddrFromSlice([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) - dst := tcpip.AddrFromSlice([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}) - if err := RewriteChecksum(header.IPv6ProtocolNumber, request, src, dst); err != nil { - t.Fatal(err) - } - - reply, err := BuildLocalEchoReply(header.IPv6ProtocolNumber, request, dst, src) - if err != nil { - t.Fatal(err) - } - if request[0] != byte(header.ICMPv6EchoRequest) { - t.Fatal("request mutated") - } - icmpHdr := header.ICMPv6(reply) - if icmpHdr.Type() != header.ICMPv6EchoReply || icmpHdr.Code() != header.ICMPv6UnusedCode { - t.Fatalf("unexpected ipv6 reply type/code: %d/%d", icmpHdr.Type(), icmpHdr.Code()) - } - if icmpHdr.Ident() != 0xabcd || icmpHdr.Sequence() != 0xef01 { - t.Fatalf("unexpected ipv6 ident/sequence: %x/%x", icmpHdr.Ident(), icmpHdr.Sequence()) - } - }) -} - -func checksumPayloadV4(payload []byte) uint16 { - return checksum.Checksum(payload, 0) -} - -func checksumPayloadV6(payload []byte) uint16 { - return checksum.Checksum(payload, 0) -} diff --git a/proxy/tun/packet.go b/proxy/tun/packet.go index f9933e307..a4a97c9c7 100644 --- a/proxy/tun/packet.go +++ b/proxy/tun/packet.go @@ -2,8 +2,14 @@ package tun import ( "encoding/binary" + "math" "math/bits" "net/netip" + "slices" + "sync" + "time" + + "github.com/xtls/xray-core/common/errors" ) const ( @@ -16,6 +22,19 @@ const ( protoTCP = 6 protoUDP = 17 protoICMPv6 = 58 + + // the smallest that a link is believed to take whatever is said about it: what every IPv4 host takes, + // and the smallest link that IPv6 runs on + ipv4SmallestMTU = 576 + ipv6SmallestMTU = 1280 + + // the header that an IPv6 packet has in front of each of its fragments, and its number + fragmentHeaderSize = 8 + protoFragment = 44 + // fragmentsMost is how many packets can be waiting for the rest of their fragments, + // fragmentsTime how long they are waited for at most + fragmentsMost = 64 + fragmentsTime = 30 * time.Second ) // packet is an IP packet with its header read @@ -24,9 +43,15 @@ type packet struct { src, dst netip.Addr // payload is what follows the IP header payload []byte + // fragment is set if payload is a part of what follows the IP header: the one at offset of the packet + // with the number id, which has more after it or not + fragment bool + more bool + offset int + id uint32 } -// parsePacket reads the IP header of b. Fragments are not accepted: what the system sends to the tun fits its MTU +// parsePacket reads the IP header of b func parsePacket(b []byte) (p packet, ok bool) { if len(b) < ipv4HeaderSize { return p, false @@ -35,9 +60,12 @@ func parsePacket(b []byte) (p packet, ok bool) { case 4: size := int(b[0]&0xf) * 4 total := int(binary.BigEndian.Uint16(b[2:])) - if size < ipv4HeaderSize || total < size || total > len(b) || binary.BigEndian.Uint16(b[6:])&0x3fff != 0 { + if size < ipv4HeaderSize || total < size || total > len(b) { return p, false } + if f := binary.BigEndian.Uint16(b[6:]); f&0x3fff != 0 { + p.fragment, p.more, p.offset, p.id = true, f&0x2000 != 0, int(f&0x1fff)*8, uint32(binary.BigEndian.Uint16(b[4:])) + } p.proto = b[9] p.src = netip.AddrFrom4([4]byte(b[12:16])) p.dst = netip.AddrFrom4([4]byte(b[16:20])) @@ -65,12 +93,55 @@ func parsePacket(b []byte) (p packet, ok bool) { } p.proto, p.payload = p.payload[0], p.payload[size:] } + if p.proto == protoFragment { + f := p.payload + if len(f) < fragmentHeaderSize { + return p, false + } + p.fragment, p.more, p.offset, p.id = true, f[3]&1 != 0, int(binary.BigEndian.Uint16(f[2:])&^7), binary.BigEndian.Uint32(f[4:]) + p.proto, p.payload = f[0], f[fragmentHeaderSize:] + } default: return p, false } return p, true } +// verified reports whether the checksum of what the packet carries is right +func (p packet) verified() bool { + data := p.payload + switch p.proto { + case protoICMPv4: + return checksum(sum(data, 0)) == 0 + case protoUDP: + if len(data) < udpHeaderSize { + return false + } + // the packet may be longer than what it carries + size := int(binary.BigEndian.Uint16(data[4:])) + if size < udpHeaderSize || size > len(data) { + return false + } + if p.src.Is4() && data[6]|data[7] == 0 { + // zero stands for no checksum + return true + } + data = data[:size] + case protoTCP, protoICMPv6: + default: + return true + } + pseudo := uint64(p.proto) + uint64(len(data)) + if p.src.Is4() { + src, dst := p.src.As4(), p.dst.As4() + pseudo = sum(dst[:], sum(src[:], pseudo)) + } else { + src, dst := p.src.As16(), p.dst.As16() + pseudo = sum(dst[:], sum(src[:], pseudo)) + } + return checksum(sum(data, pseudo)) == 0 +} + // putIPHeader writes the header of a packet from src to dst into b, without its length, and returns the size of the header func putIPHeader(b []byte, proto byte, src, dst netip.Addr) int { if src.Is4() { @@ -159,3 +230,161 @@ func checksum(s uint64) uint16 { s = s>>16 + s&0xffff return ^uint16(s) } + +// writeFragments writes a packet that is larger than mtu to tun in fragments that are not. header is its IP header +// with what follows that up to payload, which is no more than the header of a UDP packet. id is the number +// of the packet if it is an IPv6 one +func writeFragments(tun Tun, mtu int, id uint32, header, payload []byte) error { + var b [ipv6HeaderSize + fragmentHeaderSize + udpHeaderSize]byte + size := ipv4HeaderSize + if header[0]>>4 == 6 { + size = ipv6HeaderSize + } + n := copy(b[:], header[:size]) + if size == ipv6HeaderSize { + // the number goes into a header of its own, behind the IP header. That of IPv4 has it + b[6], b[n] = protoFragment, header[6] + binary.BigEndian.PutUint32(b[n+4:], id) + n += fragmentHeaderSize + } + // first goes in front of payload, in the first fragment + first := header[size:] + if n+len(first) > len(b) { + return errors.New("no room for a header of ", len(first), " bytes in a fragment") + } + // every fragment but the last is a number of 8 bytes + room, total := (mtu-n)&^7, len(first)+len(payload) + if room < 8 { + return errors.New("no room for a fragment in an MTU of ", mtu) + } + for offset := 0; offset < total; { + h, part := b[:n], min(room, total-offset) + last := offset+part == total + if size == ipv6HeaderSize { + f := uint16(offset) + if !last { + f |= 1 + } + binary.BigEndian.PutUint16(b[size+2:], f) + } else { + f := uint16(offset / 8) + if !last { + f |= 0x2000 + } + binary.BigEndian.PutUint16(b[6:], f) + } + setIPLength(b[:size], n+part) + data := payload[max(offset-len(first), 0):min(offset+part-len(first), len(payload))] + if offset == 0 { + h = b[:n+copy(b[n:], first)] + } + if err := tun.WritePacket(h, data); err != nil { + return err + } + offset += part + } + return nil +} + +// fragmented is a packet of which not every fragment has come yet +type fragmented struct { + // data is what follows its IP header, as far as its last fragment that has come goes + data []byte + // parts are what of data has come, in order and apart from each other + parts [][2]int + // size is that of data when all of it has come, known from the last fragment + size int + known bool + since time.Time +} + +// reassembly puts the packets that come in fragments together +type reassembly struct { + mu sync.Mutex + packets map[fragments]*fragmented +} + +// fragments is what the fragments of a packet have in common +type fragments struct { + src, dst netip.Addr + proto byte + id uint32 +} + +// add takes the fragment p of a packet. With the last one that was missing, it returns the packet. +// A packet whose fragments do not fit each other is given up +func (r *reassembly) add(p packet) (packet, bool) { + fragment, offset, end := p.payload, p.offset, p.offset+len(p.payload) + // what an IP packet can carry + most := math.MaxUint16 + if p.src.Is4() { + most -= ipv4HeaderSize + } + if len(fragment) == 0 || end > most || p.more && len(fragment)%8 != 0 { + return p, false + } + key, now := fragments{p.src, p.dst, p.proto, p.id}, time.Now() + r.mu.Lock() + defer r.mu.Unlock() + f := r.packets[key] + if f != nil && now.Sub(f.since) > fragmentsTime { + // what is left of a packet whose other fragments were lost: this is one of another packet with its number + f = nil + } + if f == nil { + if _, stale := r.packets[key]; !stale && len(r.packets) >= fragmentsMost { + // the one that is waited for the longest makes room, not the one that comes now + var longest fragments + var since time.Time + for other, old := range r.packets { + if since.IsZero() || old.since.Before(since) { + longest, since = other, old.since + } + } + delete(r.packets, longest) + } + if r.packets == nil { + r.packets = make(map[fragments]*fragmented) + } + f = &fragmented{since: now} + r.packets[key] = f + } + for _, part := range f.parts { + if offset >= part[0] && end <= part[1] { + // came already + return p, false + } + if offset < part[1] && part[0] < end { + // fragments of one packet do not overlap: these are of two, or made to be taken for something else + delete(r.packets, key) + return p, false + } + } + if f.known && end > f.size || !p.more && len(f.data) > end { + // past its end, or an end before what came + delete(r.packets, key) + return p, false + } + if !p.more { + f.size, f.known = end, true + } + if len(f.data) < end { + f.data = append(f.data, make([]byte, end-len(f.data))...) + } + copy(f.data[offset:], fragment) + i := 0 + for i < len(f.parts) && f.parts[i][1] < offset { + i++ + } + j := i + for ; j < len(f.parts) && f.parts[j][0] <= end; j++ { + offset, end = min(offset, f.parts[j][0]), max(end, f.parts[j][1]) + } + f.parts = slices.Replace(f.parts, i, j, [2]int{offset, end}) + if !f.known || f.parts[0] != [2]int{0, f.size} { + return p, false + } + delete(r.packets, key) + p.payload, p.fragment = f.data[:f.size], false + return p, true +} diff --git a/proxy/tun/stack.go b/proxy/tun/stack.go index 4efb1c458..24830c8d4 100644 --- a/proxy/tun/stack.go +++ b/proxy/tun/stack.go @@ -3,7 +3,12 @@ package tun import ( "context" "encoding/binary" + "io" + "math" + "math/rand/v2" + gonet "net" "net/netip" + "slices" "sync" "sync/atomic" "time" @@ -15,8 +20,16 @@ import ( // Stack interface implement ip protocol stack, bridging raw network packets and data streams type Stack interface { + // Start makes the stack read the packets of its tun Start() error Close() error + // Input takes a packet of a tun that hands them over instead of being read. The packet is not kept, + // but it may be written to. The packets of one connection have to come one after another + Input(packet []byte) + // DialTCP opens a connection to address from the one of StackOptions.Addresses that is of its family + DialTCP(ctx context.Context, address netip.AddrPort) (net.Conn, error) + // DialUDP opens a port on StackOptions.Addresses. What anyone sends to the port is read from it + DialUDP() (net.PacketConn, error) } // StackOptions for the stack implementation @@ -24,6 +37,12 @@ type StackOptions struct { Tun Tun MTU uint32 IdleTimeout time.Duration + // Addresses are what the stack opens connections from + Addresses []netip.Addr + // Remote is set if the tun is a tunnel to another system and not the tun of this one: what comes through it + // has come a long way, so its checksums are looked at, nothing but that way limits the windows, and the + // segments of TCP carry the times of both sides if the other side has them + Remote bool } // flow is what the packets of one connection have in common, src is the side of the system @@ -33,7 +52,8 @@ type flow struct { // stack turns the packets that the system sends to the tun into connections and back. // It has only what the two need to talk through a tun, and a local link when the system passes on -// what others send, see tcpConn +// what others send, see tcpConn. The tun may also be a tunnel to a system that is far away, and the stack +// the one that opens the connections type stack struct { ctx context.Context tun Tun @@ -42,32 +62,58 @@ type stack struct { segmenter segmenter // window is the window of every connection window int32 - // handle is given every new connection + // verify is set if the checksums of what comes are looked at + verify bool + // stamps is set if connections carry the times of RFC 7323 when the other side does + stamps bool + // handle is given every new connection that the other side opens. Without it those are refused handle func(conn net.Conn, destination net.Destination) udp *udpConnectionHandler + // addresses are what connections are opened from + addresses []netip.Addr - mu sync.Mutex - conns map[flow]*tcpConn - closed bool - // opening is how many of conns wait for the answer to their SYN-ACK + mu sync.Mutex + conns map[flow]*tcpConn + // sockets are the ports that DialUDP has opened + sockets map[netip.AddrPort]*udpSocket + closed bool + // opening is how many of conns wait for the answer to their SYN or SYN-ACK opening atomic.Int32 + // fragments are the packets that are larger than a link on their way and come in parts. + // identification numbers the ones that leave like that: a counter for each of a few groups of destinations, + // each from a start of its own, so that no two of them that go one way meet, and no destination learns how much + // goes to the others + fragments reassembly + identification [64]atomic.Uint32 } -// NewStack builds new ip stack -func NewStack(ctx context.Context, options StackOptions, handler *Handler) (Stack, error) { +// NewStack builds new ip stack. Without a handler it takes no connections, only opens them +func NewStack(ctx context.Context, options StackOptions, handler ConnectionHandler) (Stack, error) { s := &stack{ - ctx: ctx, - tun: options.Tun, - mtu: int(options.MTU), - window: tcpWindow, - handle: handler.HandleConnection, - conns: make(map[flow]*tcpConn), + ctx: ctx, + tun: options.Tun, + mtu: int(options.MTU), + window: tcpWindow, + addresses: options.Addresses, + verify: options.Remote, + stamps: options.Remote, + conns: make(map[flow]*tcpConn), + sockets: make(map[netip.AddrPort]*udpSocket), } if t, ok := options.Tun.(segmenter); ok && t.Segments() { s.segmenter = t s.window = tcpLargeWindow } - s.udp = newUdpConnectionHandler(s.handle, s.writeUDP) + if options.Remote { + s.window = tcpLargeWindow + } + for i := range s.identification { + s.identification[i].Store(rand.Uint32()) + } + if handler != nil { + s.handle = handler.HandleConnection + s.udp = newUdpConnectionHandler(s.handle, s.writeUDP) + } return s, nil } @@ -85,6 +131,8 @@ func (s *stack) Close() error { for _, c := range s.conns { conns = append(conns, c) } + sockets := s.sockets + s.sockets = nil s.mu.Unlock() for _, c := range conns { c.mu.Lock() @@ -93,6 +141,9 @@ func (s *stack) Close() error { } c.mu.Unlock() } + for _, u := range sockets { + u.queue.close() + } return nil } @@ -111,19 +162,59 @@ func (s *stack) read() { } return } - p, ok := parsePacket(b) - if !ok { - continue - } - switch p.proto { - case protoTCP: - s.handleTCP(p) - case protoUDP: - s.handleUDP(p) - case protoICMPv4, protoICMPv6: - s.handleICMP(p) + s.Input(b) + } +} + +// Input implements Stack. Several tunnels may hand over their packets at once +func (s *stack) Input(b []byte) { + p, ok := parsePacket(b) + if !ok || p.src.Is4In6() || p.dst.Is4In6() || p.src.IsLoopback() || p.dst.IsLoopback() || p.dst.IsUnspecified() || p.src.IsMulticast() { + // nothing comes from many at once, and an address that stands for this machine itself is not one + // that the other side of a tun can ask for. Nor is an IPv6 address that stands for an IPv4 one, which + // would be dialed as that: ::ffff:0.0.0.0 is this machine as well + return + } + if s.verify && b[0]>>4 == 4 && checksum(sum(b[:int(b[0]&0xf)*4], 0)) != 0 { + return + } + // what was put together may be of two packets with one number, its checksum tells + verify := s.verify || p.fragment + if p.fragment { + p, ok = s.fragments.add(p) + } + if !ok || verify && !p.verified() { + return + } + switch p.proto { + case protoTCP: + s.handleTCP(p) + case protoUDP: + s.handleUDP(p) + case protoICMPv4, protoICMPv6: + s.handleICMP(p) + } +} + +// local returns the address that packets to remote are sent from +func (s *stack) local(remote netip.Addr) (netip.Addr, error) { + for _, a := range s.addresses { + if a.Is4() == remote.Is4() { + return a, nil } } + return netip.Addr{}, errors.New("no address to reach ", remote, " from") +} + +// ephemeral returns a port for what is opened from here: the first that free takes, from a random one on +func ephemeral(free func(port uint16) bool) (uint16, bool) { + start := rand.Uint32() + for i := range uint32(1 << 15) { + if port := uint16(1<<15 | (start+i)&(1<<15-1)); free(port) { + return port, true + } + } + return 0, false } func (s *stack) handleTCP(p packet) { @@ -154,7 +245,7 @@ func (s *stack) handleTCP(p packet) { if c != nil && c.input(seg) { return } - if seg.flags&(tcpSyn|tcpAck|tcpRst) != tcpSyn { + if seg.flags&(tcpSyn|tcpAck|tcpRst) != tcpSyn || s.handle == nil { if seg.flags&tcpRst == 0 { s.refuse(f, seg) } @@ -164,7 +255,8 @@ func (s *stack) handleTCP(p packet) { if s.opening.Load() >= tcpOpening { return } - c = newTCPConn(s, f, seg) + c = newTCPConn(s, f) + c.synchronize(seg) s.mu.Lock() if s.closed { s.mu.Unlock() @@ -174,7 +266,7 @@ func (s *stack) handleTCP(p packet) { s.mu.Unlock() s.opening.Add(1) c.mu.Lock() - c.sendSynAck() + c.sendSyn() c.arm(tcpTick) c.mu.Unlock() } @@ -224,51 +316,189 @@ func (s *stack) handleUDP(p packet) { if size < udpHeaderSize || size > len(u) { return } + var socket *udpSocket + if s.handle == nil { + // only for a port that was opened from here + s.mu.Lock() + socket = s.sockets[netip.AddrPortFrom(p.dst, binary.BigEndian.Uint16(u[2:]))] + s.mu.Unlock() + if socket == nil { + return + } + } // the packet waits for its reader, longer than read keeps it b := buf.NewWithSize(int32(size - udpHeaderSize)) b.Write(u[udpHeaderSize:size]) src := net.UDPDestination(net.IPAddress(p.src.AsSlice()), net.Port(binary.BigEndian.Uint16(u))) - dst := net.UDPDestination(net.IPAddress(p.dst.AsSlice()), net.Port(binary.BigEndian.Uint16(u[2:]))) - s.udp.HandlePacket(src, dst, b) + if socket == nil { + s.udp.HandlePacket(src, net.UDPDestination(net.IPAddress(p.dst.AsSlice()), net.Port(binary.BigEndian.Uint16(u[2:]))), b) + } else if b.UDP = &src; !socket.queue.push(b) { + b.Release() + } } // writeUDP writes a packet with the payload from src to dst to the tun func (s *stack) writeUDP(payload []byte, src net.Destination, dst net.Destination) error { srcIP, _ := netip.AddrFromSlice(src.Address.IP()) dstIP, _ := netip.AddrFromSlice(dst.Address.IP()) - srcIP, dstIP = srcIP.Unmap(), dstIP.Unmap() - if !srcIP.IsValid() || srcIP.Is4() != dstIP.Is4() { - return errors.New("failed to write udp packet from ", src, " to ", dst) + return s.sendUDP(payload, netip.AddrPortFrom(srcIP, uint16(src.Port)), netip.AddrPortFrom(dstIP, uint16(dst.Port))) +} + +func (s *stack) sendUDP(payload []byte, src, dst netip.AddrPort) error { + srcIP, dstIP := src.Addr().Unmap(), dst.Addr().Unmap() + if !srcIP.IsValid() || srcIP.Is4() != dstIP.Is4() || len(payload) > math.MaxUint16-udpHeaderSize { + return errors.New("failed to write udp packet of ", len(payload), " bytes from ", src, " to ", dst) } var b [ipv6HeaderSize + udpHeaderSize]byte n := putIPHeader(b[:], protoUDP, srcIP, dstIP) u := b[n : n+udpHeaderSize] - binary.BigEndian.PutUint16(u, uint16(src.Port)) - binary.BigEndian.PutUint16(u[2:], uint16(dst.Port)) + binary.BigEndian.PutUint16(u, src.Port()) + binary.BigEndian.PutUint16(u[2:], dst.Port()) binary.BigEndian.PutUint16(u[4:], uint16(udpHeaderSize+len(payload))) - setIPLength(b[:n], n+udpHeaderSize+len(payload)) check := transportChecksum(b[:n], protoUDP, u, payload) if check == 0 { // zero stands for no checksum check = 0xffff } binary.BigEndian.PutUint16(u[6:], check) - return s.tun.WritePacket(b[:n+udpHeaderSize], payload) + return s.write(b[:n+udpHeaderSize], payload) } -// handleICMP answers an echo request as its destination would +// write completes the IP header of a packet that is in two parts and writes it to the tun, in fragments if it is +// larger than the MTU. A segment of a connection never is, and is not written by this +func (s *stack) write(header, payload []byte) error { + size, last := ipv6HeaderSize, 39 + if header[0]>>4 == 4 { + size, last = ipv4HeaderSize, 19 + } + // the group is chosen by the last bytes of the destination + id := s.identification[(header[last]^header[last-1]<<3)%byte(len(s.identification))].Add(1) + if header[0]>>4 == 4 { + // a link on the way may split it as well, and what is put together there is told apart by its number + binary.BigEndian.PutUint16(header[4:], uint16(id)) + } + if len(header)+len(payload) > s.mtu { + return writeFragments(s.tun, s.mtu, id, header, payload) + } + setIPLength(header[:size], len(header)+len(payload)) + return s.tun.WritePacket(header, payload) +} + +// DialUDP implements Stack +func (s *stack) DialUDP() (net.PacketConn, error) { + u := &udpSocket{stack: s, queue: newPacketQueue(udpQueueLimit)} + s.mu.Lock() + defer s.mu.Unlock() + if s.closed { + return nil, io.ErrClosedPipe + } + port, ok := ephemeral(func(port uint16) bool { + for _, a := range s.addresses { + if s.sockets[netip.AddrPortFrom(a, port)] != nil { + return false + } + } + return true + }) + if !ok || len(s.addresses) == 0 { + return nil, errors.New("no port left on ", s.addresses) + } + u.port = port + for _, a := range s.addresses { + s.sockets[netip.AddrPortFrom(a, port)] = u + } + return u, nil +} + +// udpSocket is a UDP port that was opened from here, on every address of the stack. It is not bound to one +// other side: what anyone sends to it is read from it, with who sent it +type udpSocket struct { + stack *stack + port uint16 + queue *packetQueue +} + +func (u *udpSocket) ReadFrom(p []byte) (int, net.Addr, error) { + b, ok := u.queue.pop() + if !ok { + return 0, nil, gonet.ErrClosed + } + defer b.Release() + return copy(p, b.Bytes()), b.UDP.RawNetAddr(), nil +} + +func (u *udpSocket) WriteTo(p []byte, addr net.Addr) (int, error) { + to, ok := addr.(*net.UDPAddr) + if !ok { + return 0, errors.New("not a udp address: ", addr) + } + local, err := u.stack.local(to.AddrPort().Addr().Unmap()) + if err != nil { + return 0, err + } + if err := u.stack.sendUDP(p, netip.AddrPortFrom(local, u.port), to.AddrPort()); err != nil { + return 0, err + } + return len(p), nil +} + +func (u *udpSocket) Close() error { + u.stack.mu.Lock() + for _, a := range u.stack.addresses { + if local := netip.AddrPortFrom(a, u.port); u.stack.sockets[local] == u { + delete(u.stack.sockets, local) + } + } + u.stack.mu.Unlock() + u.queue.close() + return nil +} + +// LocalAddr implements net.PacketConn with the first address of the stack +func (u *udpSocket) LocalAddr() net.Addr { + return gonet.UDPAddrFromAddrPort(netip.AddrPortFrom(u.stack.addresses[0], u.port)) +} + +func (u *udpSocket) SetDeadline(time.Time) error { + return nil +} + +func (u *udpSocket) SetReadDeadline(time.Time) error { + return nil +} + +func (u *udpSocket) SetWriteDeadline(time.Time) error { + return nil +} + +// handleICMP answers an echo request as its destination would, and takes the word that a packet of a connection +// was too large for a link on its way or found no way at all func (s *stack) handleICMP(p packet) { m := p.payload request, reply := byte(8), byte(0) if p.proto == protoICMPv6 { request, reply = 128, 129 } - if len(m) < 8 || m[0] != request || m[1] != 0 { + if len(m) < 8 { + return + } + switch { + case p.proto == protoICMPv4 && m[0] == 3 && m[1] == 4: + s.undelivered(m[8:], max(int(binary.BigEndian.Uint16(m[6:])), ipv4SmallestMTU)) + return + case p.proto == protoICMPv6 && m[0] == 2: + s.undelivered(m[8:], max(int(binary.BigEndian.Uint32(m[4:])), ipv6SmallestMTU)) + return + case p.proto == protoICMPv4 && m[0] == 3, p.proto == protoICMPv6 && m[0] == 1: + s.undelivered(m[8:], 0) + return + } + if m[0] != request || m[1] != 0 || s.handle == nil && !slices.Contains(s.addresses, p.dst) { + // a stack that takes no connections is one host, not all of them return } var b [ipv6HeaderSize]byte n := putIPHeader(b[:], p.proto, p.dst, p.src) - setIPLength(b[:n], n+len(m)) // the reply is the request with another type m[0], m[2], m[3] = reply, 0, 0 if p.proto == protoICMPv6 { @@ -276,5 +506,39 @@ func (s *stack) handleICMP(p packet) { } else { binary.BigEndian.PutUint16(m[2:], checksum(sum(m, 0))) } - _ = s.tun.WritePacket(b[:n], m) + _ = s.write(b[:n], m) +} + +// undelivered takes the start of a packet that was sent from here and did not get to where it goes. If it did not fit +// a link on its way, which takes packets of mtu bytes, the connection that sent it goes on with smaller segments. +// Without an mtu there is no way at all, and a connection that is being opened is not waited for +func (s *stack) undelivered(b []byte, mtu int) { + var proto byte + var local, remote netip.Addr + switch { + case len(b) >= ipv4HeaderSize && b[0]>>4 == 4: + proto, local, remote = b[9], netip.AddrFrom4([4]byte(b[12:16])), netip.AddrFrom4([4]byte(b[16:20])) + b = b[min(int(b[0]&0xf)*4, len(b)):] + case len(b) >= ipv6HeaderSize && b[0]>>4 == 6: + proto, local, remote = b[6], netip.AddrFrom16([16]byte(b[8:24])), netip.AddrFrom16([16]byte(b[24:40])) + b = b[ipv6HeaderSize:] + } + // of the segment there are the ports and the sequence number at least + if proto != protoTCP || len(b) < 8 { + return + } + s.mu.Lock() + c := s.conns[flow{ + src: netip.AddrPortFrom(remote, binary.BigEndian.Uint16(b[2:])), + dst: netip.AddrPortFrom(local, binary.BigEndian.Uint16(b)), + }] + s.mu.Unlock() + if c == nil { + return + } + if seq := binary.BigEndian.Uint32(b[4:]); mtu != 0 { + c.shrink(seq, mtu) + } else { + c.unreachable(seq) + } } diff --git a/proxy/tun/stack_test.go b/proxy/tun/stack_test.go index a68461481..fb7bbeef7 100644 --- a/proxy/tun/stack_test.go +++ b/proxy/tun/stack_test.go @@ -6,23 +6,16 @@ import ( "crypto/rand" "encoding/binary" "io" + "math" mrand "math/rand/v2" "net/netip" + "strings" "sync" "testing" "time" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/net" - "gvisor.dev/gvisor/pkg/buffer" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/link/channel" - "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" - "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" - gstack "gvisor.dev/gvisor/pkg/tcpip/stack" - "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" ) const testMTU = 1500 @@ -100,11 +93,13 @@ func (l *link) WriteSegment(header []byte, payload [][]byte, mss int) error { } func newTestStack(t *testing.T, l *link, handle func(net.Conn, net.Destination)) *stack { - s := &stack{ctx: context.Background(), tun: l, mtu: testMTU, window: tcpWindow, handle: handle, conns: make(map[flow]*tcpConn)} + s := &stack{ctx: context.Background(), tun: l, mtu: testMTU, window: tcpWindow, handle: handle, conns: make(map[flow]*tcpConn), sockets: make(map[netip.AddrPort]*udpSocket)} if l.segments { s.segmenter, s.window, l.wrong = l, tcpLargeWindow, t.Error } - s.udp = newUdpConnectionHandler(handle, s.writeUDP) + if handle != nil { + s.udp = newUdpConnectionHandler(handle, s.writeUDP) + } s.Start() t.Cleanup(func() { s.Close() @@ -145,123 +140,6 @@ func echo(conn net.Conn, _ net.Destination) { } } -// A real TCP on the other side of a tun that loses packets both ways gets back what it sent. -func TestTCPEcho(t *testing.T) { - for _, test := range []struct { - name string - // one packet of lose is lost - lose int - size int - // the tun takes segments that are larger than its MTU - segments bool - }{ - {"lossless", 0, 16 << 20, false}, - {"lossy", 100, 4 << 20, false}, - {"very lossy", 20, 512 << 10, false}, - {"lossless, large segments", 0, 16 << 20, true}, - {"lossy, large segments", 100, 4 << 20, true}, - } { - t.Run(test.name, func(t *testing.T) { - l := newLink() - l.segments = test.segments - random := mrand.New(mrand.NewPCG(1, uint64(test.lose))) - var mu sync.Mutex - lost := func() bool { - mu.Lock() - defer mu.Unlock() - return test.lose != 0 && random.IntN(test.lose) == 0 - } - - // the system on the other side of the tun - system := gstack.New(gstack.Options{ - NetworkProtocols: []gstack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol}, - TransportProtocols: []gstack.TransportProtocolFactory{tcp.NewProtocol}, - }) - defer system.Destroy() - endpoint := channel.New(512, testMTU, "") - if err := system.CreateNIC(1, endpoint); err != nil { - t.Fatal(err) - } - for _, address := range []tcpip.ProtocolAddress{ - {Protocol: ipv4.ProtocolNumber, AddressWithPrefix: tcpip.AddrFrom4([4]byte{10, 0, 0, 1}).WithPrefix()}, - {Protocol: ipv6.ProtocolNumber, AddressWithPrefix: tcpip.AddrFrom16([16]byte{0xfd, 15: 1}).WithPrefix()}, - } { - if err := system.AddProtocolAddress(1, address, gstack.AddressProperties{}); err != nil { - t.Fatal(err) - } - } - system.SetRouteTable([]tcpip.Route{{Destination: header.IPv4EmptySubnet, NIC: 1}, {Destination: header.IPv6EmptySubnet, NIC: 1}}) - sack := tcpip.TCPSACKEnabled(true) - system.SetTransportProtocolOption(tcp.ProtocolNumber, &sack) - // without RACK: after a tail loss probe of it that is lost too, it has no timer left to send what was lost again - recovery := tcpip.TCPRecovery(0) - system.SetTransportProtocolOption(tcp.ProtocolNumber, &recovery) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - go func() { - for { - packet := endpoint.ReadContext(ctx) - if packet == nil { - return - } - var b []byte - for _, part := range packet.AsSlices() { - b = append(b, part...) - } - packet.DecRef() - if !lost() { - select { - case l.in <- b: - default: - // as a tun does when its queue is full - } - } - } - }() - l.out = func(b []byte) { - if lost() { - return - } - protocol := ipv4.ProtocolNumber - if b[0]>>4 == 6 { - protocol = ipv6.ProtocolNumber - } - packet := gstack.NewPacketBuffer(gstack.PacketBufferOptions{Payload: buffer.MakeWithData(b)}) - endpoint.InjectInbound(protocol, packet) - packet.DecRef() - } - s := newTestStack(t, l, echo) - - for _, to := range []struct { - address tcpip.Address - protocol tcpip.NetworkProtocolNumber - }{ - {tcpip.AddrFrom4([4]byte{10, 0, 0, 2}), ipv4.ProtocolNumber}, - {tcpip.AddrFrom16([16]byte{0xfd, 15: 2}), ipv6.ProtocolNumber}, - } { - conn, err := gonet.DialTCP(system, tcpip.FullAddress{NIC: 1, Addr: to.address, Port: 80}, to.protocol) - if err != nil { - t.Fatal(err) - } - sent := make([]byte, test.size) - rand.Read(sent) - go func() { - conn.Write(sent) - conn.CloseWrite() - }() - conn.SetReadDeadline(time.Now().Add(2 * time.Minute)) - received, err := io.ReadAll(conn) - if err != nil || !bytes.Equal(received, sent) { - t.Fatal("received ", len(received), " of ", len(sent), " bytes, equal: ", bytes.Equal(received, sent), ", error: ", err) - } - conn.Close() - forgotten(t, s) - } - }) - } -} - // peer is the system's end of one connection, with every segment made by the test type peer struct { t *testing.T @@ -271,6 +149,8 @@ type peer struct { // seq is the next that the peer sends, ack the next that it expects seq, ack uint32 window uint16 + // number is that of the last packet that the stack wrote in fragments + number uint32 } func newPeer(t *testing.T, src, dst string) (*peer, *link) { @@ -282,6 +162,11 @@ func newPeer(t *testing.T, src, dst string) (*peer, *link) { // send gives the stack a segment of the peer func (p *peer) send(seq uint32, flags byte, options, payload []byte) { + p.l.in <- p.bytes(seq, flags, options, payload) +} + +// bytes is the packet with a segment of the peer +func (p *peer) bytes(seq uint32, flags byte, options, payload []byte) []byte { var b [ipv6HeaderSize + tcpHeaderSize + 40]byte n := putIPHeader(b[:], protoTCP, p.src.Addr(), p.dst.Addr()) t := b[n : n+tcpHeaderSize+len(options)] @@ -295,7 +180,7 @@ func (p *peer) send(seq uint32, flags byte, options, payload []byte) { copy(t[tcpHeaderSize:], options) setIPLength(b[:n], n+len(t)+len(payload)) binary.BigEndian.PutUint16(t[16:], transportChecksum(b[:n], protoTCP, t, payload)) - p.l.in <- append(b[:n+len(t):n+len(t)], payload...) + return append(b[:n+len(t):n+len(t)], payload...) } // packet takes the next packet that the stack wrote @@ -312,8 +197,26 @@ func (p *peer) packet() []byte { // receive takes the next segment that the stack wrote and checks its addresses and checksums func (p *peer) receive() segment { + p.t.Helper() + return p.segment(p.packet()) +} + +// dialed takes the SYN of a connection that the stack opens to the peer, which knows by it where that comes from. +// It returns the SYN and the packet that it came in +func (p *peer) dialed() (segment, []byte) { p.t.Helper() b := p.packet() + packet, ok := parsePacket(b) + if !ok || packet.proto != protoTCP || len(packet.payload) < tcpHeaderSize { + p.t.Fatal("not a segment: ", b) + } + p.dst = netip.AddrPortFrom(packet.src, binary.BigEndian.Uint16(packet.payload)) + return p.segment(b), b +} + +// segment reads the segment of a packet that the stack wrote and checks its addresses and checksums +func (p *peer) segment(b []byte) segment { + p.t.Helper() size := ipv4HeaderSize if b[0]>>4 == 6 { size = ipv6HeaderSize @@ -342,6 +245,64 @@ func (p *peer) receive() segment { } } +// icmp gives the stack an ICMP message from the peer, as it comes from a router on the way as well +func (p *peer) icmp(m []byte) { + proto := byte(protoICMPv4) + if p.src.Addr().Is6() { + proto = protoICMPv6 + } + var b [ipv6HeaderSize]byte + n := putIPHeader(b[:], proto, p.src.Addr(), p.dst.Addr()) + setIPLength(b[:n], n+len(m)) + p.l.in <- append(b[:n:n], m...) +} + +// tooLarge is the message that packet did not fit a link that takes mtu bytes +func tooLarge(packet []byte, mtu int) []byte { + if packet[0]>>4 == 6 { + return append([]byte{2, 0, 0, 0, 0, 0, byte(mtu >> 8), byte(mtu)}, packet[:ipv6HeaderSize+8]...) + } + // with the start of the packet, as far as the numbers of its segment + return append([]byte{3, 4, 0, 0, 0, 0, byte(mtu >> 8), byte(mtu)}, packet[:int(packet[0]&0xf)*4+8]...) +} + +// unreachable is the message that packet could not be delivered, for the reason that code stands for +func unreachable(packet []byte, code byte) []byte { + if packet[0]>>4 == 6 { + return append([]byte{1, code, 0, 0, 0, 0, 0, 0}, packet[:ipv6HeaderSize+8]...) + } + return append([]byte{3, code, 0, 0, 0, 0, 0, 0}, packet[:ipv4HeaderSize+8]...) +} + +// ping is an echo request without a checksum +func ping(src, dst netip.Addr) []byte { + proto, request := byte(protoICMPv4), byte(8) + if src.Is6() { + proto, request = protoICMPv6, 128 + } + var ip [ipv6HeaderSize]byte + n := putIPHeader(ip[:], proto, src, dst) + setIPLength(ip[:n], n+8) + return append(ip[:n:n], request, 0, 0, 0, 1, 2, 3, 4) +} + +// udpPacket is a UDP packet without a checksum +func udpPacket(src, dst netip.AddrPort, payload []byte) []byte { + var b [ipv6HeaderSize + udpHeaderSize]byte + n := putIPHeader(b[:], protoUDP, src.Addr(), dst.Addr()) + u := b[n : n+udpHeaderSize] + binary.BigEndian.PutUint16(u, src.Port()) + binary.BigEndian.PutUint16(u[2:], dst.Port()) + binary.BigEndian.PutUint16(u[4:], uint16(udpHeaderSize+len(payload))) + setIPLength(b[:n], n+udpHeaderSize+len(payload)) + return append(b[:n+udpHeaderSize:n+udpHeaderSize], payload...) +} + +// udp gives the stack a UDP packet +func (p *peer) udp(src, dst netip.AddrPort, payload string) { + p.l.in <- udpPacket(src, dst, []byte(payload)) +} + // silent fails if the stack writes something within d func (p *peer) silent(d time.Duration) { p.t.Helper() @@ -412,6 +373,8 @@ func TestTCPHandshakeAndClose(t *testing.T) { {"10.0.0.1:1234", "10.0.0.2:80", nil, []byte{2, 4, 5, 0xb4}}, {"10.0.0.1:1234", "10.0.0.2:80", []byte{2, 4, 2, 0, 4, 2, 1, 3, 3, 7, 0, 0}, []byte{2, 4, 5, 0xb4, 1, 1, 4, 2, 1, 3, 3, tcpWindowShift}}, {"[fd00::1]:1234", "[fd00::2]:80", []byte{2, 4, 5, 0xa0, 1, 3, 3, 7}, []byte{2, 4, 5, 0xa0, 1, 3, 3, tcpWindowShift}}, + // the times of RFC 7323 are for a stack of a tunnel + {"10.0.0.1:1234", "10.0.0.2:80", []byte{2, 4, 5, 0xb4, 1, 1, 8, 10, 0, 0, 0, 1, 0, 0, 0, 0}, []byte{2, 4, 5, 0xb4}}, } { conns := make(chan accepted, 1) p, l := newPeer(t, test.src, test.dst) @@ -620,6 +583,246 @@ func TestTCPCongestion(t *testing.T) { segments(fresh(tcpStart + 2)...) } +// A loss is paid for once. Acknowledgments that repeat themselves tell of one only when the system cannot say what +// it has. A segment that was sent again is sent once more when the system still asks for it after an answer had the +// time to come, and the timer waits for that one. When the timer runs out all the same, the way takes no less +// afterwards than after the loss, and it is not given at once what one acknowledgment then says has arrived. +func TestTCPLossAgain(t *testing.T) { + conns := make(chan accepted, 5) + // start opens a connection that says what it has if sack is set, with twenty segments sent and not acknowledged + start := func(src string, sack bool) (*peer, *tcpConn) { + t.Helper() + p, l := newPeer(t, src, "10.0.0.2:80") + newTestStack(t, l, accept(conns)) + options := []byte{2, 4, 0, 100} + if sack { + options = append(options, 4, 2, 0, 0) + } + p.connect(options) + a := take(t, conns) + go a.conn.(buf.Writer).WriteMultiBuffer(buf.MergeBytes(nil, make([]byte, 8000))) + for range tcpStart { + p.receive() + } + p.ack += 100 * tcpStart + p.send(p.seq, tcpAck, nil, nil) + for range 2 * tcpStart { + p.receive() + } + return p, a.conn.(*tcpConn) + } + // has says that the system has what it was sent from from to to, past what it acknowledges + has := func(p *peer, from, to uint32) []byte { + return binary.BigEndian.AppendUint32(binary.BigEndian.AppendUint32([]byte{1, 1, 5, 10}, p.ack+from), p.ack+to) + } + again := func(p *peer, what string) { + t.Helper() + if s := p.receive(); s.seq != p.ack || len(s.payload) != 100 { + t.Fatal(what, ": seq ", s.seq-p.ack, " with ", len(s.payload), " bytes") + } + } + + p, c := start("10.0.0.1:1234", true) + state := func() (cwnd, ssthresh uint32, stalled, retransmits int) { + c.mu.Lock() + defer c.mu.Unlock() + return c.cwnd, c.ssthresh, c.stalled, c.retransmits + } + // the system repeats itself, as it does for a segment that it got twice: it would say what it has if one were lost + for range 3 { + p.send(p.seq, tcpAck, nil, nil) + } + p.silent(tcpTick / 2) + if cwnd, _, _, _ := state(); cwnd != 2*tcpStart*100 { + t.Fatal("the way takes ", cwnd, " after acknowledgments that repeat themselves") + } + + // an answer takes a while, and so does the system before it says that the first of them is missing + c.mu.Lock() + c.srtt, c.rttvar = tcpTick, 0 + c.mu.Unlock() + time.Sleep(tcpTick - tcpTick/5) + p.send(p.seq, tcpAck, has(p, 100, 2000), nil) + again(p, "the lost one") + // the timer waits for the answer from when that was sent again, and half as much is on its way as before + if _, ssthresh, stalled, _ := state(); ssthresh != tcpStart*100 || stalled != 0 { + t.Fatal("after a loss the way takes ", ssthresh, ", the timer has waited ", stalled, " ticks") + } + for range tcpStart - 1 { + p.receive() + } + // it says so again before the answer to what was sent again could be there: nothing + time.Sleep(tcpTick / 2) + p.send(p.seq, tcpAck, has(p, 100, 2000), nil) + p.silent(tcpTick) + // and again after that time: what was sent again is lost as well, and is sent once more + p.send(p.seq, tcpAck, has(p, 100, 2000), nil) + again(p, "the lost one once more") + if _, _, stalled, retransmits := state(); stalled != 0 || retransmits != 0 { + t.Fatal("sent once more by the timer ", retransmits, " times, which has waited ", stalled, " ticks") + } + // that was the one time. The timer waits for the answer to it, then it runs out: the way is tried with one + // segment, and takes no less afterwards than after the loss + time.Sleep(tcpTick + tcpTick/3) + p.send(p.seq, tcpAck, has(p, 100, 2000), nil) + p.silent(tcpTick / 4) + again(p, "the lost one after the timer") + if cwnd, ssthresh, _, retransmits := state(); cwnd != 100 || ssthresh != tcpStart*100 || retransmits != 1 { + t.Fatal("after the timer the way takes ", cwnd, " and then ", ssthresh, ", sent again ", retransmits, " times") + } + // all of it arrives with the one that was sent again: that is not how much the way takes at once + p.ack += 100 * (3*tcpStart - 1) + p.send(p.seq, tcpAck, nil, nil) + for range 3 { + p.receive() + } + p.silent(tcpTick / 2) + if cwnd, _, _, _ := state(); cwnd != 300 { + t.Fatal("the way takes ", cwnd, " after one acknowledgment for all that was lost") + } + // the next loss on the connection: its first segment is sent again once, whatever time the last loss has left + time.Sleep(tcpTick) + p.send(p.seq, tcpAck, has(p, 100, 300), nil) + again(p, "the first of the next loss") + if s := p.receive(); s.seq == p.ack { + t.Fatal("the first of the next loss is sent again twice") + } + p.silent(tcpTick / 2) + + // two are lost. When the first has come, the second is waited for as long as an answer takes + p, c = start("10.0.0.1:1235", true) + c.mu.Lock() + c.srtt, c.rttvar = tcpTick, 0 + c.mu.Unlock() + p.send(p.seq, tcpAck, has(p, 200, 2000), nil) + again(p, "the first of two") + if s := p.receive(); s.seq != p.ack+100 { + t.Fatal("not the second of two: seq ", s.seq-p.ack) + } + for range tcpStart - 2 { + p.receive() + } + time.Sleep(tcpTick + tcpTick/2) + p.ack += 100 + p.send(p.seq, tcpAck, has(p, 100, 1900), nil) + if s := p.receive(); s.seq == p.ack { + t.Fatal("the second of two is sent again with the answer to the first") + } + p.send(p.seq, tcpAck, has(p, 100, 1900), nil) + p.silent(tcpTick / 2) + + // an answer that takes next to no time is given some all the same: the stack may get to what the system said + // before only after it + p, c = start("10.0.0.1:1237", true) + c.mu.Lock() + c.srtt, c.rttvar = time.Millisecond, 0 + c.mu.Unlock() + p.send(p.seq, tcpAck, has(p, 100, 2000), nil) + again(p, "the lost one of a near system") + for range tcpStart - 1 { + p.receive() + } + time.Sleep(tcpTick / 20) + p.send(p.seq, tcpAck, has(p, 100, 2000), nil) + p.silent(tcpTick / 20) + time.Sleep(tcpTick / 4) + p.send(p.seq, tcpAck, has(p, 100, 2000), nil) + again(p, "the lost one of a near system once more") + if _, _, _, retransmits := state(); retransmits != 0 { + t.Fatal("sent once more by the timer") + } + + // a timer that runs out with no loss seen before does halve what the way takes + p, c = start("10.0.0.1:1238", true) + again(p, "the first segment after the timer") + if cwnd, ssthresh, _, retransmits := state(); cwnd != 100 || ssthresh != tcpStart*100 || retransmits != 1 { + t.Fatal("after the timer the way takes ", cwnd, " and then ", ssthresh, ", sent again ", retransmits, " times") + } + + // a system that cannot say what it has: the third time it says the same, the segment is lost + p, c = start("10.0.0.1:1236", false) + for range 2 { + p.send(p.seq, tcpAck, nil, nil) + } + p.silent(tcpTick / 2) + p.send(p.seq, tcpAck, nil, nil) + again(p, "the first segment") + if _, _, _, retransmits := state(); retransmits != 0 { + t.Fatal("sent again by the timer") + } +} + +// while the way to the system is away an open connection sends again every two seconds at most, so that it goes on +// soon after the way is back, while the next segment still waits the doubled time, and it is given up when nothing +// was acknowledged for long enough +func TestTCPWayBack(t *testing.T) { + conns := make(chan accepted, 1) + p, l := newPeer(t, "10.0.0.1:1234", "10.0.0.2:80") + s := newTestStack(t, l, accept(conns)) + p.connect([]byte{2, 4, 0, 100}) + a := take(t, conns) + c := a.conn.(*tcpConn) + + // an answer takes a while: the timer waits as long once, and then no longer than two seconds + c.mu.Lock() + c.retransmitTicks = tcpRetransmitTicksOpen * 4 / 5 + c.mu.Unlock() + start := time.Now() + go a.conn.(buf.Writer).WriteMultiBuffer(buf.MergeBytes(nil, make([]byte, 100))) + p.receive() + for i, most := range []time.Duration{tcpRetransmitTicksOpen * 4 / 5 * tcpTick, tcpRetransmitTicksOpen * tcpTick} { + if s := p.receive(); s.seq != p.ack || len(s.payload) != 100 { + t.Fatal("not sent again: seq ", s.seq-p.ack, " with ", len(s.payload), " bytes") + } + if d := time.Since(start); d > most+most/4 || d < most-2*tcpTick { + t.Fatal("sent again ", i+1, " after ", d, ", not after ", most) + } + start = time.Now() + } + c.mu.Lock() + ticks := c.retransmitTicks + c.mu.Unlock() + if ticks != tcpRetransmitTicksOpen*4/5*4 { + t.Fatal("the next segment waits ", ticks, " ticks") + } + + // the way is back + p.ack += 100 + p.send(p.seq, tcpAck, nil, nil) + p.silent(tcpTick) + c.mu.Lock() + retransmits := c.retransmits + c.mu.Unlock() + if retransmits != 0 { + t.Fatal("sent again ", retransmits, " times after the acknowledgment") + } + + // it is away again, now for longer than the connection waits + c.mu.Lock() + c.retransmitTicks = tcpRetransmitTicks + c.mu.Unlock() + go a.conn.(buf.Writer).WriteMultiBuffer(buf.MergeBytes(nil, make([]byte, 100))) + p.receive() + p.receive() + c.mu.Lock() + c.lostTicks = tcpGiveUpTicks + c.mu.Unlock() + result := make(chan error, 1) + go func() { + _, err := a.conn.Read(make([]byte, 1)) + result <- err + }() + select { + case err := <-result: + if err != errTCPTimeout { + t.Fatal("not given up: ", err) + } + case <-time.After(2*tcpRetransmitTicksOpen*tcpTick + time.Second): + t.Fatal("not given up") + } + forgotten(t, s) +} + // No more is sent than the system takes, and the writer goes on when it takes more. // The FIN that comes with a segment before the one in front of it is taken when that one has come. func TestTCPEndOutOfOrder(t *testing.T) { @@ -842,6 +1045,42 @@ func TestTCPReset(t *testing.T) { forgotten(t, s) } +// A SYN on a connection that is closed opens it anew when it comes from the system of this machine, which has let the +// old one go. Through a tunnel it is answered with what is expected, as anyone who knows the addresses could send it. +func TestTCPSynAfterClose(t *testing.T) { + for _, tunnel := range []bool{false, true} { + conns := make(chan accepted, 2) + p, l := newPeer(t, "10.0.0.1:1234", "10.0.0.2:80") + s := newTestStack(t, l, accept(conns)) + s.verify = tunnel + p.connect(nil) + a := take(t, conns) + a.conn.Close() + if seg := p.receive(); seg.flags != tcpFin|tcpAck || seg.seq != p.ack { + t.Fatal("not a FIN: flags ", seg.flags, ", seq ", seg.seq) + } + p.send(p.seq+1000, tcpSyn, nil, nil) + if !tunnel { + if seg := p.receive(); seg.flags != tcpSyn|tcpAck || seg.ack != p.seq+1001 { + t.Fatal("not opened anew: flags ", seg.flags, ", ack ", seg.ack-p.seq) + } + continue + } + if seg := p.receive(); seg.flags != tcpAck || seg.ack != p.seq { + t.Fatal("not answered with what is expected: flags ", seg.flags, ", ack ", seg.ack-p.seq) + } + p.silent(tcpTick / 2) + // and it is still there: its FIN is acknowledged, and the other side closes as well + p.ack++ + p.send(p.seq, tcpFin|tcpAck, nil, nil) + p.seq++ + if seg := p.receive(); seg.flags != tcpAck || seg.ack != p.seq { + t.Fatal("the FIN is not acknowledged: flags ", seg.flags, ", ack ", seg.ack-p.seq) + } + forgotten(t, s) + } +} + // A UDP packet starts a connection, which answers from any address, and an echo request is answered. func TestUDPAndICMP(t *testing.T) { for _, test := range []struct{ src, dst, other string }{ @@ -909,6 +1148,1091 @@ func TestUDPAndICMP(t *testing.T) { } } +// newDialStack is a stack that opens connections from its addresses and takes none +func newDialStack(t *testing.T, l *link, addresses ...string) *stack { + s := newTestStack(t, l, nil) + for _, a := range addresses { + s.addresses = append(s.addresses, netip.MustParseAddr(a)) + } + return s +} + +// dialed is what DialTCP returns +type dialed struct { + conn net.Conn + err error +} + +func dial(ctx context.Context, s *stack, remote netip.AddrPort) chan dialed { + result := make(chan dialed, 1) + go func() { + conn, err := s.DialTCP(ctx, remote) + result <- dialed{conn, err} + }() + return result +} + +// A connection that is opened from here offers what the stack has in its SYN, takes what the answer says of it, +// and is then like one that the system opened. +func TestTCPDial(t *testing.T) { + for _, test := range []struct { + remote, local string + expected, options []byte + window uint16 + }{ + {"10.0.0.1:80", "10.0.0.2", []byte{2, 4, 5, 0xb4, 1, 1, 4, 2, 1, 3, 3, tcpWindowShift}, []byte{2, 4, 2, 0, 4, 2, 1, 3, 3, 7, 0, 0}, tcpWindow >> tcpWindowShift}, + {"[fd00::1]:80", "fd00::2", []byte{2, 4, 5, 0xa0, 1, 1, 4, 2, 1, 3, 3, tcpWindowShift}, nil, 0xffff}, + } { + p, l := newPeer(t, test.remote, "10.0.0.2:0") + s := newDialStack(t, l, "10.0.0.2", "fd00::2") + result := dial(context.Background(), s, p.src) + syn, _ := p.dialed() + if syn.flags != tcpSyn || syn.window != 0xffff || !bytes.Equal(syn.options, test.expected) || p.dst.Addr().String() != test.local || p.dst.Port() < 1<<15 { + t.Fatal("not a SYN from ", test.local, ": flags ", syn.flags, ", window ", syn.window, ", options ", syn.options, ", from ", p.dst) + } + p.ack = syn.seq + 1 + p.send(p.seq, tcpSyn|tcpAck, test.options, nil) + p.seq++ + d := <-result + if d.err != nil || d.conn.RemoteAddr().String() != test.remote || d.conn.LocalAddr().String() != p.dst.String() { + t.Fatal("connection from ", d.conn.LocalAddr(), " to ", d.conn.RemoteAddr(), ", error: ", d.err) + } + // the time that the answer took is taken, the next that is timed is what is written + c := d.conn.(*tcpConn) + c.mu.Lock() + timed := c.timedAt.IsZero() + c.mu.Unlock() + if !timed { + t.Fatal("the time that the answer took is not taken") + } + // the answer is acknowledged by then, the other side does not wait for a timer to open its end + select { + case b := <-p.sent: + if s := p.segment(b); s.flags != tcpAck || s.seq != p.ack || s.ack != p.seq || s.window != test.window || len(s.options) != 0 { + t.Fatal("the answer is not acknowledged: flags ", s.flags, ", seq ", s.seq, ", ack ", s.ack, ", window ", s.window) + } + default: + t.Fatal("the answer is not acknowledged yet") + } + + write(t, d.conn, []byte("abc")) + packet := p.packet() + if s := p.segment(packet); string(s.payload) != "abc" || s.seq != p.ack || s.flags != tcpAck|tcpPsh { + t.Fatal("wrong segment: seq ", s.seq, ", flags ", s.flags, ", payload ", s.payload) + } + // a router that says it found no way for it does not end a connection that is open + p.icmp(unreachable(packet, 1)) + p.ack += 3 + p.send(p.seq, tcpAck|tcpPsh, nil, []byte("def")) + p.seq += 3 + read(t, d.conn, "def") + if s := p.receive(); s.flags != tcpAck || s.ack != p.seq { + t.Fatal("not acknowledged: flags ", s.flags, ", ack ", s.ack) + } + d.conn.Close() + if s := p.receive(); s.flags != tcpFin|tcpAck || s.seq != p.ack { + t.Fatal("not a FIN: flags ", s.flags, ", seq ", s.seq) + } + p.ack++ + p.send(p.seq, tcpFin|tcpAck, nil, nil) + p.seq++ + if s := p.receive(); s.flags != tcpAck || s.ack != p.seq { + t.Fatal("the FIN is not acknowledged: flags ", s.flags, ", ack ", s.ack) + } + forgotten(t, s) + } +} + +// A SYN that is not answered is sent again. The wait for its answer ends when the other side refuses it, +// and when the one who waits gives up. +func TestTCPDialUnanswered(t *testing.T) { + p, l := newPeer(t, "10.0.0.1:80", "10.0.0.2:0") + s := newDialStack(t, l, "10.0.0.2") + if _, err := s.DialTCP(context.Background(), netip.MustParseAddrPort("[fd00::1]:80")); err == nil { + t.Fatal("a connection from no address") + } + + // an address of IPv4 may come in the form that IPv6 has for it + result := dial(context.Background(), s, netip.AddrPortFrom(netip.AddrFrom16(p.src.Addr().As16()), p.src.Port())) + syn, _ := p.dialed() + // a SYN of the other side is not an answer, and nothing to refuse either + p.send(p.seq, tcpSyn, nil, nil) + if again := p.receive(); again.flags != tcpSyn || again.seq != syn.seq || !bytes.Equal(again.options, syn.options) { + t.Fatal("not the SYN again: flags ", again.flags, ", seq ", again.seq, ", options ", again.options) + } + // what answers another SYN, of a connection that the other side has from before, is told that there is none + p.ack = syn.seq + 100 + p.send(p.seq, tcpSyn|tcpAck, nil, nil) + if s := p.receive(); s.flags != tcpRst || s.seq != p.ack { + t.Fatal("not a reset: flags ", s.flags, ", seq ", s.seq) + } + // and so is a reset for another SYN + p.send(p.seq, tcpRst|tcpAck, nil, nil) + p.ack = syn.seq + 1 + p.send(p.seq, tcpRst|tcpAck, nil, nil) + if d := <-result; d.err != errTCPRefused { + t.Fatal("not refused: ", d.err) + } + forgotten(t, s) + + ctx, cancel := context.WithCancel(context.Background()) + result = dial(ctx, s, p.src) + syn, _ = p.dialed() + cancel() + if d := <-result; d.err != context.Canceled { + t.Fatal("not given up: ", d.err) + } + forgotten(t, s) + // the answer comes too late + p.ack = syn.seq + 1 + p.send(p.seq, tcpSyn|tcpAck, nil, nil) + if s := p.receive(); s.flags != tcpRst || s.seq != p.ack { + t.Fatal("not a reset: flags ", s.flags, ", seq ", s.seq) + } + + // an answer to a SYN that was sent again opens the connection as well, the time that it took is not known then + result = dial(context.Background(), s, p.src) + syn, _ = p.dialed() + p.receive() + p.ack = syn.seq + 1 + p.send(p.seq, tcpSyn|tcpAck, nil, nil) + d := <-result + if d.err != nil { + t.Fatal(d.err) + } + c := d.conn.(*tcpConn) + c.mu.Lock() + srtt, stalled, retransmits := c.srtt, c.stalled, c.retransmits + c.mu.Unlock() + if srtt != 0 || stalled != 0 || retransmits != 0 { + t.Fatal("after the answer: round trip ", srtt, ", ", stalled, " ticks without one, sent again ", retransmits, " times") + } + d.conn.Close() + + // a router on the way that cannot deliver the SYN ends the wait as well, whatever it says is unreachable + for _, test := range []struct { + remote string + code byte + }{ + {"10.0.0.1:80", 1}, + {"10.0.0.1:80", 3}, + {"[fd00::1]:80", 0}, + {"[fd00::1]:80", 4}, + } { + p, l := newPeer(t, test.remote, "10.0.0.2:0") + s := newDialStack(t, l, "10.0.0.2", "fd00::2") + result := dial(context.Background(), s, p.src) + _, packet := p.dialed() + // not when it says so about another SYN + other := bytes.Clone(packet) + other[len(other)-tcpHeaderSize-12+7]++ + p.icmp(unreachable(other, test.code)) + select { + case d := <-result: + t.Fatal("not waited for anymore: ", d.err) + case <-time.After(50 * time.Millisecond): + } + p.icmp(unreachable(packet, test.code)) + if d := <-result; d.err != errTCPUnreachable { + t.Fatal("still waited for: ", d.err) + } + forgotten(t, s) + } +} + +// A UDP port that is opened from here sends to anyone and takes what anyone sends to it. +// A stack that only opens connections takes none. +func TestUDPDial(t *testing.T) { + for _, test := range []struct{ remote, other, local, across, acrossLocal string }{ + {"10.0.0.1:53", "10.0.0.3:5353", "10.0.0.2", "[fd00::9]:9", "fd00::2"}, + {"[fd00::1]:53", "[fd00::3]:5353", "fd00::2", "10.0.0.9:9", "10.0.0.2"}, + } { + p, l := newPeer(t, test.remote, "10.0.0.2:0") + s := newDialStack(t, l, "10.0.0.2", "fd00::2") + conn, err := s.DialUDP() + if err != nil { + t.Fatal(err) + } + local := netip.AddrPortFrom(netip.MustParseAddr(test.local), netip.MustParseAddrPort(conn.LocalAddr().String()).Port()) + if local.Port() < 1<<15 { + t.Fatal("a port on ", local) + } + b := make([]byte, 100) + numbers := map[netip.AddrPort]map[uint16]bool{} + for _, to := range []netip.AddrPort{p.src, netip.MustParseAddrPort(test.other), p.src} { + if numbers[to] == nil { + numbers[to] = map[uint16]bool{} + } + // an address of IPv4 may come in the form that IPv6 has for it + if _, err := conn.WriteTo([]byte("abc"), &net.UDPAddr{IP: net.IP(to.Addr().AsSlice()).To16(), Port: int(to.Port())}); err != nil { + t.Fatal(err) + } + raw := p.packet() + // a link on the way may split it, and tells by the number what to put together + if raw[0]>>4 == 4 && (raw[6] != 0 || checksum(sum(raw[:ipv4HeaderSize], 0)) != 0 || numbers[to][binary.BigEndian.Uint16(raw[4:])]) { + t.Fatal("wrong header of the packet to ", to, ": ", raw[:ipv4HeaderSize]) + } + numbers[to][binary.BigEndian.Uint16(raw[4:])] = true + packet, ok := parsePacket(raw) + u := packet.payload + if !ok || packet.proto != protoUDP || netip.AddrPortFrom(packet.src, binary.BigEndian.Uint16(u)) != local || + netip.AddrPortFrom(packet.dst, binary.BigEndian.Uint16(u[2:])) != to || string(u[udpHeaderSize:]) != "abc" { + t.Fatal("wrong packet to ", to, ": ", packet) + } + var ip [ipv6HeaderSize]byte + if size := putIPHeader(ip[:], protoUDP, packet.src, packet.dst); transportChecksum(ip[:size], protoUDP, u, nil) != 0 { + t.Fatal("wrong checksum of the packet to ", to) + } + // what is for another port is for nobody + p.udp(to, netip.AddrPortFrom(local.Addr(), local.Port()^1), "none") + p.udp(to, local, "defg") + if n, from, err := conn.ReadFrom(b); err != nil || string(b[:n]) != "defg" || from.String() != to.String() { + t.Fatal("read ", string(b[:n]), " from ", from, ", error: ", err) + } + } + // the port is one on the address of the other family as well + across, acrossLocal := netip.MustParseAddrPort(test.across), netip.AddrPortFrom(netip.MustParseAddr(test.acrossLocal), local.Port()) + if _, err := conn.WriteTo([]byte("abc"), &net.UDPAddr{IP: across.Addr().AsSlice(), Port: int(across.Port())}); err != nil { + t.Fatal(err) + } + if packet, ok := parsePacket(p.packet()); !ok || packet.proto != protoUDP || packet.dst != across.Addr() || + netip.AddrPortFrom(packet.src, binary.BigEndian.Uint16(packet.payload)) != acrossLocal { + t.Fatal("wrong packet to ", across, ": ", packet) + } + p.udp(across, acrossLocal, "hij") + if n, from, err := conn.ReadFrom(b); err != nil || string(b[:n]) != "hij" || from.String() != across.String() { + t.Fatal("read ", string(b[:n]), " from ", from, ", error: ", err) + } + + // a stack that takes no connections is one host: it answers a ping of its address and of no other + l.in <- ping(p.src.Addr(), netip.MustParseAddrPort(test.other).Addr()) + l.in <- ping(p.src.Addr(), local.Addr()) + if packet, ok := parsePacket(p.packet()); !ok || packet.src != local.Addr() || packet.dst != p.src.Addr() || packet.payload[0]&^129 != 0 { + t.Fatal("wrong echo reply: ", packet) + } + p.silent(20 * time.Millisecond) + + conn.Close() + p.udp(p.src, local, "late") + if _, _, err := conn.ReadFrom(b); err == nil { + t.Fatal("read from a closed port") + } + s.mu.Lock() + ports := len(s.sockets) + s.mu.Unlock() + if ports != 0 { + t.Fatal("ports left in the stack: ", ports) + } + // a port that is open when the stack is closed has nothing more to read either + left, err := s.DialUDP() + if err != nil { + t.Fatal(err) + } + defer func() { + s.Close() + if _, _, err := left.ReadFrom(b); err == nil { + t.Fatal("read from a port of a closed stack") + } + if _, err := s.DialUDP(); err != io.ErrClosedPipe { + t.Fatal("a port on a closed stack: ", err) + } + if _, err := s.DialTCP(context.Background(), p.src); err != io.ErrClosedPipe { + t.Fatal("a connection from a closed stack: ", err) + } + }() + + p.dst = netip.AddrPortFrom(local.Addr(), 80) + p.send(p.seq, tcpSyn, nil, nil) + if s := p.receive(); s.flags != tcpRst|tcpAck || s.ack != p.seq+1 { + t.Fatal("not a reset: flags ", s.flags, ", ack ", s.ack) + } + forgotten(t, s) + } +} + +// When a link on the way does not take a segment, the connection goes on with smaller ones. +func TestTCPTooLarge(t *testing.T) { + for _, test := range []struct { + src, dst string + mtu, mss int + // the router has the packet with options in its header + options bool + }{ + {"10.0.0.1:1234", "10.0.0.2:80", 1000, 960, false}, + {"10.0.0.1:1234", "10.0.0.2:80", 1000, 960, true}, + {"10.0.0.1:1234", "10.0.0.2:80", 100, ipv4SmallestMTU - ipv4HeaderSize - tcpHeaderSize, false}, + {"[fd00::1]:1234", "[fd00::2]:80", 1300, 1240, false}, + {"[fd00::1]:1234", "[fd00::2]:80", 100, ipv6SmallestMTU - ipv6HeaderSize - tcpHeaderSize, false}, + } { + conns := make(chan accepted, 1) + p, l := newPeer(t, test.src, test.dst) + newTestStack(t, l, accept(conns)) + p.connect(nil) + a := take(t, conns) + + sent := make([]byte, 3000) + rand.Read(sent) + write(t, a.conn, sent) + packet := p.packet() + first := p.segment(packet) + for size := len(first.payload); size < len(sent); { + size += len(p.receive().payload) + } + // said about something that was not sent, it changes nothing + other := bytes.Clone(packet) + binary.BigEndian.PutUint32(other[len(other)-len(first.payload)-tcpHeaderSize+4:], first.seq-1) + p.icmp(tooLarge(other, test.mtu)) + // nor is a message read that is cut short, or that has less of the packet than the numbers of its segment + message := tooLarge(packet, test.mtu) + for _, size := range []int{4, 8, 16, len(message) - 1} { + p.icmp(message[:size]) + } + p.silent(50 * time.Millisecond) + + if test.options { + message = append(append(append(message[:8:8], packet[:ipv4HeaderSize]...), 1, 1, 1, 1), packet[ipv4HeaderSize:ipv4HeaderSize+8]...) + message[8]++ + } + // none of what is on its way fits. Its first segment comes again at once and the others as that is + // acknowledged, so that one who says so again and again is not sent all of it every time + p.icmp(message) + var received []byte + for len(received) < len(sent) { + s := p.receive() + if len(s.payload) > test.mss || s.seq != p.ack { + t.Fatal("a segment of ", len(s.payload), " bytes at ", s.seq-p.ack, " past what is acknowledged, expected ", test.mss, " at most at 0") + } + if len(received) == 0 { + p.silent(50 * time.Millisecond) + // and its answer is waited for from when it was sent again + c := a.conn.(*tcpConn) + c.mu.Lock() + waited := !c.headAt.IsZero() + c.mu.Unlock() + if !waited { + t.Fatal("the time when the first segment was sent again is not kept") + } + } + received = append(received, s.payload...) + p.ack += uint32(len(s.payload)) + p.send(p.seq, tcpAck, nil, nil) + } + if !bytes.Equal(received, sent) { + t.Fatal("sent something else again") + } + // and what is written from now on is as small + write(t, a.conn, sent) + if s := p.receive(); len(s.payload) != test.mss { + t.Fatal("a segment of ", len(s.payload), " bytes, expected ", test.mss) + } + } +} + +// With the times of RFC 7323 every segment carries the time of this side and the last one of the other side that +// came where the stream goes on. A segment has room for less then, and what is held is told in one block less. +func TestTCPStamps(t *testing.T) { + stamps := func(s segment) (value, echo uint32, rest []byte) { + t.Helper() + if len(s.options) < tcpStamps || !bytes.Equal(s.options[:4], []byte{1, 1, 8, 10}) { + t.Fatal("no times in the options ", s.options) + } + return binary.BigEndian.Uint32(s.options[4:]), binary.BigEndian.Uint32(s.options[8:]), s.options[tcpStamps:] + } + stamped := func(value, echo uint32, options ...byte) []byte { + return append(binary.BigEndian.AppendUint32(binary.BigEndian.AppendUint32([]byte{1, 1, 8, 10}, value), echo), options...) + } + const room = testMTU - ipv4HeaderSize - tcpHeaderSize + + conns := make(chan accepted, 1) + p, l := newPeer(t, "10.0.0.1:1234", "10.0.0.2:80") + newTestStack(t, l, accept(conns)).stamps = true + p.send(p.seq, tcpSyn, stamped(100, 0, 2, 4, 5, 0xb4, 4, 2, 0, 0), nil) + p.seq++ + s := p.receive() + own, echo, rest := stamps(s) + if s.flags != tcpSyn|tcpAck || s.ack != p.seq || echo != 100 || !bytes.Equal(rest, []byte{2, 4, 5, 0xb4, 1, 1, 4, 2}) { + t.Fatal("not an answer with the time of the SYN: flags ", s.flags, ", options ", s.options) + } + p.ack = s.seq + 1 + p.send(p.seq, tcpAck, stamped(101, own), nil) + a := take(t, conns) + + // a segment has that much less room, also when a link on the way takes less. The time of this side goes on + time.Sleep(2 * time.Millisecond) + write(t, a.conn, make([]byte, testMTU)) + packet := p.packet() + s = p.segment(packet) + if value, echo, rest := stamps(s); len(s.payload) != room-tcpStamps || echo != 101 || int32(value-own) <= 0 || len(rest) != 0 { + t.Fatal("a segment of ", len(s.payload), " bytes with options ", s.options) + } + if s = p.receive(); len(s.payload) != testMTU-room+tcpStamps { + t.Fatal("the rest is ", len(s.payload), " bytes") + } + p.icmp(tooLarge(packet, 1000)) + if s = p.receive(); len(s.payload) != 1000-ipv4HeaderSize-tcpHeaderSize-tcpStamps || s.seq != p.ack { + t.Fatal("a segment of ", len(s.payload), " bytes at ", s.seq-p.ack, " for a link that takes 1000") + } + p.ack += testMTU + p.send(p.seq, tcpAck, stamped(102, own), nil) + + // the time that is sent back is not the one of a segment that waits for another + p.send(p.seq+3, tcpAck, stamped(110, own), []byte("def")) + s = p.receive() + if _, echo, rest := stamps(s); s.ack != p.seq || echo != 102 || !bytes.Equal(rest, binary.BigEndian.AppendUint32(binary.BigEndian.AppendUint32([]byte{1, 1, 5, 10}, p.seq+3), p.seq+6)) { + t.Fatal("ack ", s.ack-p.seq, " past what was sent, options ", s.options) + } + // nor an older one that comes late + p.send(p.seq, tcpAck, stamped(90, own), []byte("abc")) + p.seq += 6 + s = p.receive() + if _, echo, rest := stamps(s); s.ack != p.seq || echo != 102 || len(rest) != 0 { + t.Fatal("ack ", s.ack-p.seq, " past what was sent, options ", s.options) + } + read(t, a.conn, "abcdef") + p.send(p.seq, tcpAck, stamped(111, own), []byte("g")) + p.seq++ + read(t, a.conn, "g") + s = p.receive() + if _, echo, _ := stamps(s); s.ack != p.seq || echo != 111 { + t.Fatal("ack ", s.ack-p.seq, " past what was sent, options ", s.options) + } + // the one that fills a hole is acknowledged at once, with its own time + p.send(p.seq+3, tcpAck, stamped(120, own), []byte("jkl")) + p.receive() + p.send(p.seq, tcpAck, stamped(115, own), []byte("hij")) + p.seq += 6 + s = p.receive() + if _, echo, _ := stamps(s); s.ack != p.seq || echo != 115 { + t.Fatal("ack ", s.ack-p.seq, " past what was sent, options ", s.options) + } + // of two that one acknowledgment is for, it is the time of the first + p.silent(2 * tcpTick) + p.send(p.seq, tcpAck, stamped(130, own), []byte("m")) + p.send(p.seq+1, tcpAck, stamped(131, own), []byte("n")) + p.seq += 2 + s = p.receive() + if _, echo, _ := stamps(s); s.ack != p.seq || echo != 130 { + t.Fatal("ack ", s.ack-p.seq, " past what was sent, options ", s.options) + } + // times that are not whole are none + p.send(p.seq, tcpAck, []byte{8, 6, 0, 0, 2, 0, 0, 0}, []byte("o")) + p.seq++ + s = p.receive() + if _, echo, _ := stamps(s); s.ack != p.seq || echo != 130 { + t.Fatal("ack ", s.ack-p.seq, " past what was sent, options ", s.options) + } + // three blocks of what is held fit next to the times + for i := range uint32(4) { + p.send(p.seq+3+6*i, tcpAck, stamped(132, own), []byte("xyz")) + s = p.receive() + } + last, _, rest := stamps(s) + if len(rest) != 4+3*8 { + t.Fatal("what is held is told with ", s.options) + } + // what is sent while a segment is on its way to the tun has the time of that one, whichever gets there first + c := a.conn.(*tcpConn) + c.mu.Lock() + c.sending = true + time.Sleep(2 * time.Millisecond) + c.send(c.sndNxt, tcpAck, nil, nil) + c.sending = false + c.mu.Unlock() + if value, _, _ := stamps(p.receive()); value != last { + t.Fatal("a time ", value-last, " ms after the one of the segment that is being written") + } + + // a SYN with times that are not whole does not offer them + q, l := newPeer(t, "10.0.0.1:1235", "10.0.0.2:80") + newTestStack(t, l, accept(conns)).stamps = true + if options := q.connect([]byte{8, 6, 0, 0, 2, 0, 0, 0}); !bytes.Equal(options, []byte{2, 4, 5, 0xb4}) { + t.Fatal("times taken from a SYN that has none: ", options) + } + + // a connection that is opened from here offers the times, and has them if the answer does + bases := []uint32{own} + for _, answered := range []bool{true, false} { + p, l := newPeer(t, "10.0.0.1:80", "10.0.0.2:0") + dialing := newDialStack(t, l, "10.0.0.2") + dialing.stamps = true + result := dial(context.Background(), dialing, p.src) + syn, _ := p.dialed() + own, echo, rest := stamps(syn) + if syn.flags != tcpSyn || echo != 0 || !bytes.Equal(rest, []byte{2, 4, 5, 0xb4, 1, 1, 4, 2, 1, 3, 3, tcpWindowShift}) { + t.Fatal("not a SYN that offers the times: flags ", syn.flags, ", options ", syn.options) + } + // every connection starts its time somewhere else + for _, other := range bases { + if apart := int32(own - other); apart > -5000 && apart < 5000 { + t.Fatal("the time of a connection is ", apart, " ms from the one of another") + } + } + bases = append(bases, own) + p.ack = syn.seq + 1 + var options []byte + if answered { + options = stamped(200, own) + } + p.send(p.seq, tcpSyn|tcpAck, options, nil) + p.seq++ + d := <-result + if d.err != nil { + t.Fatal(d.err) + } + ack := p.receive() + write(t, d.conn, make([]byte, testMTU)) + s := p.receive() + if answered { + if _, echo, _ := stamps(ack); echo != 200 || len(s.payload) != room-tcpStamps { + t.Fatal("options ", ack.options, ", then a segment of ", len(s.payload), " bytes") + } + } else if len(ack.options) != 0 || len(s.options) != 0 || len(s.payload) != room { + t.Fatal("options ", ack.options, ", then a segment of ", len(s.payload), " bytes with ", s.options) + } + } +} + +// Two stacks, one that opens connections and one that takes them, with a link between them that loses packets +// both ways: what is sent comes back. +func TestTCPEcho(t *testing.T) { + for _, test := range []struct { + name string + // one packet of lose is lost + lose int + size int + // the tun of the one that takes the connections takes segments that are larger than its MTU + segments bool + // the segments carry the times of both sides + stamps bool + }{ + {"lossless", 0, 16 << 20, false, false}, + {"lossy", 100, 4 << 20, false, false}, + {"very lossy", 20, 512 << 10, false, false}, + {"lossless, large segments", 0, 16 << 20, true, false}, + {"lossy, large segments", 100, 4 << 20, true, false}, + {"lossy, times", 100, 4 << 20, false, true}, + } { + t.Run(test.name, func(t *testing.T) { + random := mrand.New(mrand.NewPCG(2, uint64(test.lose))) + var mu sync.Mutex + to := func(l *link) func(b []byte) { + return func(b []byte) { + mu.Lock() + lost := test.lose != 0 && random.IntN(test.lose) == 0 + mu.Unlock() + if !lost { + select { + case l.in <- b: + default: + // as a tun does when its queue is full + } + } + } + } + near, far := newLink(), newLink() + near.out, far.out = to(far), to(near) + far.segments = test.segments + server := newTestStack(t, far, echo) + client := newDialStack(t, near, "10.0.0.1", "fd00::1") + server.stamps, client.stamps = test.stamps, test.stamps + + for _, remote := range []string{"10.0.0.2:80", "[fd00::2]:80"} { + conn, err := client.DialTCP(context.Background(), netip.MustParseAddrPort(remote)) + if err != nil { + t.Fatal(err) + } + sent := make([]byte, test.size) + rand.Read(sent) + go conn.Write(sent) + received := make([]byte, len(sent)) + done := make(chan error, 1) + go func() { + _, err := io.ReadFull(conn, received) + done <- err + }() + select { + case err := <-done: + if err != nil || !bytes.Equal(received, sent) { + t.Fatal("received something else than what was sent, error: ", err) + } + case <-time.After(2 * time.Minute): + t.Fatal("what was sent did not come back") + } + conn.Close() + forgotten(t, client) + forgotten(t, server) + } + }) + } +} + +// split cuts a packet as a link that takes less does: into fragments with size bytes of what follows the IP header +func split(packet []byte, size int, id uint32) [][]byte { + n := ipv4HeaderSize + if packet[0]>>4 == 6 { + n = ipv6HeaderSize + } + var fragments [][]byte + for data, offset := packet[n:], 0; offset < len(data); offset += size { + end := min(offset+size, len(data)) + f := bytes.Clone(packet[:n]) + if n == ipv6HeaderSize { + at := uint16(offset) + if end < len(data) { + at |= 1 + } + f[6] = protoFragment + f = binary.BigEndian.AppendUint32(binary.BigEndian.AppendUint16(append(f, packet[6], 0), at), id) + } else { + at := uint16(offset / 8) + if end < len(data) { + at |= 0x2000 + } + binary.BigEndian.PutUint16(f[4:], uint16(id)) + binary.BigEndian.PutUint16(f[6:], at) + } + f = append(f, data[offset:end]...) + setIPLength(f[:n], len(f)) + fragments = append(fragments, f) + } + return fragments +} + +// joined takes the fragments of a packet that the stack wrote, checks them, and returns the packet +func (p *peer) joined() []byte { + p.t.Helper() + var header, data []byte + number := p.number + for { + b := p.packet() + n, at, id, more := ipv4HeaderSize, 0, uint32(0), false + if b[0]>>4 == 6 { + n = ipv6HeaderSize + if b[6] != protoFragment || int(binary.BigEndian.Uint16(b[4:])) != len(b)-n { + p.t.Fatal("not a fragment: ", b[:n]) + } + f := b[n : n+fragmentHeaderSize] + header = bytes.Clone(b[:n]) + header[6] = f[0] + at, more, id = int(binary.BigEndian.Uint16(f[2:])&^7), f[3]&1 != 0, binary.BigEndian.Uint32(f[4:]) + n += fragmentHeaderSize + } else { + if checksum(sum(b[:n], 0)) != 0 || int(binary.BigEndian.Uint16(b[2:])) != len(b) { + p.t.Fatal("wrong header of a fragment: ", b[:n]) + } + f := binary.BigEndian.Uint16(b[6:]) + header = bytes.Clone(b[:n]) + header[6], header[7] = 0, 0 + at, more, id = int(f&0x1fff)*8, f&0x2000 != 0, uint32(binary.BigEndian.Uint16(b[4:])) + } + if len(data) == 0 { + if id == number { + p.t.Fatal("the number of the last packet again: ", id) + } + number, p.number = id, id + } + if len(b) > testMTU || at != len(data) || id != number || more && (len(b)-n)%8 != 0 || more && len(b)+8 <= testMTU { + p.t.Fatal("a fragment of ", len(b), " bytes at ", at, " with the number ", id, ", expected at ", len(data), " with ", number) + } + data = append(data, b[n:]...) + if !more { + setIPLength(header, len(header)+len(data)) + return append(header, data...) + } + } +} + +// A packet that is larger than a link on its way comes in fragments, in any order, and leaves in fragments. +func TestFragments(t *testing.T) { + for _, test := range []struct{ src, dst string }{ + {"10.0.0.1:1234", "10.0.0.2:53"}, + {"[fd00::1]:1234", "[fd00::2]:53"}, + } { + conns := make(chan accepted, 1) + p, l := newPeer(t, test.src, test.dst) + s := newTestStack(t, l, accept(conns)) + sent := make([]byte, 3000) + rand.Read(sent) + receive := func(a accepted, expected []byte) { + t.Helper() + mb, err := a.conn.(buf.Reader).ReadMultiBuffer() + if err != nil || len(mb) != 1 || !bytes.Equal(mb[0].Bytes(), expected) { + t.Fatal("read ", mb.Len(), " bytes, expected the ", len(expected), " that were sent, error: ", err) + } + buf.ReleaseMulti(mb) + } + + whole := summed(udpPacket(p.src, p.dst, sent)) + parts := split(whole, 1024, 7) + for _, i := range []int{2, 0, 0, 1} { + l.in <- parts[i] + } + a := take(t, conns) + receive(a, sent) + + if err := a.conn.(buf.Writer).WriteMultiBuffer(buf.MultiBuffer{buf.FromBytes(sent)}); err != nil { + t.Fatal(err) + } + packet, ok := parsePacket(p.joined()) + u := packet.payload + if !ok || packet.fragment || packet.proto != protoUDP || netip.AddrPortFrom(packet.src, binary.BigEndian.Uint16(u)) != p.dst || + netip.AddrPortFrom(packet.dst, binary.BigEndian.Uint16(u[2:])) != p.src || int(binary.BigEndian.Uint16(u[4:])) != len(u) || !bytes.Equal(u[udpHeaderSize:], sent) { + t.Fatal("wrong packet: ", packet.src, packet.dst, packet.proto, len(u)) + } + var ip [ipv6HeaderSize]byte + if size := putIPHeader(ip[:], protoUDP, packet.src, packet.dst); transportChecksum(ip[:size], protoUDP, u, nil) != 0 { + t.Fatal("wrong checksum of the packet") + } + + proto, request, reply := byte(protoICMPv4), byte(8), byte(0) + if p.src.Addr().Is6() { + proto, request, reply = protoICMPv6, 128, 129 + } + n := putIPHeader(ip[:], proto, p.src.Addr(), p.dst.Addr()) + m := append([]byte{request, 0, 0, 0, 1, 2, 3, 4}, sent...) + setIPLength(ip[:n], n+len(m)) + for _, part := range split(summed(append(ip[:n:n], m...)), 1480, 8) { + l.in <- part + } + // the fragments of two packets with one number are not one packet: what is put together has its checksum looked at + mixed := split(summed(udpPacket(p.src, p.dst, bytes.Repeat([]byte{1}, 2000))), 1024, 11) + l.in <- mixed[0] + l.in <- split(summed(udpPacket(p.src, p.dst, bytes.Repeat([]byte{2}, 2000))), 1024, 11)[1] + packet, ok = parsePacket(p.joined()) + m = packet.payload + if !ok || packet.proto != proto || packet.src != p.dst.Addr() || packet.dst != p.src.Addr() || m[0] != reply || !bytes.Equal(m[8:], sent) { + t.Fatal("wrong echo reply: ", packet.src, packet.dst, packet.proto, len(m)) + } + size := putIPHeader(ip[:], proto, packet.src, packet.dst) + if proto == protoICMPv4 && checksum(sum(m, 0)) != 0 || proto == protoICMPv6 && transportChecksum(ip[:size], proto, m, nil) != 0 { + t.Fatal("wrong checksum of the echo reply") + } + + // packets whose other fragments do not come are only so many, and do not keep a later one from coming + for id := range uint32(fragmentsMost) { + l.in <- split(whole, 1024, 100+id)[0] + } + for _, part := range split(whole, 1024, 9) { + l.in <- part + } + receive(a, sent) + s.fragments.mu.Lock() + left := len(s.fragments.packets) + s.fragments.mu.Unlock() + if left != fragmentsMost-1 { + t.Fatal("packets that are still waited for: ", left) + } + } +} + +// An MTU that has no room for a fragment is one that nothing larger than it is written to. +func TestFragmentsWithoutRoom(t *testing.T) { + for _, test := range []struct { + src, dst string + mtu int + }{ + {"10.0.0.2:53", "10.0.0.1:1234", ipv4HeaderSize + 7}, + {"[fd00::2]:53", "[fd00::1]:1234", ipv6HeaderSize + fragmentHeaderSize + 7}, + } { + l := newLink() + l.out = func([]byte) { t.Error("a packet with an MTU of ", test.mtu) } + s := &stack{tun: l, mtu: test.mtu} + if err := s.sendUDP(make([]byte, 100), netip.MustParseAddrPort(test.src), netip.MustParseAddrPort(test.dst)); err == nil { + t.Fatal("no error with an MTU of ", test.mtu) + } + } +} + +// summed fills in the checksum of a UDP packet or an ICMP message +func summed(b []byte) []byte { + n, proto := ipv4HeaderSize, b[9] + if b[0]>>4 == 6 { + n, proto = ipv6HeaderSize, b[6] + } + at := 2 + if proto == protoUDP { + at = 6 + } + m := b[n:] + m[at], m[at+1] = 0, 0 + if proto == protoICMPv4 { + binary.BigEndian.PutUint16(m[at:], checksum(sum(m, 0))) + } else { + binary.BigEndian.PutUint16(m[at:], transportChecksum(b[:n], proto, m, nil)) + } + return b +} + +// What comes through a tunnel has come a long way and is not taken with a wrong checksum. +// And nothing that is from or for this machine itself is taken from any tun. +func TestChecksumsAndLoopback(t *testing.T) { + for _, test := range []struct { + src, dst, loopback string + read []string + }{ + {"10.0.0.1:1234", "10.0.0.2:80", "127.0.0.1:80", []string{"zero", "abc", "pad"}}, + {"[fd00::1]:1234", "[fd00::2]:80", "[::1]:80", []string{"abc", "pad"}}, + } { + conns := make(chan accepted, 2) + p, l := newPeer(t, test.src, test.dst) + s := newTestStack(t, l, accept(conns)) + s.verify = true + damaged := func(b []byte) []byte { + b = bytes.Clone(b) + b[len(b)-1] ^= 1 + return b + } + + syn := p.bytes(p.seq, tcpSyn, nil, []byte{0}) + l.in <- damaged(syn) + if syn[0]>>4 == 4 { + header := bytes.Clone(syn) + header[8]-- + l.in <- header + } + p.silent(50 * time.Millisecond) + l.in <- syn + if s := p.receive(); s.flags != tcpSyn|tcpAck || s.ack != p.seq+1 { + t.Fatal("not an answer to the SYN: flags ", s.flags, ", ack ", s.ack) + } + p.send(p.seq+1, tcpRst, nil, nil) + forgotten(t, s) + if syn[0]>>4 == 4 { + // the options of a header are a part of it + options := append(append(bytes.Clone(syn[:ipv4HeaderSize]), 1, 1, 1, 1), syn[ipv4HeaderSize:]...) + options[0]++ + binary.BigEndian.PutUint16(options[2:], uint16(len(options))) + options[10], options[11] = 0, 0 + binary.BigEndian.PutUint16(options[10:], checksum(sum(options[:ipv4HeaderSize+4], 0))) + wrong := bytes.Clone(options) + wrong[ipv4HeaderSize] = 0 + l.in <- wrong + p.silent(50 * time.Millisecond) + l.in <- options + if s := p.receive(); s.flags != tcpSyn|tcpAck || s.ack != p.seq+1 { + t.Fatal("not an answer to the SYN: flags ", s.flags, ", ack ", s.ack) + } + p.send(p.seq+1, tcpRst, nil, nil) + forgotten(t, s) + } + + // a UDP packet may go without a checksum, but not an IPv6 one + l.in <- udpPacket(p.src, p.dst, []byte("zero")) + l.in <- damaged(summed(udpPacket(p.src, p.dst, []byte("abc")))) + l.in <- summed(udpPacket(p.src, p.dst, []byte("abc"))) + // a packet may be longer than what it carries, and the checksum is of that + padded := append(summed(udpPacket(p.src, p.dst, []byte("pad"))), 0, 0, 0, 0) + setIPLength(padded[:len(padded)-4-3-udpHeaderSize], len(padded)) + l.in <- padded + a := take(t, conns) + for _, expected := range test.read { + read(t, a.conn, expected) + } + + proto, request := byte(protoICMPv4), byte(8) + if p.src.Addr().Is6() { + proto, request = protoICMPv6, 128 + } + var ip [ipv6HeaderSize]byte + n := putIPHeader(ip[:], proto, p.src.Addr(), p.dst.Addr()) + setIPLength(ip[:n], n+12) + echo := summed(append(ip[:n:n], request, 0, 0, 0, 1, 2, 3, 4, 'a', 'b', 'c', 'd')) + l.in <- damaged(echo) + p.silent(50 * time.Millisecond) + l.in <- echo + if packet, ok := parsePacket(p.packet()); !ok || packet.proto != proto || string(packet.payload[4:]) != "\x01\x02\x03\x04abcd" { + t.Fatal("wrong echo reply: ", packet) + } + + // to this machine itself, from it, to no address, and from many at once + loopback := netip.MustParseAddrPort(test.loopback) + none := netip.AddrPortFrom(netip.IPv4Unspecified(), 80) + if p.src.Addr().Is6() { + none = netip.AddrPortFrom(netip.IPv6Unspecified(), 80) + } + l.in <- summed(udpPacket(netip.AddrPortFrom(p.src.Addr(), 1236), none, []byte("abc"))) + many := netip.AddrPortFrom(netip.MustParseAddr("224.0.0.1"), 1234) + if p.src.Addr().Is6() { + many = netip.AddrPortFrom(netip.MustParseAddr("ff02::1"), 1234) + } + for _, from := range []netip.AddrPort{loopback, many} { + l.in <- summed(udpPacket(from, netip.AddrPortFrom(p.dst.Addr(), 53), []byte("abc"))) + } + l.in <- summed(udpPacket(netip.AddrPortFrom(p.src.Addr(), 1235), loopback, []byte("abc"))) + if p.src.Addr().Is6() { + // nor to or from an IPv6 address that stands for an IPv4 one: ::ffff:0.0.0.0 would be dialed as 0.0.0.0 + for _, to := range []string{"[::ffff:0.0.0.0]:53", "[::ffff:127.0.0.1]:53", "[::ffff:10.0.0.2]:53"} { + l.in <- summed(udpPacket(netip.AddrPortFrom(p.src.Addr(), 1237), netip.MustParseAddrPort(to), []byte("abc"))) + } + l.in <- summed(udpPacket(netip.MustParseAddrPort("[::ffff:10.0.0.1]:1238"), netip.AddrPortFrom(p.dst.Addr(), 53), []byte("abc"))) + dst := p.dst + p.dst = netip.MustParseAddrPort("[::ffff:0.0.0.0]:80") + p.send(p.seq, tcpSyn, nil, nil) + p.dst = dst + } + p.dst = loopback + p.send(p.seq, tcpSyn, nil, nil) + p.silent(50 * time.Millisecond) + select { + case a := <-conns: + t.Fatal("a connection from ", a.conn.RemoteAddr(), " to ", a.dest) + default: + } + } +} + +// handler is a ConnectionHandler that is a function +type handler func(net.Conn, net.Destination) + +func (h handler) HandleConnection(conn net.Conn, dest net.Destination) { h(conn, dest) } + +// What a stack is like depends on what it is made with. +func TestNewStack(t *testing.T) { + l := newLink() + made, _ := NewStack(context.Background(), StackOptions{Tun: l, MTU: testMTU}, nil) + if s := made.(*stack); s.mtu != testMTU || s.window != tcpWindow || s.verify || s.stamps || s.segmenter != nil || s.handle != nil || s.udp != nil { + t.Fatal("wrong stack for a tun of this machine that only opens connections: ", s) + } + l.segments = true + made, _ = NewStack(context.Background(), StackOptions{Tun: l, MTU: testMTU}, handler(echo)) + if s := made.(*stack); s.window != tcpLargeWindow || s.verify || s.stamps || s.segmenter == nil || s.handle == nil || s.udp == nil { + t.Fatal("wrong stack for a tun that takes large segments: ", s) + } + l.segments = false + addresses := []netip.Addr{netip.MustParseAddr("10.0.0.2")} + made, _ = NewStack(context.Background(), StackOptions{Tun: l, MTU: testMTU, Addresses: addresses, Remote: true}, nil) + if s := made.(*stack); s.window != tcpLargeWindow || !s.verify || !s.stamps || s.segmenter != nil || len(s.addresses) != 1 { + t.Fatal("wrong stack for a tunnel to another system: ", s) + } +} + +// What is opened from here gets a port that nothing else of it is on, as long as there is one. +func TestPorts(t *testing.T) { + p, l := newPeer(t, "10.0.0.1:80", "10.0.0.2:0") + s := newDialStack(t, l, "10.0.0.2", "fd00::2") + // all but one are taken, UDP ports on one address or the other + const free = 1<<15 + 1234 + udp, tcp := &udpSocket{queue: newPacketQueue(1)}, new(tcpConn) + s.mu.Lock() + for port := 1 << 15; port < 1<<16; port++ { + if port != free { + s.sockets[netip.AddrPortFrom(s.addresses[port&1], uint16(port))] = udp + s.conns[flow{src: p.src, dst: netip.AddrPortFrom(s.addresses[0], uint16(port))}] = tcp + } + } + s.mu.Unlock() + defer func() { + s.mu.Lock() + for f, c := range s.conns { + if c == tcp { + delete(s.conns, f) + } + } + s.mu.Unlock() + }() + + conn, err := s.DialUDP() + if err != nil || netip.MustParseAddrPort(conn.LocalAddr().String()).Port() != free { + t.Fatal("a port that is taken: ", conn, ", error: ", err) + } + if _, err := s.DialUDP(); err == nil { + t.Fatal("a port although none is left") + } + ctx, cancel := context.WithCancel(context.Background()) + result := dial(ctx, s, p.src) + if p.dialed(); p.dst.Port() != free { + t.Fatal("a connection from a port that is taken: ", p.dst) + } + if _, err := s.DialTCP(context.Background(), p.src); err == nil { + t.Fatal("a connection although no port is left") + } + // only for the same other side + other := dial(ctx, s, netip.MustParseAddrPort("10.0.0.1:81")) + p.packet() + cancel() + <-result + <-other +} + +// Fragments that do not fit each other are of no packet, and those of a packet are not put together +// with those of another. +func TestFragmentsThatDoNotFit(t *testing.T) { + src, dst := netip.MustParseAddr("10.0.0.1"), netip.MustParseAddr("10.0.0.2") + fragment := func(from netip.Addr, proto byte, id uint32, offset, size int, more bool, fill byte) packet { + return packet{proto: proto, src: from, dst: dst, payload: bytes.Repeat([]byte{fill}, size), fragment: true, more: more, offset: offset, id: id} + } + udp := func(offset, size int, more bool, fill byte) packet { + return fragment(src, protoUDP, 1, offset, size, more, fill) + } + waited := func(r *reassembly) int { + r.mu.Lock() + defer r.mu.Unlock() + return len(r.packets) + } + // add takes fragments, the last of which makes a packet or not + add := func(r *reassembly, fragments ...packet) (packet, bool) { + for _, f := range fragments[:len(fragments)-1] { + if p, ok := r.add(f); ok { + t.Fatal("a packet before its last fragment: ", p) + } + } + return r.add(fragments[len(fragments)-1]) + } + + r := new(reassembly) + for i, f := range []packet{ + // without anything, not a number of 8 bytes with more to come, past what an IP packet carries + udp(0, 0, true, 1), + udp(0, 12, true, 1), + udp(math.MaxUint16-ipv4HeaderSize-7, 8, false, 1), + fragment(netip.MustParseAddr("fd00::1"), protoUDP, 1, math.MaxUint16-7, 8, false, 1), + } { + if _, ok := r.add(f); ok || waited(r) != 0 { + t.Fatal("a fragment that is none is waited for more of: ", i) + } + } + for i, fragments := range [][]packet{ + // overlapping, a second end past the first and before it, past the end, an end before what came + {udp(0, 16, true, 1), udp(8, 16, true, 2)}, + {udp(8, 8, false, 1), udp(16, 8, false, 2)}, + {udp(8, 8, false, 1), udp(0, 8, false, 2)}, + {udp(8, 8, false, 1), udp(16, 16, true, 2)}, + {udp(16, 8, true, 1), udp(8, 8, false, 2)}, + } { + r := new(reassembly) + if _, ok := add(r, fragments...); ok || waited(r) != 0 { + t.Fatal("fragments that do not fit each other are still waited for: ", i) + } + } + + // one that comes twice is taken once, as it came first + if p, ok := add(r, udp(0, 16, true, 1), udp(0, 16, true, 2), udp(16, 8, false, 3)); !ok || string(p.payload) != strings.Repeat("\x01", 16)+strings.Repeat("\x03", 8) || waited(r) != 0 { + t.Fatal("wrong packet of a fragment that came twice: ", p, ok) + } + // the same number from someone else, or of another protocol, is another packet + if p, ok := add(r, udp(0, 8, true, 1), fragment(netip.MustParseAddr("10.0.0.3"), protoUDP, 1, 8, 8, false, 2), + fragment(src, protoICMPv4, 1, 8, 8, false, 3), udp(8, 8, false, 4)); !ok || string(p.payload) != strings.Repeat("\x01", 8)+strings.Repeat("\x04", 8) || waited(r) != 2 { + t.Fatal("wrong packet among others with its number: ", p, ok) + } + // what is left of a packet whose other fragments were lost is not put together with a later one of its number + r = new(reassembly) + r.add(udp(0, 8, true, 1)) + r.packets[fragments{src, dst, protoUDP, 1}].since = time.Now().Add(-2 * fragmentsTime) + if p, ok := add(r, udp(8, 8, false, 2), udp(0, 8, true, 3)); !ok || string(p.payload) != strings.Repeat("\x03", 8)+strings.Repeat("\x02", 8) { + t.Fatal("wrong packet after one of its number was given up: ", p, ok) + } + // when too many are waited for, the one that is waited for the longest makes room + r = new(reassembly) + for id := range uint32(fragmentsMost) { + r.add(fragment(src, protoUDP, id, 0, 8, true, 1)) + } + r.packets[fragments{src, dst, protoUDP, 7}].since = time.Now().Add(-time.Second) + r.add(fragment(src, protoUDP, fragmentsMost, 0, 8, true, 1)) + if r.packets[fragments{src, dst, protoUDP, 7}] != nil || r.packets[fragments{src, dst, protoUDP, fragmentsMost}] == nil || waited(r) != fragmentsMost { + t.Fatal("not the one that was waited for the longest made room") + } +} + // The checksum is that of the 16-bit words added one by one. func TestChecksum(t *testing.T) { b := make([]byte, 5000) diff --git a/proxy/tun/tcp_lightweight.go b/proxy/tun/tcp_lightweight.go index 1a5964d44..bb888b684 100644 --- a/proxy/tun/tcp_lightweight.go +++ b/proxy/tun/tcp_lightweight.go @@ -1,10 +1,12 @@ package tun import ( + "context" "encoding/binary" "io" "math" "math/rand/v2" + "net/netip" "slices" "sync" "time" @@ -37,7 +39,9 @@ const ( tcpHeld = 1024 // tcpSmallest is the smallest mss that the system is believed, the one of Linux tcpSmallest = 48 - // tcpOpening is how many connections can be waiting for the answer to their SYN-ACK + // tcpStamps is what the times of both sides take of the options of every segment that carries them (RFC 7323) + tcpStamps = 12 + // tcpOpening is how many connections that the other side opens can be waiting for the answer to their SYN-ACK tcpOpening = 65535 // tcpStart is how many segments are sent before any of them is acknowledged tcpStart = 10 @@ -48,12 +52,18 @@ const ( // tcpTick is how often a connection that waits for something looks at what it waits for tcpTick = 100 * time.Millisecond - // ticks without anything acknowledged before it is sent again, twice as many every next time, a minute at most + // ticks without anything acknowledged before it is sent again, twice as many every next time, a minute at most, + // while an open connection that waits sends again every two seconds at most or as often as an answer takes, + // twice as seldom every next minute: when its way was away for a while it goes on soon after the way is back, + // and the next segment still waits the doubled time, so that its answer can be timed tcpRetransmitTicks = 3 + tcpRetransmitTicksOpen = 20 tcpRetransmitTicksMost = 600 - // how many times it is sent again before the connection is given up: that is after four minutes, as what is - // behind a wireless link may be away for a while, and after twenty seconds for one that never answered - tcpRetransmits = 10 + tcpMinuteTicks = int(time.Minute / tcpTick) + // an open connection is given up after four minutes of ticks without an acknowledgment since the timer first sent + // it again, as what is behind a wireless link may be away for a while (ticks, so that the time a computer sleeps + // is not counted), and one that never answered after it was sent again that many times, after twenty seconds + tcpGiveUpTicks = 4 * tcpMinuteTicks tcpSynRetransmits = 5 // ticks between the probes of a window that stays closed tcpProbeTicks = 10 @@ -62,10 +72,15 @@ const ( ) var ( - errTCPReset = errors.New("connection reset") - errTCPTimeout = errors.New("connection timed out") + errTCPReset = errors.New("connection reset") + errTCPTimeout = errors.New("connection timed out") + errTCPRefused = errors.New("connection refused") + errTCPUnreachable = errors.New("destination unreachable") ) +// tcpStarted is where the times that segments carry count from +var tcpStarted = time.Now() + // segment is a TCP segment that came from the tun type segment struct { seq, ack uint32 @@ -92,7 +107,7 @@ type held struct { data *buf.Buffer } -// tcpConn is a TCP connection that the system opened through the tun. +// tcpConn is a TCP connection that the system opened through the tun, or one that was opened to it by DialTCP. // Mostly the two are on the same machine, with nothing but the queue of the tun in between, which drops what // does not fit. But the system may also pass on what others send, as a router does, and then the link to them // is in between. So a connection has windows that are small enough, keeps what came after a lost segment, @@ -114,12 +129,19 @@ type tcpConn struct { sndShift, rcvShift uint8 // sack is set if the system takes selective acknowledgments sack bool + // stamps is set if every segment carries the time of this side and the last one that came of the other side, + // which sees by it how long its segments take, also the ones that it sent again (RFC 7323). + // stampBase is what the time of this side starts from, stampSent the one that the last segment carried + stamps bool + stampBase, stampSent uint32 mu sync.Mutex readable sync.Cond writable sync.Cond established bool + // opened is there while DialTCP waits for the answer to its SYN, and closed when that wait is over + opened chan struct{} // closed is set when Close is called closed bool // err is why the connection is gone @@ -140,6 +162,8 @@ type tcpConn struct { rcvHeld []held // rcvLast is the seq of the last one that was put into rcvHeld rcvLast uint32 + // stamp is the time of the other side that is sent back to it + stamp uint32 sndUna, sndNxt uint32 sndWnd uint32 @@ -165,6 +189,9 @@ type tcpConn struct { recover uint32 // lost is the end of what is lost past the last of sacked, resent the end of what was sent again since then lost, resent uint32 + // headAt is when the segment at sndUna was sent again, or took that place while something was lost. + // It is zero when that segment is not sent once more for the system still asking for it: it was already + headAt time.Time // timed is the end of a segment that was sent at timedAt and not again, to see how long its acknowledgment takes. // The first one is the SYN-ACK timed uint32 @@ -176,17 +203,17 @@ type tcpConn struct { armed bool // stalled counts ticks without an acknowledgment of what waits for one stalled, retransmitTicks, retransmits int - lingered bool + // lostTicks counts the ticks since the timer sent again the first time since something was acknowledged + lostTicks int + lingered bool } -func newTCPConn(s *stack, f flow, syn segment) *tcpConn { +func newTCPConn(s *stack, f flow) *tcpConn { c := &tcpConn{ stack: s, flow: f, src: net.TCPDestination(net.IPAddress(f.src.Addr().AsSlice()), net.Port(f.src.Port())), dst: net.TCPDestination(net.IPAddress(f.dst.Addr().AsSlice()), net.Port(f.dst.Port())), - rcvNxt: syn.seq + 1, - sndWnd: uint32(syn.window), window: s.window, ssthresh: uint32(s.window), timedAt: time.Now(), @@ -196,6 +223,17 @@ func newTCPConn(s *stack, f flow, syn segment) *tcpConn { c.writable.L = &c.mu c.ipSize = putIPHeader(c.ip[:], protoTCP, f.dst.Addr(), f.src.Addr()) c.mss = s.mtu - c.ipSize - tcpHeaderSize + c.sndUna = rand.Uint32() + c.sndNxt = c.sndUna + 1 + c.stampBase = rand.Uint32() + return c +} + +// synchronize takes what the SYN of the other side says. c.mu is held, or nobody else has c yet +func (c *tcpConn) synchronize(syn segment) { + c.rcvNxt = syn.seq + 1 + c.sndWnd = uint32(syn.window) + c.sack, c.stamps, c.sndShift, c.rcvShift = false, false, 0, 0 for kind, value, o := nextOption(syn.options); kind != 0; kind, value, o = nextOption(o) { switch { case kind == 2 && len(value) == 2: @@ -205,20 +243,77 @@ func newTCPConn(s *stack, f flow, syn segment) *tcpConn { c.rcvShift = tcpWindowShift case kind == 4: c.sack = true + case kind == 8 && len(value) == 8 && c.stack.stamps: + c.stamps, c.stamp = true, binary.BigEndian.Uint32(value) } } + if c.stamps { + c.mss -= tcpStamps + } c.mss = max(c.mss, tcpSmallest) c.cwnd = tcpStart * uint32(c.mss) - c.sndUna = rand.Uint32() - c.sndNxt = c.sndUna + 1 - return c +} + +// DialTCP implements Stack +func (s *stack) DialTCP(ctx context.Context, remote netip.AddrPort) (net.Conn, error) { + remote = netip.AddrPortFrom(remote.Addr().Unmap(), remote.Port()) + local, err := s.local(remote.Addr()) + if err != nil { + return nil, err + } + opened := make(chan struct{}) + s.mu.Lock() + if s.closed { + s.mu.Unlock() + return nil, io.ErrClosedPipe + } + f := flow{src: remote} + port, ok := ephemeral(func(port uint16) bool { + f.dst = netip.AddrPortFrom(local, port) + return s.conns[f] == nil + }) + if !ok { + s.mu.Unlock() + return nil, errors.New("no port left to reach ", remote, " from") + } + f.dst = netip.AddrPortFrom(local, port) + c := newTCPConn(s, f) + // what the SYN offers, until the answer says what of it is taken + c.sack, c.stamps, c.rcvShift, c.opened = true, s.stamps, tcpWindowShift, opened + s.conns[f] = c + s.mu.Unlock() + s.opening.Add(1) + c.mu.Lock() + if c.err == nil { + c.sendSyn() + c.arm(tcpTick) + } + c.mu.Unlock() + + select { + case <-opened: + case <-ctx.Done(): + } + c.mu.Lock() + defer c.mu.Unlock() + if !c.established { + if c.err == nil { + c.abort(ctx.Err()) + } + return nil, c.err + } + return c, nil } // header writes the headers of a segment with size bytes of payload into b and returns their size. // The checksum of the segment is left to write. c.mu is held func (c *tcpConn) header(b []byte, seq uint32, flags byte, options []byte, size int) int { n := copy(b, c.ip[:c.ipSize]) - t := b[n : n+tcpHeaderSize+len(options)] + stamps := 0 + if c.stamps { + stamps = tcpStamps + } + t := b[n : n+tcpHeaderSize+stamps+len(options)] binary.BigEndian.PutUint16(t, c.flow.dst.Port()) binary.BigEndian.PutUint16(t[2:], c.flow.src.Port()) binary.BigEndian.PutUint32(t[4:], seq) @@ -233,7 +328,18 @@ func (c *tcpConn) header(b []byte, seq uint32, flags byte, options []byte, size binary.BigEndian.PutUint16(t[14:], uint16(min(window>>c.rcvShift, math.MaxUint16))) } t[16], t[17], t[18], t[19] = 0, 0, 0, 0 - copy(t[tcpHeaderSize:], options) + if c.stamps { + o := t[tcpHeaderSize:] + o[0], o[1], o[2], o[3] = 1, 1, 8, 10 + if !c.sending { + // what is sent while WriteMultiBuffer writes a segment has the time of that one: it may get to the tun + // first, and the other side drops a segment with a time before the last one that it took + c.stampSent = c.stampBase + uint32(time.Since(tcpStarted)/time.Millisecond) + } + binary.BigEndian.PutUint32(o[4:], c.stampSent) + binary.BigEndian.PutUint32(o[8:], c.stamp) + } + copy(t[tcpHeaderSize+stamps:], options) setIPLength(b[:n], n+len(t)+size) c.rcvAcked = c.rcvNxt c.rcvAdvertised = window @@ -254,7 +360,8 @@ func (c *tcpConn) send(seq uint32, flags byte, options, payload []byte) { c.write(b[:c.header(b[:], seq, flags, options, len(payload))], payload) } -func (c *tcpConn) sendSynAck() { +// sendSyn sends the answer to the SYN of the other side, or the SYN of a connection that is opened from here +func (c *tcpConn) sendSyn() { options := [12]byte{2, 4} binary.BigEndian.PutUint16(options[2:], uint16(c.stack.mtu-c.ipSize-tcpHeaderSize)) n := 4 @@ -264,7 +371,11 @@ func (c *tcpConn) sendSynAck() { if c.rcvShift != 0 { n += copy(options[n:], []byte{1, 3, 3, c.rcvShift}) } - c.send(c.sndUna, tcpSyn|tcpAck, options[:n], nil) + flags := byte(tcpSyn | tcpAck) + if c.opened != nil { + flags = tcpSyn + } + c.send(c.sndUna, flags, options[:n], nil) } // sendAck acknowledges what was received, and tells what of rcvHeld waits for a lost segment @@ -278,8 +389,12 @@ func (c *tcpConn) sendAck() { c.send(c.sndNxt, tcpAck, nil, nil) return } - // the block with the segment that came last goes first - var blocks [4][2]uint32 + // the block with the segment that came last goes first. Next to the times there is room for one less + var room [4][2]uint32 + blocks := room[:] + if c.stamps { + blocks = room[:3] + } n, last := 1, false for i := 0; i < len(c.rcvHeld); { start, end, recent := c.rcvHeld[i].seq, c.rcvHeld[i].seq, false @@ -306,6 +421,22 @@ func (c *tcpConn) sendAck() { c.send(c.sndNxt, tcpAck, options[:4+8*len(list)], nil) } +// stamped keeps the time that a segment of the other side carries, to send it back. It is the one of the segment +// that the stream goes on with, so that the other side sees how long that one took, and not one of what came +// before it. c.mu is held +func (c *tcpConn) stamped(s segment) { + if int32(s.seq-c.rcvAcked) > 0 { + return + } + for kind, value, o := nextOption(s.options); kind != 0; kind, value, o = nextOption(o) { + if kind == 8 && len(value) == 8 { + if stamp := binary.BigEndian.Uint32(value); int32(stamp-c.stamp) > 0 { + c.stamp = stamp + } + } + } +} + // input takes a segment of the connection. It returns false if the connection is gone // and the segment is the start of another one func (c *tcpConn) input(s segment) bool { @@ -314,6 +445,32 @@ func (c *tcpConn) input(s segment) bool { if c.err != nil { return s.flags&(tcpSyn|tcpAck|tcpRst) != tcpSyn } + if c.opened != nil { + // only the answer to the SYN is waited for, or the word that nobody takes it + switch { + case s.flags&tcpAck == 0: + case s.ack != c.sndNxt: + // of a connection that the other side still has from before: it is told that there is none + if s.flags&tcpRst == 0 { + c.stack.refuse(c.flow, s) + } + case s.flags&tcpRst != 0: + c.abort(errTCPRefused) + case s.flags&tcpSyn != 0: + c.synchronize(s) + c.established = true + c.sndUna, c.sndBase = s.ack, s.ack + c.stalled, c.retransmits = 0, 0 + if !c.timedAt.IsZero() { + c.clocked() + } + c.stack.opening.Add(-1) + close(c.opened) + c.opened = nil + c.sendAck() + } + return true + } if s.flags&tcpRst != 0 { // from where the system is, or was before its FIN, not from someone who knows no more than the addresses if s.seq-c.rcvNxt <= uint32(c.window) || c.rcvFin && s.seq+1 == c.rcvNxt { @@ -324,9 +481,12 @@ func (c *tcpConn) input(s segment) bool { if s.flags&tcpSyn != 0 { switch { case !c.established && s.seq+1 == c.rcvNxt: - c.sendSynAck() + c.sendSyn() c.timedAt = time.Time{} - case c.closed && s.flags&tcpAck == 0: + case c.closed && s.flags&tcpAck == 0 && !c.stack.verify: + // the system of this machine opens the same ports again: it has let the old connection go. + // What comes through a tunnel is answered with what is expected instead, as anyone who knows + // the addresses could send it, and the other side answers that with a reset if it is gone c.abort(errTCPReset) return false default: @@ -337,6 +497,9 @@ func (c *tcpConn) input(s segment) bool { if s.flags&tcpAck == 0 { return true } + if c.stamps { + c.stamped(s) + } if !c.established { if s.ack != c.sndNxt { return true @@ -385,6 +548,7 @@ func (c *tcpConn) acknowledge(s segment) { if !c.timedAt.IsZero() && int32(c.sndUna-c.timed) >= 0 { c.clocked() } + recovering := c.recovering switch { case c.recovering && int32(c.sndUna-c.recover) >= 0: c.recovering = false @@ -393,11 +557,18 @@ func (c *tcpConn) acknowledge(s segment) { if end := c.sndUna + min(uint32(c.mss), c.recover-c.sndUna); int32(end-c.lost) > 0 { c.lost = end } + c.headAt = time.Now() } // the way takes more if what it was given has arrived, as long as it was given about as much as it takes if sent >= c.cwnd/2 { if c.cwnd < c.ssthresh { - c.cwnd = min(c.cwnd+acked, c.ssthresh) + grow := acked + if recovering { + // after a timeout: one segment that is sent again can acknowledge all that came after it, + // which says nothing about how much the way takes at once + grow = min(grow, 2*uint32(c.mss)) + } + c.cwnd = min(c.cwnd+grow, c.ssthresh) } else if !c.recovering { // a segment more for every cwnd that arrives c.cwnd = min(c.cwnd+max(uint32(uint64(c.mss)*uint64(acked)/uint64(c.cwnd)), 1), uint32(c.window)) @@ -417,12 +588,22 @@ func (c *tcpConn) acknowledge(s segment) { } } } - if !c.recovering && c.sndUna != c.sndNxt && (len(c.sacked) > 0 || c.dupAcks >= 3) { - // the system has something past sndUna, or says the same for the third time: the segment at sndUna is lost + if !c.recovering && c.sndUna != c.sndNxt && (len(c.sacked) > 0 || !c.sack && c.dupAcks >= 3) { + // the system has something past sndUna, or says the same for the third time when it cannot say what it has: + // the segment at sndUna is lost. One that can say it repeats itself also for what it got twice c.congested() c.lost = c.sndUna + min(uint32(c.mss), c.sndNxt-c.sndUna) } if c.recovering { + if acked == 0 && c.srtt != 0 && !c.headAt.IsZero() && time.Since(c.headAt) > max(c.srtt+max(c.srtt/4, 4*c.rttvar), tcpTick/4) { + // the segment that the system waits for was sent again longer ago than an answer takes, and the system + // still answers what comes after it: that one was lost as well. It is sent once more, and only once: + // the timer would find it too, but late, and then nothing is known anymore of how much the way takes + if start, end := c.hole(c.sndUna); start == c.sndUna && start != end { + c.retransmit(start, start+min(end-start, uint32(c.mss))) + c.headAt, c.stalled = time.Time{}, 0 + } + } c.resend() } c.sndWnd = window @@ -451,7 +632,12 @@ func (c *tcpConn) clocked() { c.rttvar += ((c.srtt - d).Abs() - c.rttvar) / 4 c.srtt += (d - c.srtt) / 8 } - c.retransmitTicks = max(tcpRetransmitTicks, int((c.srtt+4*c.rttvar)/tcpTick)+2) + c.retransmitTicks = c.answerTicks() +} + +// answerTicks is how many ticks an acknowledgment is waited for as long as nothing was sent again. c.mu is held +func (c *tcpConn) answerTicks() int { + return max(tcpRetransmitTicks, int((c.srtt+4*c.rttvar)/tcpTick)+2) } // sackAdd puts what the system says it has into sacked. c.mu is held @@ -481,7 +667,7 @@ func (c *tcpConn) congested() { c.ssthresh = max(min(c.cwnd, c.sndNxt-c.sndUna)/2, 2*uint32(c.mss)) c.cwnd = c.ssthresh c.recovering, c.recover, c.resent = true, c.sndNxt, c.sndUna - c.timedAt = time.Time{} + c.timedAt, c.headAt = time.Time{}, time.Time{} } // hole returns the first part at or after seq of what is lost: what the system does not have @@ -543,6 +729,10 @@ func (c *tcpConn) resend() { room = c.cwnd - flight } end = start + min(end-start, room) + if start == c.sndUna { + // its answer is waited for from now on + c.headAt, c.stalled = time.Now(), 0 + } c.retransmit(start, end) c.resent = end } @@ -642,9 +832,9 @@ func (c *tcpConn) release() { // hold keeps a segment that is not the next one. c.mu is held func (c *tcpConn) hold(seq uint32, payload []byte) { - i := 0 - for i < len(c.rcvHeld) && int32(c.rcvHeld[i].seq-seq) < 0 { - i++ + i := len(c.rcvHeld) + for i > 0 && int32(c.rcvHeld[i-1].seq-seq) >= 0 { + i-- } // without what the one before and the one after have if i > 0 { @@ -666,8 +856,9 @@ func (c *tcpConn) hold(seq uint32, payload []byte) { if len(payload) == 0 || len(c.rcvHeld) == tcpHeld { return } - b := buf.NewWithSize(int32(len(payload))) - b.Write(payload) + // as much memory as it carries: the room that is left counts bytes, and a thousand tiny segments in pooled + // buffers would take two megabytes + b := buf.FromBytes(slices.Clone(payload)) c.rcvHeld = slices.Insert(c.rcvHeld, i, held{seq, b}) c.rcvLast = seq } @@ -716,6 +907,38 @@ func (c *tcpConn) queued(parts [][]byte, seq uint32, size int) ([][]byte, int) { return parts, n } +// shrink makes the segments fit a link on the way that takes packets of mtu bytes, when the one at seq did not. +// None of what is on its way fits: it is sent again, the first segment at once and the others as they are +// acknowledged. Only what is said about a segment that was sent and is not acknowledged is believed +func (c *tcpConn) shrink(seq uint32, mtu int) { + c.mu.Lock() + defer c.mu.Unlock() + mss := mtu - c.ipSize - tcpHeaderSize + if c.stamps { + mss -= tcpStamps + } + mss = max(mss, tcpSmallest) + if c.err != nil || !c.established || seq-c.sndUna >= c.sndNxt-c.sndUna || mss >= c.mss { + return + } + c.mss = mss + // as what is lost, without the way taking less for it: it is the size that it did not take + c.recovering, c.recover, c.lost = true, c.sndNxt, c.sndNxt + c.timedAt = time.Time{} + c.resent = c.sndUna + min(uint32(mss), c.sndNxt-c.sndUna) + c.headAt = time.Now() + c.retransmit(c.sndUna, c.resent) +} + +// unreachable ends the wait for the answer to the SYN at seq, which a router on the way could not deliver +func (c *tcpConn) unreachable(seq uint32) { + c.mu.Lock() + defer c.mu.Unlock() + if c.err == nil && c.opened != nil && seq == c.sndUna { + c.abort(errTCPUnreachable) + } +} + // arm makes tick run after d, unless it is going to run already. c.mu is held func (c *tcpConn) arm(d time.Duration) { if c.armed { @@ -742,19 +965,34 @@ func (c *tcpConn) tick() { } switch { case c.sndUna != c.sndNxt: - if c.stalled++; c.stalled >= c.retransmitTicks { - if c.retransmits++; c.retransmits > tcpRetransmits || !c.established && c.retransmits > tcpSynRetransmits { + c.stalled++ + due := c.stalled >= c.retransmitTicks + if c.established && c.retransmits > 0 { + due = due || c.stalled >= max(tcpRetransmitTicksOpen, c.answerTicks())<<(c.lostTicks/tcpMinuteTicks) + } + if due { + if c.retransmits++; c.retransmits == 1 { + c.lostTicks = 0 + } else { + c.lostTicks += c.stalled + } + if c.established && c.lostTicks > tcpGiveUpTicks || !c.established && c.retransmits > tcpSynRetransmits { c.abort(errTCPTimeout) return } c.stalled = 0 c.retransmitTicks = min(c.retransmitTicks*2, tcpRetransmitTicksMost) if !c.established { - c.sendSynAck() + c.sendSyn() c.timedAt = time.Time{} } else { - // what the system does not say it has is lost, and the way to it is tried with a segment again + // what the system does not say it has is lost, and the way to it is tried with a segment again. + // It takes half of what it took before the loss, not half of that again if the loss was seen already + ssthresh, recovering := c.ssthresh, c.recovering c.congested() + if recovering { + c.ssthresh = ssthresh + } c.cwnd, c.lost = uint32(c.mss), c.sndNxt c.resend() } @@ -792,6 +1030,10 @@ func (c *tcpConn) abort(err error) { if c.timer != nil { c.timer.Stop() } + if c.opened != nil { + close(c.opened) + c.opened = nil + } for _, h := range c.rcvHeld { h.data.Release() } @@ -879,9 +1121,9 @@ func (c *tcpConn) WriteMultiBuffer(mb buf.MultiBuffer) error { c.writing = len(mb) // a segment is one part of a Buffer, or as much as there is to send if the tun splits it by itself var room [tcpParts][]byte - limit, parts := c.mss, room[:0:1] + parts := room[:0:1] if c.stack.segmenter != nil { - limit, parts = tcpSegment, room[:0] + parts = room[:0] } for end := c.sndNxt + size; c.sndNxt != end; { // not a part of a segment when all of it can go later, with what is acknowledged by then @@ -897,7 +1139,11 @@ func (c *tcpConn) WriteMultiBuffer(mb buf.MultiBuffer) error { if c.err != nil { break } - most := min(limit, int(c.space())) + // the mss may have become smaller while the last segment was written + most := min(c.mss, int(c.space())) + if c.stack.segmenter != nil { + most = min(tcpSegment, int(c.space())) + } if most > c.mss { // the same for what the tun splits most -= most % c.mss @@ -907,7 +1153,7 @@ func (c *tcpConn) WriteMultiBuffer(mb buf.MultiBuffer) error { if c.sndNxt+uint32(n) == end { flags |= tcpPsh } - var b [ipv6HeaderSize + tcpHeaderSize]byte + var b [ipv6HeaderSize + tcpHeaderSize + tcpStamps]byte h := b[:c.header(b[:], c.sndNxt, flags, nil, n)] if c.sndUna == c.sndNxt { // the ticks without an acknowledgment are counted from the first segment that waits for one diff --git a/proxy/tun/udp_fullcone.go b/proxy/tun/udp_fullcone.go index 82f115b37..6a71f4b73 100644 --- a/proxy/tun/udp_fullcone.go +++ b/proxy/tun/udp_fullcone.go @@ -39,9 +39,7 @@ func (u *udpConnectionHandler) HandlePacket(src net.Destination, dst net.Destina u.RLock() conn, found := u.udpConns[src] if found { - select { - case conn.egress <- data: - default: + if !conn.queue.push(data) { errors.LogDebug(context.Background(), "drop udp with size ", data.Len(), " to ", dst.NetAddr(), " original ", conn.dst.NetAddr(), " > queue full") data.Release() } @@ -55,17 +53,14 @@ func (u *udpConnectionHandler) HandlePacket(src net.Destination, dst net.Destina conn, found = u.udpConns[src] if !found { - egress := make(chan *buf.Buffer, 1024) - conn = &udpConn{handler: u, egress: egress, src: src, dst: dst} + conn = &udpConn{handler: u, queue: newPacketQueue(udpQueueLimit), src: src, dst: dst} u.udpConns[src] = conn go u.handleConnection(conn, dst) } - // send packet data to the egress channel, if it has buffer, or discard - select { - case conn.egress <- data: - default: + // queue the packet for the reader of the connection, or discard it + if !conn.queue.push(data) { errors.LogDebug(context.Background(), "drop udp with size ", data.Len(), " to ", dst.NetAddr(), " original ", conn.dst.NetAddr(), " > queue full 2") data.Release() } @@ -76,7 +71,7 @@ func (u *udpConnectionHandler) connectionFinished(conn *udpConn) { // Close runs twice per flow; a newer conn may already own this src. if u.udpConns[conn.src] == conn { delete(u.udpConns, conn.src) - close(conn.egress) + conn.queue.close() } u.Unlock() } @@ -85,13 +80,13 @@ func (u *udpConnectionHandler) connectionFinished(conn *udpConn) { type udpConn struct { handler *udpConnectionHandler - egress chan *buf.Buffer - src net.Destination - dst net.Destination + queue *packetQueue + src net.Destination + dst net.Destination } func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) { - b, ok := <-c.egress + b, ok := c.queue.pop() if !ok { return nil, io.EOF } @@ -100,7 +95,7 @@ func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) { // Read packets from the connection func (c *udpConn) Read(p []byte) (int, error) { - b, ok := <-c.egress + b, ok := c.queue.pop() if !ok { return 0, io.EOF } @@ -137,7 +132,7 @@ func (c *udpConn) Write(p []byte) (int, error) { // sending packets back mean sending payload with source/destination reversed err := c.handler.writePacket(p, c.dst, c.src) if err != nil { - return 0, nil + return 0, err } return len(p), nil @@ -168,3 +163,68 @@ func (c *udpConn) SetReadDeadline(t time.Time) error { func (c *udpConn) SetWriteDeadline(t time.Time) error { return nil } + +// udpQueueLimit bounds the packets waiting for one UDP flow; more are dropped. +const udpQueueLimit = 1024 + +// packetQueue holds the packets waiting for one UDP flow. Unlike a buffered +// channel of the same bound it only allocates for packets actually queued, so +// the many idle flows kept until the idle timeout cost next to nothing. +type packetQueue struct { + mu sync.Mutex + items []*buf.Buffer + limit int + notify chan struct{} + closed bool +} + +func newPacketQueue(limit int) *packetQueue { + return &packetQueue{limit: limit, notify: make(chan struct{}, 1)} +} + +// push queues b and reports whether it was accepted. +func (q *packetQueue) push(b *buf.Buffer) bool { + q.mu.Lock() + defer q.mu.Unlock() + if q.closed || len(q.items) >= q.limit { + return false + } + q.items = append(q.items, b) + select { + case q.notify <- struct{}{}: + default: + } + return true +} + +// pop blocks until a packet is queued or the queue is closed and drained. +func (q *packetQueue) pop() (*buf.Buffer, bool) { + for { + q.mu.Lock() + if len(q.items) > 0 { + b := q.items[0] + q.items[0] = nil + q.items = q.items[1:] + if len(q.items) == 0 { + q.items = nil + } + q.mu.Unlock() + return b, true + } + if q.closed { + q.mu.Unlock() + return nil, false + } + q.mu.Unlock() + <-q.notify + } +} + +func (q *packetQueue) close() { + q.mu.Lock() + defer q.mu.Unlock() + if !q.closed { + q.closed = true + close(q.notify) + } +} diff --git a/proxy/wireguard/tun_test.go b/proxy/tun/udp_fullcone_test.go similarity index 73% rename from proxy/wireguard/tun_test.go rename to proxy/tun/udp_fullcone_test.go index 88a45336f..f764830d1 100644 --- a/proxy/wireguard/tun_test.go +++ b/proxy/tun/udp_fullcone_test.go @@ -1,48 +1,48 @@ -package wireguard +package tun import ( "testing" "time" + "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/net" ) -// BenchmarkUDPManagerNewSession measures what one new UDP flow costs the +// BenchmarkUDPNewSession measures what one new UDP flow costs the // inbound while it stays open: QUIC and DNS open many short flows, and each // one lives until the connection idle timeout. -func BenchmarkUDPManagerNewSession(b *testing.B) { - m := &udpManager{ - handler: func(conn net.Conn, dest net.Destination) {}, - m: make(map[string]*udpConn), - } +func BenchmarkUDPNewSession(b *testing.B) { + u := newUdpConnectionHandler(func(conn net.Conn, dest net.Destination) {}, nil) dst := net.UDPDestination(net.ParseAddress("1.1.1.1"), 443) - payload := make([]byte, 1200) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { src := net.UDPDestination(net.IPAddress([]byte{10, byte(i >> 16), byte(i >> 8), byte(i)}), net.Port(1024+i%60000)) - m.feed(src, dst, payload) + u.HandlePacket(src, dst, &buf.Buffer{}) } } func TestPacketQueueOrderAndClose(t *testing.T) { q := newPacketQueue(udpQueueLimit) for i := 0; i < 3; i++ { - if !q.push(&packet{p: []byte{byte(i)}}) { + b := buf.New() + b.WriteByte(byte(i)) + if !q.push(b) { t.Fatalf("push %d rejected", i) } } for i := 0; i < 3; i++ { - p, ok := q.pop() - if !ok || p.p[0] != byte(i) { - t.Fatalf("pop %d: got %v, %v", i, p, ok) + b, ok := q.pop() + if !ok || b.Byte(0) != byte(i) { + t.Fatalf("pop %d: got %v, %v", i, b, ok) } + b.Release() } q.close() if _, ok := q.pop(); ok { t.Fatal("pop after close returned a packet") } - if q.push(&packet{}) { + if q.push(&buf.Buffer{}) { t.Fatal("push after close accepted") } } @@ -50,11 +50,11 @@ func TestPacketQueueOrderAndClose(t *testing.T) { func TestPacketQueueLimit(t *testing.T) { q := newPacketQueue(udpQueueLimit) for i := 0; i < udpQueueLimit; i++ { - if !q.push(&packet{}) { + if !q.push(&buf.Buffer{}) { t.Fatalf("push %d rejected below the limit", i) } } - if q.push(&packet{}) { + if q.push(&buf.Buffer{}) { t.Fatal("push above the limit accepted") } } @@ -80,7 +80,7 @@ func TestPacketQueueCloseUnblocksReader(t *testing.T) { func TestPacketQueueDropsDrainedStorage(t *testing.T) { q := newPacketQueue(udpQueueLimit) for i := 0; i < 100; i++ { - q.push(&packet{}) + q.push(&buf.Buffer{}) } for i := 0; i < 100; i++ { q.pop() diff --git a/proxy/wireguard/queue_bench_test.go b/proxy/tun/udp_queue_bench_test.go similarity index 85% rename from proxy/wireguard/queue_bench_test.go rename to proxy/tun/udp_queue_bench_test.go index 3380c84a8..34c95e542 100644 --- a/proxy/wireguard/queue_bench_test.go +++ b/proxy/tun/udp_queue_bench_test.go @@ -1,16 +1,18 @@ -package wireguard +package tun import ( "runtime" "testing" + + "github.com/xtls/xray-core/common/buf" ) const benchBatch = 64 // Raw cost of queueing and draining a small burst, as one flow's reader does. func BenchmarkQueueBurstChan(b *testing.B) { - ch := make(chan *packet, udpQueueLimit) - p := &packet{} + ch := make(chan *buf.Buffer, udpQueueLimit) + p := &buf.Buffer{} b.ReportAllocs() for i := 0; i < b.N; i++ { for j := 0; j < benchBatch; j++ { @@ -24,7 +26,7 @@ func BenchmarkQueueBurstChan(b *testing.B) { func BenchmarkQueueBurstPacketQueue(b *testing.B) { q := newPacketQueue(udpQueueLimit) - p := &packet{} + p := &buf.Buffer{} b.ReportAllocs() for i := 0; i < b.N; i++ { for j := 0; j < benchBatch; j++ { @@ -39,8 +41,8 @@ func BenchmarkQueueBurstPacketQueue(b *testing.B) { // Producer and consumer on different goroutines; the producer yields when the // queue is full instead of spinning, like a blocking channel send would. func BenchmarkQueueStreamChan(b *testing.B) { - ch := make(chan *packet, udpQueueLimit) - p := &packet{} + ch := make(chan *buf.Buffer, udpQueueLimit) + p := &buf.Buffer{} done := make(chan struct{}) go func() { for range ch { @@ -58,7 +60,7 @@ func BenchmarkQueueStreamChan(b *testing.B) { func BenchmarkQueueStreamPacketQueue(b *testing.B) { q := newPacketQueue(udpQueueLimit) - p := &packet{} + p := &buf.Buffer{} done := make(chan struct{}) go func() { for { diff --git a/proxy/wireguard/client.go b/proxy/wireguard/client.go index c47e3c8b1..862fb4e99 100644 --- a/proxy/wireguard/client.go +++ b/proxy/wireguard/client.go @@ -117,8 +117,8 @@ func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) { errors.LogWarning(context.Background(), "Using kernel TUN") tun, tnet, err = createKernelTun(localAddresses, dnses, int(conf.Mtu)) } else { - errors.LogWarning(context.Background(), "Using gVisor TUN") - tun, tnet, _, err = CreateNetTUN(localAddresses, dnses, int(conf.Mtu), true) + errors.LogWarning(context.Background(), "Using built-in TCP/IP stack") + tun, tnet, err = CreateNetTUN(localAddresses, dnses, int(conf.Mtu), nil) } if err != nil { return nil, err diff --git a/proxy/wireguard/icmp.go b/proxy/wireguard/icmp.go deleted file mode 100644 index fba64c309..000000000 --- a/proxy/wireguard/icmp.go +++ /dev/null @@ -1,91 +0,0 @@ -package wireguard - -import ( - "context" - - "github.com/xtls/xray-core/common/errors" - tunicmp "github.com/xtls/xray-core/proxy/tun/icmp" - "gvisor.dev/gvisor/pkg/buffer" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/stack" - "gvisor.dev/gvisor/pkg/tcpip/transport/icmp" -) - -// CreateICMPEchoResponder answers ICMP echo requests from peers locally, the way -// the TUN inbound does: ICMP is not proxied, but ping and connectivity checks -// through the tunnel get a reply instead of timing out. -// -// In promiscuous mode gVisor skips its own IPv4 echo reply for addresses that are -// not assigned to the NIC and leaves it to a custom handler; IPv6 is registered -// too so both families behave the same. -func CreateICMPEchoResponder(gstack *stack.Stack) { - gstack.SetTransportProtocolHandler(icmp.ProtocolNumber4, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool { - return handleICMPEcho(gstack, header.IPv4ProtocolNumber, id, pkt) - }) - gstack.SetTransportProtocolHandler(icmp.ProtocolNumber6, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool { - return handleICMPEcho(gstack, header.IPv6ProtocolNumber, id, pkt) - }) -} - -func handleICMPEcho(gstack *stack.Stack, netProto tcpip.NetworkProtocolNumber, id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool { - srcIP := id.RemoteAddress - dstIP := id.LocalAddress - if srcIP.Len() == 0 || dstIP.Len() == 0 { - return true - } - - headerBytes := pkt.TransportHeader().Slice() - payloadBytes := pkt.Data().AsRange().ToSlice() - message := make([]byte, len(headerBytes)+len(payloadBytes)) - copy(message, headerBytes) - copy(message[len(headerBytes):], payloadBytes) - - if _, _, ok := tunicmp.ParseEchoRequest(netProto, message); !ok { - return true - } - - reply, err := tunicmp.BuildLocalEchoReply(netProto, message, dstIP, srcIP) - if err != nil { - errors.LogInfoInner(context.Background(), err, "failed to build local icmp echo reply") - return true - } - if err := writeRawICMPPacket(gstack, netProto, reply, dstIP, srcIP); err != nil { - errors.LogInfoInner(context.Background(), err, "failed to write local icmp echo reply") - } - return true -} - -func writeRawICMPPacket(gstack *stack.Stack, netProto tcpip.NetworkProtocolNumber, message []byte, srcIP, dstIP tcpip.Address) error { - pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ - ReserveHeaderBytes: header.IPv6MinimumSize, - Payload: buffer.MakeWithData(message), - }) - defer pkt.DecRef() - - if netProto == header.IPv4ProtocolNumber { - ipHdr := header.IPv4(pkt.NetworkHeader().Push(header.IPv4MinimumSize)) - ipHdr.Encode(&header.IPv4Fields{ - TotalLength: uint16(header.IPv4MinimumSize + len(message)), - TTL: 64, - Protocol: uint8(header.ICMPv4ProtocolNumber), - SrcAddr: srcIP, - DstAddr: dstIP, - }) - ipHdr.SetChecksum(^ipHdr.CalculateChecksum()) - } else { - ipHdr := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize)) - ipHdr.Encode(&header.IPv6Fields{ - PayloadLength: uint16(len(message)), - TransportProtocol: header.ICMPv6ProtocolNumber, - HopLimit: 64, - SrcAddr: srcIP, - DstAddr: dstIP, - }) - } - - if err := gstack.WriteRawPacket(1, netProto, buffer.MakeWithView(pkt.ToView())); err != nil { - return errors.New("failed to write raw icmp packet back to stack ", err) - } - return nil -} diff --git a/proxy/wireguard/icmp_test.go b/proxy/wireguard/icmp_test.go deleted file mode 100644 index 6cbd232b3..000000000 --- a/proxy/wireguard/icmp_test.go +++ /dev/null @@ -1,176 +0,0 @@ -package wireguard - -import ( - "bytes" - "net/netip" - "testing" - "time" - - "github.com/xtls/xray-core/common/net" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/checksum" - "gvisor.dev/gvisor/pkg/tcpip/header" -) - -func newICMPTestStack(t *testing.T) *netTun { - t.Helper() - dev, _, gstack, err := CreateNetTUN([]netip.Addr{ - netip.MustParseAddr("10.66.0.1"), - netip.MustParseAddr("fd00::1"), - }, nil, 1420, false) - if err != nil { - t.Fatal(err) - } - t.Cleanup(func() { dev.Close() }) - CreateForwarder(gstack, func(conn net.Conn, dest net.Destination) { conn.Close() }) - CreateICMPEchoResponder(gstack) - return dev.(*netTun) -} - -// startReader must run before the request is written: the stack may answer -// synchronously inside Write, and netTun hands packets over an unbuffered channel. -func startReader(dev *netTun) <-chan []byte { - got := make(chan []byte, 1) - go func() { - buf := make([]byte, 2048) - sizes := make([]int, 1) - if _, err := dev.Read([][]byte{buf}, sizes, 0); err == nil { - got <- buf[:sizes[0]] - } - }() - return got -} - -func awaitPacket(t *testing.T, got <-chan []byte) []byte { - t.Helper() - select { - case p := <-got: - return p - case <-time.After(2 * time.Second): - t.Fatal("no echo reply from the stack") - return nil - } -} - -func TestICMPv4EchoReply(t *testing.T) { - dev := newICMPTestStack(t) - src := tcpip.AddrFrom4([4]byte{10, 66, 0, 2}) - dst := tcpip.AddrFrom4([4]byte{1, 1, 1, 1}) - payload := []byte("xray wireguard ping") - - icmpMsg := make([]byte, header.ICMPv4MinimumSize+len(payload)) - req := header.ICMPv4(icmpMsg) - req.SetType(header.ICMPv4Echo) - req.SetIdent(0x1234) - req.SetSequence(7) - copy(req.Payload(), payload) - req.SetChecksum(header.ICMPv4Checksum(req[:header.ICMPv4MinimumSize], checksum.Checksum(payload, 0))) - - pkt := make([]byte, header.IPv4MinimumSize+len(icmpMsg)) - ip := header.IPv4(pkt) - ip.Encode(&header.IPv4Fields{ - TotalLength: uint16(len(pkt)), - TTL: 64, - Protocol: uint8(header.ICMPv4ProtocolNumber), - SrcAddr: src, - DstAddr: dst, - }) - ip.SetChecksum(^ip.CalculateChecksum()) - copy(pkt[header.IPv4MinimumSize:], icmpMsg) - - got := startReader(dev) - if _, err := dev.Write([][]byte{pkt}, 0); err != nil { - t.Fatal(err) - } - - reply := header.IPv4(awaitPacket(t, got)) - if !reply.IsValid(len(reply)) { - t.Fatal("invalid ipv4 reply") - } - if reply.SourceAddress() != dst || reply.DestinationAddress() != src { - t.Fatalf("reply addresses %v -> %v, want %v -> %v", reply.SourceAddress(), reply.DestinationAddress(), dst, src) - } - if reply.TransportProtocol() != header.ICMPv4ProtocolNumber { - t.Fatalf("reply protocol %v, want icmpv4", reply.TransportProtocol()) - } - echo := header.ICMPv4(reply.Payload()) - if echo.Type() != header.ICMPv4EchoReply { - t.Fatalf("reply type %v, want echo reply", echo.Type()) - } - if echo.Ident() != 0x1234 || echo.Sequence() != 7 { - t.Fatalf("reply ident/seq %#x/%d, want 0x1234/7", echo.Ident(), echo.Sequence()) - } - if !bytes.Equal(echo.Payload(), payload) { - t.Fatalf("reply payload %q, want %q", echo.Payload(), payload) - } - if checksum.Checksum(echo, 0) != 0xffff { - t.Fatal("bad icmpv4 checksum") - } -} - -func TestICMPv6EchoReply(t *testing.T) { - dev := newICMPTestStack(t) - src := tcpip.AddrFrom16([16]byte{0xfd, 15: 2}) - dst := tcpip.AddrFrom16([16]byte{0x26, 0x06, 0x47, 0x00, 0x47, 0x00, 15: 0x11}) - payload := []byte("xray wireguard ping6") - - icmpMsg := make([]byte, header.ICMPv6MinimumSize+len(payload)) - req := header.ICMPv6(icmpMsg) - req.SetType(header.ICMPv6EchoRequest) - req.SetIdent(0x4321) - req.SetSequence(9) - copy(req.Payload(), payload) - req.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{ - Header: req[:header.ICMPv6MinimumSize], - Src: src, - Dst: dst, - PayloadCsum: checksum.Checksum(payload, 0), - PayloadLen: len(payload), - })) - - pkt := make([]byte, header.IPv6MinimumSize+len(icmpMsg)) - ip := header.IPv6(pkt) - ip.Encode(&header.IPv6Fields{ - PayloadLength: uint16(len(icmpMsg)), - TransportProtocol: header.ICMPv6ProtocolNumber, - HopLimit: 64, - SrcAddr: src, - DstAddr: dst, - }) - copy(pkt[header.IPv6MinimumSize:], icmpMsg) - - got := startReader(dev) - if _, err := dev.Write([][]byte{pkt}, 0); err != nil { - t.Fatal(err) - } - - reply := header.IPv6(awaitPacket(t, got)) - if !reply.IsValid(len(reply)) { - t.Fatal("invalid ipv6 reply") - } - if reply.SourceAddress() != dst || reply.DestinationAddress() != src { - t.Fatalf("reply addresses %v -> %v, want %v -> %v", reply.SourceAddress(), reply.DestinationAddress(), dst, src) - } - echo := header.ICMPv6(reply.Payload()) - if echo.Type() != header.ICMPv6EchoReply { - t.Fatalf("reply type %v, want echo reply", echo.Type()) - } - if echo.Ident() != 0x4321 || echo.Sequence() != 9 { - t.Fatalf("reply ident/seq %#x/%d, want 0x4321/9", echo.Ident(), echo.Sequence()) - } - if !bytes.Equal(echo.Payload(), payload) { - t.Fatalf("reply payload %q, want %q", echo.Payload(), payload) - } - zeroed := header.ICMPv6(append([]byte(nil), echo[:header.ICMPv6MinimumSize]...)) - zeroed.SetChecksum(0) - want := header.ICMPv6Checksum(header.ICMPv6ChecksumParams{ - Header: zeroed, - Src: dst, - Dst: src, - PayloadCsum: checksum.Checksum(echo.Payload(), 0), - PayloadLen: len(echo.Payload()), - }) - if echo.Checksum() != want { - t.Fatalf("icmpv6 checksum %#x, want %#x", echo.Checksum(), want) - } -} diff --git a/proxy/wireguard/netstack.go b/proxy/wireguard/netstack.go index cc04f1580..b2b0f4e68 100644 --- a/proxy/wireguard/netstack.go +++ b/proxy/wireguard/netstack.go @@ -10,7 +10,6 @@ import ( "crypto/rand" "encoding/binary" "errors" - "fmt" "io" "net" "net/netip" @@ -18,101 +17,63 @@ import ( "regexp" "strconv" "strings" - "syscall" + "sync" "time" + "github.com/xtls/xray-core/common/buf" xnet "github.com/xtls/xray-core/common/net" + xtun "github.com/xtls/xray-core/proxy/tun" "golang.zx2c4.com/wireguard/tun" "golang.org/x/net/dns/dnsmessage" - "gvisor.dev/gvisor/pkg/buffer" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/link/channel" - "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" - "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" - "gvisor.dev/gvisor/pkg/tcpip/stack" - "gvisor.dev/gvisor/pkg/tcpip/transport/icmp" - "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" - "gvisor.dev/gvisor/pkg/tcpip/transport/udp" ) +// netTunQueue is how many packets of the stack can wait to be read from its device +const netTunQueue = 1024 + +// netTun is the device of a TCP/IP stack of Xray: what is written to it goes to the stack, +// and what the stack sends is read from it type netTun struct { - ep *channel.Endpoint - stack *stack.Stack - events chan tun.Event - notifyHandle *channel.NotificationHandle - incomingPacket chan *buffer.View - closed chan struct{} - mtu int - dnsServers []netip.Addr - hasV4, hasV6 bool + stack xtun.Stack + events chan tun.Event + // packets are what the stack has sent and Read has not taken yet + packets chan *buf.Buffer + closed chan struct{} + closeOnce sync.Once + mtu int } -func CreateNetTUN(localAddresses, dnsServers []netip.Addr, mtu int, handleLocal bool) (tun.Device, *Net, *stack.Stack, error) { - opts := stack.Options{ - NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol}, - TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}, - HandleLocal: handleLocal, - } +// CreateNetTUN returns a device with a TCP/IP stack on it, and what opens connections through that from +// localAddresses. With a handler the stack takes every connection that comes from the device, for any address. +// Without one it takes none +func CreateNetTUN(localAddresses, dnsServers []netip.Addr, mtu int, handler xtun.ConnectionHandler) (tun.Device, *Net, error) { dev := &netTun{ - ep: channel.New(1024, uint32(mtu), ""), - stack: stack.New(opts), - events: make(chan tun.Event, 10), - incomingPacket: make(chan *buffer.View), - closed: make(chan struct{}), - dnsServers: dnsServers, - mtu: mtu, + events: make(chan tun.Event, 10), + packets: make(chan *buf.Buffer, netTunQueue), + closed: make(chan struct{}), + mtu: mtu, } - sackEnabledOpt := tcpip.TCPSACKEnabled(true) // TCP SACK is disabled by default - tcpipErr := dev.stack.SetTransportProtocolOption(tcp.ProtocolNumber, &sackEnabledOpt) - if tcpipErr != nil { - return nil, nil, nil, fmt.Errorf("could not enable TCP SACK: %v", tcpipErr) - } - dev.notifyHandle = dev.ep.AddNotify(dev) - tcpipErr = dev.stack.CreateNIC(1, dev.ep) - if tcpipErr != nil { - return nil, nil, nil, fmt.Errorf("CreateNIC: %v", tcpipErr) - } - for _, ip := range localAddresses { - var protoNumber tcpip.NetworkProtocolNumber - if ip.Is4() { - protoNumber = ipv4.ProtocolNumber - } else if ip.Is6() { - protoNumber = ipv6.ProtocolNumber - } - protoAddr := tcpip.ProtocolAddress{ - Protocol: protoNumber, - AddressWithPrefix: tcpip.AddrFromSlice(ip.AsSlice()).WithPrefix(), - } - tcpipErr := dev.stack.AddProtocolAddress(1, protoAddr, stack.AddressProperties{}) - if tcpipErr != nil { - return nil, nil, nil, fmt.Errorf("AddProtocolAddress(%v): %v", ip, tcpipErr) - } - if ip.Is4() { - dev.hasV4 = true - } else if ip.Is6() { - dev.hasV6 = true - } - } - if dev.hasV4 { - dev.stack.AddRoute(tcpip.Route{Destination: header.IPv4EmptySubnet, NIC: 1}) - } - if dev.hasV6 { - dev.stack.AddRoute(tcpip.Route{Destination: header.IPv6EmptySubnet, NIC: 1}) + stack, err := xtun.NewStack(context.Background(), xtun.StackOptions{Tun: dev, MTU: uint32(mtu), Addresses: localAddresses, Remote: true}, handler) + if err != nil { + return nil, nil, err } + dev.stack = stack tnet := &Net{ - DialContextTCPAddrPort: dev.DialContextTCPAddrPort, + DialContextTCPAddrPort: stack.DialTCP, DialUDPAddrPort: dev.DialUDPAddrPort, - dnsServers: dev.dnsServers, - hasV4: dev.hasV4, - hasV6: dev.hasV6, + dnsServers: dnsServers, + } + for _, ip := range localAddresses { + if ip.Is4() { + tnet.hasV4 = true + } else if ip.Is6() { + tnet.hasV6 = true + } } dev.events <- tun.EventUp - return dev, tnet, dev.stack, nil + return dev, tnet, nil } func (tun *netTun) Name() (string, error) { @@ -128,73 +89,62 @@ func (tun *netTun) Events() <-chan tun.Event { } func (tun *netTun) Read(buf [][]byte, sizes []int, offset int) (int, error) { - var view *buffer.View select { - case view = <-tun.incomingPacket: + case packet := <-tun.packets: + sizes[0] = copy(buf[0][offset:], packet.Bytes()) + packet.Release() + return 1, nil case <-tun.closed: return 0, os.ErrClosed } - - n, err := view.Read(buf[0][offset:]) - view.Release() - if err != nil { - return 0, err - } - sizes[0] = n - return 1, nil } func (tun *netTun) Write(buf [][]byte, offset int) (int, error) { for _, buf := range buf { - packet := buf[offset:] - if len(packet) == 0 { - continue - } - - pkb := stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buffer.MakeWithData(packet)}) - switch packet[0] >> 4 { - case 4: - tun.ep.InjectInbound(header.IPv4ProtocolNumber, pkb) - case 6: - tun.ep.InjectInbound(header.IPv6ProtocolNumber, pkb) - default: - return 0, syscall.EAFNOSUPPORT - } + tun.stack.Input(buf[offset:]) } return len(buf), nil } -func (tun *netTun) WriteNotify() { - pkt := tun.ep.Read() - if pkt == nil { - return - } - - view := pkt.ToView() - pkt.DecRef() - +// WritePacket implements xtun.Tun. The packet is in the hands of the stack only until this returns, so it is copied +// for Read. The stack does not wait for the one who reads the device, unless that one is far behind: it answers +// what it is written inside Write, and whoever writes would wait with it and lose what comes meanwhile +func (tun *netTun) WritePacket(header, payload []byte) error { + packet := buf.NewWithSize(int32(len(header) + len(payload))) + packet.Write(header) + packet.Write(payload) select { - case tun.incomingPacket <- view: + case tun.packets <- packet: + return nil case <-tun.closed: - view.Release() + packet.Release() + return os.ErrClosed } } +// ReadPacket implements xtun.Tun. It is not called: the packets are handed to the stack by Write +func (tun *netTun) ReadPacket([]byte) ([]byte, error) { + <-tun.closed + return nil, os.ErrClosed +} + +// Start implements xtun.Tun +func (tun *netTun) Start() error { + return nil +} + +// Index implements xtun.Tun +func (tun *netTun) Index() (int, error) { + return 0, nil +} + func (tun *netTun) Close() error { - tun.stack.RemoveNIC(1) - tun.stack.Close() - tun.ep.RemoveNotify(tun.notifyHandle) - tun.ep.Close() - - if tun.events != nil { - close(tun.events) - } - - // we don't close incomingPacket, because WriteNotify may be mid-send on it (DNS lookup) and would panic. - if tun.closed != nil { + tun.closeOnce.Do(func() { + // before the stack ends its connections: nobody may be reading anymore what it writes for that close(tun.closed) - } - + tun.stack.Close() + close(tun.events) + }) return nil } @@ -206,17 +156,8 @@ func (tun *netTun) BatchSize() int { return 1 } -func (tun *netTun) DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (net.Conn, error) { - fa, pn := convertToFullAddr(addr) - return gonet.DialContextTCP(ctx, tun.stack, fa, pn) -} - func (tun *netTun) DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, error) { - var pn tcpip.NetworkProtocolNumber = ipv6.ProtocolNumber - if raddr.IsValid() || raddr.Port() > 0 { - _, pn = convertToFullAddr(raddr) - } - conn, err := gonet.DialUDP(tun.stack, nil, nil, pn) + conn, err := tun.stack.DialUDP() if err != nil { return nil, err } @@ -234,20 +175,6 @@ type Net struct { cache cache } -func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) { - var protoNumber tcpip.NetworkProtocolNumber - if endpoint.Addr().Is4() { - protoNumber = ipv4.ProtocolNumber - } else { - protoNumber = ipv6.ProtocolNumber - } - return tcpip.FullAddress{ - NIC: 1, - Addr: tcpip.AddrFromSlice(endpoint.Addr().AsSlice()), - Port: endpoint.Port(), - }, protoNumber -} - var ( errNoSuchHost = errors.New("no such host") errLameReferral = errors.New("lame referral") @@ -451,6 +378,8 @@ func (tnet *Net) exchange(ctx context.Context, server netip.Addr, q dnsmessage.Q return dnsmessage.Parser{}, dnsmessage.Header{}, err } } + // what is opened through the stack of Xray takes no deadline, it ends when it is closed + stop := context.AfterFunc(ctx, func() { c.Close() }) var p dnsmessage.Parser var h dnsmessage.Header if useUDP { @@ -458,8 +387,15 @@ func (tnet *Net) exchange(ctx context.Context, server netip.Addr, q dnsmessage.Q } else { p, h, err = dnsStreamRoundTrip(c, id, q, tcpReq) } + stop() c.Close() if err != nil { + if ctx.Err() == context.DeadlineExceeded { + // as from a connection that takes a deadline + err = &net.OpError{Op: "read", Net: c.RemoteAddr().Network(), Err: os.ErrDeadlineExceeded} + } else if ctx.Err() != nil { + err = ctx.Err() + } if err == context.Canceled { err = errCanceled } else if err == context.DeadlineExceeded { diff --git a/proxy/wireguard/netstack_test.go b/proxy/wireguard/netstack_test.go new file mode 100644 index 000000000..d64c38d4a --- /dev/null +++ b/proxy/wireguard/netstack_test.go @@ -0,0 +1,310 @@ +package wireguard + +import ( + "bytes" + "context" + "crypto/rand" + "encoding/binary" + "errors" + "io" + gonet "net" + "net/netip" + "strings" + "testing" + "time" + + "github.com/xtls/xray-core/common/net" + "golang.org/x/net/dns/dnsmessage" + "golang.zx2c4.com/wireguard/tun" +) + +type closeHandler struct{} + +func (closeHandler) HandleConnection(conn net.Conn, _ net.Destination) { conn.Close() } + +// onesSum is the Internet checksum of b on top of initial, not inverted. +func onesSum(b []byte, initial uint32) uint16 { + sum := initial + for ; len(b) >= 2; b = b[2:] { + sum += uint32(binary.BigEndian.Uint16(b)) + } + if len(b) == 1 { + sum += uint32(b[0]) << 8 + } + for sum>>16 != 0 { + sum = sum>>16 + sum&0xffff + } + return uint16(sum) +} + +// The inbound answers ICMP echo requests from peers locally, the way the TUN +// inbound does: ICMP is not proxied, but ping and connectivity checks through +// the tunnel get a reply instead of timing out. The request goes in through +// Write and the reply comes out of Read, as the WireGuard device uses them. +func TestNetTUNEchoReply(t *testing.T) { + for _, test := range []struct { + src, dst string + request byte + reply byte + }{ + {"10.66.0.2", "1.1.1.1", 8, 0}, + {"fd00::2", "2606:4700:4700::1111", 128, 129}, + } { + dev, _, err := CreateNetTUN([]netip.Addr{netip.MustParseAddr("10.66.0.1"), netip.MustParseAddr("fd00::1")}, nil, 1420, closeHandler{}) + if err != nil { + t.Fatal(err) + } + src, dst := netip.MustParseAddr(test.src), netip.MustParseAddr(test.dst) + payload := []byte("xray wireguard ping") + message := append([]byte{test.request, 0, 0, 0, 0x12, 0x34, 0, 7}, payload...) + + // The stack drops what has a wrong checksum, as what comes through a tunnel has come a long way. + var pkt []byte + if src.Is4() { + pkt = make([]byte, 20, 20+len(message)) + pkt[0] = 0x45 + binary.BigEndian.PutUint16(pkt[2:], uint16(20+len(message))) + pkt[8], pkt[9] = 64, 1 + copy(pkt[12:], src.AsSlice()) + copy(pkt[16:], dst.AsSlice()) + binary.BigEndian.PutUint16(pkt[10:], ^onesSum(pkt, 0)) + binary.BigEndian.PutUint16(message[2:], ^onesSum(message, 0)) + } else { + pkt = make([]byte, 40, 40+len(message)) + pkt[0] = 0x60 + binary.BigEndian.PutUint16(pkt[4:], uint16(len(message))) + pkt[6], pkt[7] = 58, 64 + copy(pkt[8:], src.AsSlice()) + copy(pkt[24:], dst.AsSlice()) + binary.BigEndian.PutUint16(message[2:], ^onesSum(message, uint32(onesSum(pkt[8:40], uint32(len(message))+58)))) + } + pkt = append(pkt, message...) + damaged := bytes.Clone(pkt) + damaged[len(damaged)-1] ^= 1 + + // The stack answers inside Write, the reply waits for whoever reads the device. + got := make(chan []byte, 1) + const offset = 16 + go func() { + b := make([]byte, 2048) + sizes := make([]int, 1) + if _, err := dev.Read([][]byte{b}, sizes, offset); err == nil { + got <- b[offset : offset+sizes[0]] + } + }() + if _, err := dev.Write([][]byte{append(make([]byte, offset), damaged...), append(make([]byte, offset), pkt...)}, offset); err != nil { + t.Fatal(err) + } + var reply []byte + select { + case reply = <-got: + case <-time.After(2 * time.Second): + t.Fatal("no echo reply from the stack") + } + + header := len(pkt) - len(message) + if len(reply) != len(pkt) || reply[0] != pkt[0] { + t.Fatalf("reply of %d bytes, version byte %#x", len(reply), reply[0]) + } + var replySrc, replyDst netip.Addr + pseudo := uint32(0) + if src.Is4() { + replySrc, replyDst = netip.AddrFrom4([4]byte(reply[12:16])), netip.AddrFrom4([4]byte(reply[16:20])) + if onesSum(reply[:20], 0) != 0xffff || reply[9] != 1 { + t.Fatal("bad ipv4 header in the reply") + } + } else { + replySrc, replyDst = netip.AddrFrom16([16]byte(reply[8:24])), netip.AddrFrom16([16]byte(reply[24:40])) + if reply[6] != 58 { + t.Fatalf("reply protocol %d, want icmpv6", reply[6]) + } + pseudo = uint32(onesSum(reply[8:40], uint32(len(message))+58)) + } + if replySrc != dst || replyDst != src { + t.Fatalf("reply addresses %v -> %v, want %v -> %v", replySrc, replyDst, dst, src) + } + echo := reply[header:] + if echo[0] != test.reply || echo[1] != 0 { + t.Fatalf("reply type %d code %d, want echo reply", echo[0], echo[1]) + } + // A reply to the damaged request, which came first, would not have this payload. + if !bytes.Equal(echo[4:], message[4:]) { + t.Fatalf("reply ident, seq and payload %x, want %x", echo[4:], message[4:]) + } + if onesSum(echo, pseudo) != 0xffff { + t.Fatal("bad icmp checksum in the reply") + } + + dev.Close() + dev.Close() + if _, err := dev.Read([][]byte{make([]byte, 2048)}, make([]int, 1), 0); err == nil { + t.Fatal("read from a closed device") + } + } +} + +// testServer answers what comes through the device of a server: names over UDP port 53, except those that +// start with "silent", and everything else with what it was sent. +type testServer struct{} + +func (testServer) HandleConnection(conn net.Conn, dest net.Destination) { + defer conn.Close() + b := make([]byte, 4096) + for { + n, err := conn.Read(b) + if err != nil { + return + } + answer := b[:n] + if dest.Network == net.Network_UDP && dest.Port == 53 { + var parser dnsmessage.Parser + header, err := parser.Start(b[:n]) + if err != nil { + continue + } + question, err := parser.Question() + if err != nil || strings.HasPrefix(question.Name.String(), "silent") { + continue + } + builder := dnsmessage.NewBuilder(nil, dnsmessage.Header{ID: header.ID, Response: true, RecursionAvailable: true}) + builder.StartQuestions() + builder.Question(question) + builder.StartAnswers() + builder.AResource(dnsmessage.ResourceHeader{Name: question.Name, Class: dnsmessage.ClassINET, TTL: 60}, dnsmessage.AResource{A: [4]byte{192, 0, 2, 1}}) + if answer, err = builder.Finish(); err != nil { + continue + } + } + if _, err := conn.Write(answer); err != nil { + return + } + } +} + +// A client and a server, each with a stack on its device and the two devices joined as a tunnel joins them: +// names are resolved, and TCP and UDP go through, as the outbound and the inbound use them. +func TestNetTUNClientAndServer(t *testing.T) { + server, _, err := CreateNetTUN([]netip.Addr{netip.MustParseAddr("10.66.0.1")}, nil, 1420, testServer{}) + if err != nil { + t.Fatal(err) + } + client, tnet, err := CreateNetTUN([]netip.Addr{netip.MustParseAddr("10.66.0.2")}, []netip.Addr{netip.MustParseAddr("10.66.0.53")}, 1420, nil) + if err != nil { + t.Fatal(err) + } + defer server.Close() + defer client.Close() + // As in a tunnel, what reads a device does not wait for the other end to take the packet: a stack answers + // inside Write, and its answer has to be read meanwhile. + for _, pair := range [][2]tun.Device{{client, server}, {server, client}} { + packets := make(chan []byte, 1024) + go func() { + defer close(packets) + bufs, sizes := [][]byte{make([]byte, 2048)}, make([]int, 1) + for { + if _, err := pair[0].Read(bufs, sizes, 0); err != nil { + return + } + select { + case packets <- bytes.Clone(bufs[0][:sizes[0]]): + default: + } + } + }() + go func() { + for packet := range packets { + pair[1].Write([][]byte{packet}, 0) + } + }() + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + if addrs, err := tnet.LookupContextHost(ctx, "xray.example"); err != nil || len(addrs) != 1 || addrs[0] != "192.0.2.1" { + t.Fatal("resolved to ", addrs, ", error: ", err) + } + // a server that does not answer is not waited for longer than the one who asks wants to wait + short, cancelShort := context.WithTimeout(ctx, 200*time.Millisecond) + start := time.Now() + var timeout *gonet.DNSError + if addrs, err := tnet.LookupContextHost(short, "silent.example"); !errors.As(err, &timeout) || !timeout.IsTimeout || time.Since(start) > 3*time.Second { + t.Fatal("resolved to ", addrs, " after ", time.Since(start), ", error: ", err) + } + cancelShort() + + sent := make([]byte, 1<<20) + rand.Read(sent) + for _, network := range []string{"tcp", "udp"} { + conn, err := tnet.DialContext(ctx, network, "xray.example:7") + if err != nil { + t.Fatal(err) + } + size := len(sent) + if network == "udp" { + // more than the tunnel takes in one packet + size = 3000 + } + go conn.Write(sent[:size]) + received := make([]byte, size) + conn.SetReadDeadline(time.Now().Add(10 * time.Second)) + done := make(chan error, 1) + go func() { + _, err := io.ReadFull(conn, received) + done <- err + }() + select { + case err := <-done: + if err != nil || !bytes.Equal(received, sent[:size]) { + t.Fatal(network, ": received something else than what was sent, error: ", err) + } + case <-ctx.Done(): + t.Fatal(network, ": what was sent did not come back") + } + conn.Close() + } +} + +// A packet of the stack is in its hands only until WritePacket returns, Read gets it as it was then. +// A device that is closed is not waited for, also not by a stack that has something to send when it is closed. +func TestNetTUNWritePacket(t *testing.T) { + dev, tnet, err := CreateNetTUN([]netip.Addr{netip.MustParseAddr("10.66.0.2")}, nil, 1420, nil) + if err != nil { + t.Fatal(err) + } + b, sizes := make([]byte, 16), make([]int, 1) + for i := range 1000 { + header, payload := []byte{byte(i), 1, 2, 3}, []byte{4, 5, 6, byte(i)} + go func() { + dev.(*netTun).WritePacket(header, payload) + clear(header) + clear(payload) + }() + if _, err := dev.Read([][]byte{b}, sizes, 0); err != nil || !bytes.Equal(b[:sizes[0]], []byte{byte(i), 1, 2, 3, 4, 5, 6, byte(i)}) { + t.Fatal("read ", b[:sizes[0]], " of packet ", i, ", error: ", err) + } + } + + // nobody reads now, and no more can wait to be read: a connection waits to send its SYN + for range netTunQueue { + dev.(*netTun).WritePacket([]byte{1}, nil) + } + dialed := make(chan error, 1) + go func() { + _, err := tnet.DialContextTCPAddrPort(context.Background(), netip.MustParseAddrPort("192.0.2.1:80")) + dialed <- err + }() + time.Sleep(50 * time.Millisecond) + closed := make(chan struct{}) + go func() { + dev.Close() + close(closed) + }() + select { + case <-closed: + case <-time.After(5 * time.Second): + t.Fatal("the device is not closed while a packet waits to be read") + } + if err := <-dialed; err == nil { + t.Fatal("a connection through a closed device") + } +} diff --git a/proxy/wireguard/server.go b/proxy/wireguard/server.go index 175948d36..fda0a837a 100644 --- a/proxy/wireguard/server.go +++ b/proxy/wireguard/server.go @@ -21,13 +21,13 @@ import ( "github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/stats" + xtun "github.com/xtls/xray-core/proxy/tun" "github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/stat" "golang.org/x/crypto/curve25519" "golang.zx2c4.com/wireguard/device" "golang.zx2c4.com/wireguard/tun" - "gvisor.dev/gvisor/pkg/tcpip/stack" ) type Server struct { @@ -43,10 +43,9 @@ type Server struct { uplinkCounter stats.Counter downlinkCounter stats.Counter - tun tun.Device - stack *stack.Stack - dev *device.Device - mu sync.Mutex + tun tun.Device + dev *device.Device + mu sync.Mutex pub [32]byte users *sync.Map @@ -95,11 +94,6 @@ func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) { return nil, err } - tun, _, stack, err := CreateNetTUN(localAddresses, nil, int(conf.Mtu), false) - if err != nil { - return nil, err - } - pri := common.Must2(ParseKey(conf.SecretKey)) var pub [32]byte curve25519.ScalarBaseMult(&pub, pri) @@ -126,15 +120,15 @@ func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) { uplinkCounter: uplinkCounter, downlinkCounter: downlinkCounter, - tun: tun, - stack: stack, - pub: pub, users: users, } - // Install the stack's protocol handlers before the device can deliver packets to it (Start -> dev.Up). - CreateForwarder(stack, s.HandleConnection) - CreateICMPEchoResponder(stack) + // The stack hands its connections to s from the first packet on, which the device delivers after Start -> dev.Up. + tun, _, err := CreateNetTUN(localAddresses, nil, int(conf.Mtu), s) + if err != nil { + return nil, err + } + s.tun = tun return s, nil } @@ -379,9 +373,13 @@ func (s *Server) HandleConnection(conn net.Conn, dest net.Destination) { }) errors.LogInfo(ctx, "processing from ", source, " to ", dest) + writer := buf.NewWriter(conn) + if dest.Network == net.Network_TCP { + writer = &xtun.TCPWriter{Writer: writer} + } link := &transport.Link{ Reader: &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)}, - Writer: buf.NewWriter(conn), + Writer: writer, } if err := s.dispatcher.DispatchLink(ctx, dest, link); err != nil { errors.LogError(ctx, errors.New("connection closed").Base(err)) diff --git a/proxy/wireguard/server_test.go b/proxy/wireguard/server_test.go new file mode 100644 index 000000000..218d4734a --- /dev/null +++ b/proxy/wireguard/server_test.go @@ -0,0 +1,71 @@ +package wireguard + +import ( + "context" + "io" + "net/netip" + "sync" + "sync/atomic" + "testing" + + "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/buf" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/protocol" + "github.com/xtls/xray-core/features/routing" + "github.com/xtls/xray-core/transport" +) + +// stackConn is a TCP connection as the stack hands it over: it is its own buf.Writer. +type stackConn struct { + net.Conn + closed atomic.Int32 +} + +func (c *stackConn) Read([]byte) (int, error) { return 0, io.EOF } + +func (c *stackConn) WriteMultiBuffer(mb buf.MultiBuffer) error { + buf.ReleaseMulti(mb) + return nil +} + +func (c *stackConn) Close() error { + c.closed.Add(1) + return nil +} + +func (c *stackConn) RemoteAddr() net.Addr { + return &net.TCPAddr{IP: net.IP{10, 0, 0, 2}, Port: 1234} +} + +// closingDispatcher does what an outbound does when the other side has no more to send. +type closingDispatcher struct { + routing.Dispatcher + conn *stackConn + closed int32 +} + +func (d *closingDispatcher) DispatchLink(_ context.Context, _ net.Destination, link *transport.Link) error { + common.Close(link.Writer) + d.closed = d.conn.closed.Load() + return nil +} + +func TestServerKeepsConnectionOpenWhenWriterCloses(t *testing.T) { + conn := &stackConn{} + dispatcher := &closingDispatcher{conn: conn} + users := &sync.Map{} + users.Store([32]byte{1}, &protocol.MemoryUser{ + Email: "love@xray.com", + Account: &MemoryAccount{AllowedIPs: []netip.Prefix{netip.MustParsePrefix("10.0.0.2/32")}}, + }) + s := &Server{ctx: context.Background(), dispatcher: dispatcher, users: users} + s.HandleConnection(conn, net.TCPDestination(net.ParseAddress("192.0.2.1"), 443)) + + if dispatcher.closed != 0 { + t.Fatal("the connection was closed with its writer, while the peer may still be sending") + } + if got := conn.closed.Load(); got != 1 { + t.Fatalf("connection closed %d times, want 1", got) + } +} diff --git a/proxy/wireguard/tun.go b/proxy/wireguard/tun.go index 2d3a4a970..fd906d34f 100644 --- a/proxy/wireguard/tun.go +++ b/proxy/wireguard/tun.go @@ -1,27 +1,12 @@ package wireguard import ( - "context" "fmt" - "io" "runtime" "strconv" "strings" - "sync" - "time" - "github.com/xtls/xray-core/common/buf" - "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" - "gvisor.dev/gvisor/pkg/buffer" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" - "gvisor.dev/gvisor/pkg/tcpip/checksum" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/stack" - "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" - "gvisor.dev/gvisor/pkg/tcpip/transport/udp" - "gvisor.dev/gvisor/pkg/waiter" ) func CalculateInterfaceName(name string) (tunName string) { @@ -48,334 +33,3 @@ func CalculateInterfaceName(name string) (tunName string) { tunName = fmt.Sprintf("%s%d", tunName, tunIndex) return } - -func CreateForwarder(gstack *stack.Stack, handler func(conn net.Conn, dest net.Destination)) { - gstack.SetPromiscuousMode(1, true) - gstack.SetSpoofing(1, true) - - tcpForwarder := tcp.NewForwarder(gstack, 0, 65535, func(r *tcp.ForwarderRequest) { - go func(r *tcp.ForwarderRequest) { - var wq waiter.Queue - id := r.ID() - - ep, err := r.CreateEndpoint(&wq) - if err != nil { - errors.LogError(context.Background(), err.String()) - r.Complete(true) - return - } - - options := ep.SocketOptions() - options.SetKeepAlive(false) - options.SetReuseAddress(true) - options.SetReusePort(true) - - handler(gonet.NewTCPConn(&wq, ep), net.TCPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort))) - - ep.Close() - r.Complete(false) - }(r) - }) - gstack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket) - - manager := &udpManager{ - stack: gstack, - handler: handler, - m: make(map[string]*udpConn), - } - - gstack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool { - data := pkt.Data().AsRange().ToSlice() - // if len(data) == 0 { - // return false - // } - srcIP := net.IPAddress(id.RemoteAddress.AsSlice()) - dstIP := net.IPAddress(id.LocalAddress.AsSlice()) - if srcIP == nil || dstIP == nil { - panic(id) - } - src := net.UDPDestination(srcIP, net.Port(id.RemotePort)) - dst := net.UDPDestination(dstIP, net.Port(id.LocalPort)) - manager.feed(src, dst, data) - return true - }) -} - -type udpManager struct { - stack *stack.Stack - handler func(conn net.Conn, dest net.Destination) - m map[string]*udpConn - mutex sync.RWMutex -} - -func (m *udpManager) feed(src net.Destination, dst net.Destination, data []byte) { - m.mutex.RLock() - uc, ok := m.m[src.NetAddr()] - if ok { - if !uc.queue.push(&packet{p: data, dest: &dst}) { - errors.LogDebug(context.Background(), "drop udp with size ", len(data), " to ", dst.NetAddr(), " original ", uc.dst.NetAddr(), " > queue full") - } - m.mutex.RUnlock() - return - } - m.mutex.RUnlock() - - m.mutex.Lock() - defer m.mutex.Unlock() - - uc, ok = m.m[src.NetAddr()] - if !ok { - uc = &udpConn{ - queue: newPacketQueue(udpQueueLimit), - src: src, - dst: dst, - } - uc.writeFunc = m.writeRawUDPPacket - uc.closeFunc = func() { - m.mutex.Lock() - m.close(uc) - m.mutex.Unlock() - } - m.m[src.NetAddr()] = uc - go m.handler(uc, dst) - } - - if !uc.queue.push(&packet{p: data, dest: &dst}) { - errors.LogDebug(context.Background(), "drop udp with size ", len(data), " to ", dst.NetAddr(), " original ", uc.dst.NetAddr(), " > queue full 2") - } -} - -func (m *udpManager) close(uc *udpConn) { - if !uc.closed { - uc.closed = true - uc.queue.close() - delete(m.m, uc.src.NetAddr()) - } -} - -func (m *udpManager) writeRawUDPPacket(payload []byte, src net.Destination, dst net.Destination) error { - udpLen := header.UDPMinimumSize + len(payload) - srcIP := tcpip.AddrFromSlice(src.Address.IP()) - dstIP := tcpip.AddrFromSlice(dst.Address.IP()) - - // build packet with appropriate IP header size - isIPv4 := dst.Address.Family().IsIPv4() - ipHdrSize := header.IPv6MinimumSize - ipProtocol := header.IPv6ProtocolNumber - if isIPv4 { - ipHdrSize = header.IPv4MinimumSize - ipProtocol = header.IPv4ProtocolNumber - } - - pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ - ReserveHeaderBytes: ipHdrSize + header.UDPMinimumSize, - Payload: buffer.MakeWithData(payload), - }) - defer pkt.DecRef() - - // Build UDP header - udpHdr := header.UDP(pkt.TransportHeader().Push(header.UDPMinimumSize)) - udpHdr.Encode(&header.UDPFields{ - SrcPort: uint16(src.Port), - DstPort: uint16(dst.Port), - Length: uint16(udpLen), - }) - - // Calculate and set UDP checksum - xsum := header.PseudoHeaderChecksum(header.UDPProtocolNumber, srcIP, dstIP, uint16(udpLen)) - udpHdr.SetChecksum(^udpHdr.CalculateChecksum(checksum.Checksum(payload, xsum))) - - // Build IP header - if isIPv4 { - ipHdr := header.IPv4(pkt.NetworkHeader().Push(header.IPv4MinimumSize)) - ipHdr.Encode(&header.IPv4Fields{ - TotalLength: uint16(header.IPv4MinimumSize + udpLen), - TTL: 64, - Protocol: uint8(header.UDPProtocolNumber), - SrcAddr: srcIP, - DstAddr: dstIP, - }) - ipHdr.SetChecksum(^ipHdr.CalculateChecksum()) - } else { - ipHdr := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize)) - ipHdr.Encode(&header.IPv6Fields{ - PayloadLength: uint16(udpLen), - TransportProtocol: header.UDPProtocolNumber, - HopLimit: 64, - SrcAddr: srcIP, - DstAddr: dstIP, - }) - } - - // dispatch the packet - err := m.stack.WriteRawPacket(1, ipProtocol, buffer.MakeWithView(pkt.ToView())) - if err != nil { - return errors.New("failed to write raw udp packet back to stack err ", err) - } - - return nil -} - -type packet struct { - p []byte - dest *net.Destination -} - -type udpConn struct { - queue *packetQueue - src net.Destination - dst net.Destination - writeFunc func(payload []byte, src net.Destination, dst net.Destination) error - closeFunc func() - closed bool -} - -func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) { - for { - q, ok := c.queue.pop() - if !ok { - return nil, io.EOF - } - - b := buf.New() - if _, err := b.Write(q.p); err != nil { - errors.LogErrorInner(context.Background(), err, "drop packet to ", q.dest, " with size ", len(q.p)) - b.Release() - continue - } - - b.UDP = q.dest - - return buf.MultiBuffer{b}, nil - } -} - -func (c *udpConn) Read(p []byte) (int, error) { - q, ok := c.queue.pop() - if !ok { - return 0, io.EOF - } - n := copy(p, q.p) - if n != len(q.p) { - return 0, io.ErrShortBuffer - } - return n, nil -} - -func (c *udpConn) WriteMultiBuffer(mb buf.MultiBuffer) error { - for i, b := range mb { - dst := c.dst - if b.UDP != nil { - if b.UDP.Address.Family().IsDomain() { - errors.LogError(context.Background(), "impossible domain packet ", b.UDP, " reply via original target ", dst) - } else { - dst = *b.UDP - } - } - err := c.writeFunc(b.Bytes(), dst, c.src) - if err != nil { - buf.ReleaseMulti(mb[i:]) - return err - } - b.Release() - } - return nil -} - -func (c *udpConn) Write(p []byte) (int, error) { - err := c.writeFunc(p, c.dst, c.src) - if err != nil { - return 0, err - } - return len(p), nil -} - -func (c *udpConn) Close() error { - c.closeFunc() - return nil -} - -func (c *udpConn) LocalAddr() net.Addr { - return c.dst.RawNetAddr() -} - -func (c *udpConn) RemoteAddr() net.Addr { - return c.src.RawNetAddr() -} - -func (c *udpConn) SetDeadline(t time.Time) error { - return nil -} - -func (c *udpConn) SetReadDeadline(t time.Time) error { - return nil -} - -func (c *udpConn) SetWriteDeadline(t time.Time) error { - return nil -} - -// udpQueueLimit bounds the packets waiting for one UDP flow; more are dropped. -const udpQueueLimit = 1024 - -// packetQueue holds the packets waiting for one UDP flow. Unlike a buffered -// channel of the same bound it only allocates for packets actually queued, so -// the many idle flows kept until the idle timeout cost next to nothing. -type packetQueue struct { - mu sync.Mutex - items []*packet - limit int - notify chan struct{} - closed bool -} - -func newPacketQueue(limit int) *packetQueue { - return &packetQueue{limit: limit, notify: make(chan struct{}, 1)} -} - -// push queues p and reports whether it was accepted. -func (q *packetQueue) push(p *packet) bool { - q.mu.Lock() - defer q.mu.Unlock() - if q.closed || len(q.items) >= q.limit { - return false - } - q.items = append(q.items, p) - select { - case q.notify <- struct{}{}: - default: - } - return true -} - -// pop blocks until a packet is queued or the queue is closed and drained. -func (q *packetQueue) pop() (*packet, bool) { - for { - q.mu.Lock() - if len(q.items) > 0 { - p := q.items[0] - q.items[0] = nil - q.items = q.items[1:] - if len(q.items) == 0 { - q.items = nil - } - q.mu.Unlock() - return p, true - } - if q.closed { - q.mu.Unlock() - return nil, false - } - q.mu.Unlock() - <-q.notify - } -} - -func (q *packetQueue) close() { - q.mu.Lock() - defer q.mu.Unlock() - if !q.closed { - q.closed = true - close(q.notify) - } -} diff --git a/testing/scenarios/masque_test.go b/testing/scenarios/masque_test.go index 95526a222..876ff0d2e 100644 --- a/testing/scenarios/masque_test.go +++ b/testing/scenarios/masque_test.go @@ -25,10 +25,6 @@ import ( "golang.org/x/net/http2" "golang.org/x/net/http2/hpack" "golang.org/x/sync/errgroup" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" - "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" - "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" "github.com/xtls/xray-core/app/log" "github.com/xtls/xray-core/app/proxyman" @@ -64,54 +60,31 @@ const ( masqueAuthorization = "Basic dUBleGFtcGxlLmNvbTpw" ) +// masqueEcho answers what comes to the echo port of the server, TCP and UDP alike +type masqueEcho struct{} + +func (masqueEcho) HandleConnection(conn net.Conn, dest net.Destination) { + defer conn.Close() + if dest.Port != masqueEchoPort { + return + } + b := make([]byte, 2048) + for { + n, err := conn.Read(b) + if err != nil { + return + } + if _, err := conn.Write(xor(b[:n])); err != nil { + return + } + } +} + func startMasqueServer(t *testing.T, h2 bool) (net.Port, [32]byte) { - dev, _, gstack, err := wireguard.CreateNetTUN([]netip.Addr{masqueServerV4, masqueServerV6}, nil, transmasque.MinPacketSize, false) + dev, _, err := wireguard.CreateNetTUN([]netip.Addr{masqueServerV4, masqueServerV6}, nil, transmasque.MinPacketSize, masqueEcho{}) common.Must(err) t.Cleanup(func() { dev.Close() }) - for _, addr := range []netip.Addr{masqueServerV4, masqueServerV6} { - proto := ipv4.ProtocolNumber - if addr.Is6() { - proto = ipv6.ProtocolNumber - } - local := tcpip.FullAddress{NIC: 1, Addr: tcpip.AddrFromSlice(addr.AsSlice()), Port: masqueEchoPort} - l, err := gonet.ListenTCP(gstack, local, proto) - common.Must(err) - go func() { - for { - c, err := l.Accept() - if err != nil { - return - } - go func() { - defer c.Close() - b := make([]byte, 2048) - for { - n, err := c.Read(b) - if err != nil { - return - } - if _, err := c.Write(xor(b[:n])); err != nil { - return - } - } - }() - } - }() - u, err := gonet.DialUDP(gstack, &local, nil, proto) - common.Must(err) - go func() { - b := make([]byte, 2048) - for { - n, addr, err := u.ReadFrom(b) - if err != nil { - return - } - u.WriteTo(xor(b[:n]), addr) - } - }() - } - var current atomic.Pointer[connectip.Conn] go func() { bufs := [][]byte{make([]byte, transmasque.MinPacketSize)} diff --git a/testing/scenarios/wireguard_test.go b/testing/scenarios/wireguard_test.go index ffb717cec..a3014c48e 100644 --- a/testing/scenarios/wireguard_test.go +++ b/testing/scenarios/wireguard_test.go @@ -1,14 +1,17 @@ package scenarios import ( + "encoding/hex" "testing" - //"time" + "time" "github.com/xtls/xray-core/app/log" "github.com/xtls/xray-core/app/proxyman" + "github.com/xtls/xray-core/app/router" "github.com/xtls/xray-core/common" clog "github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/serial" core "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/infra/conf" @@ -17,9 +20,18 @@ import ( "github.com/xtls/xray-core/proxy/wireguard" "github.com/xtls/xray-core/testing/servers/tcp" "github.com/xtls/xray-core/testing/servers/udp" - //"golang.org/x/sync/errgroup" + "golang.org/x/crypto/curve25519" + "golang.org/x/sync/errgroup" ) +func wireguardPublicKey(private string) string { + key, err := wireguard.ParseKey(private) + common.Must(err) + var public [32]byte + curve25519.ScalarBaseMult(&public, key) + return hex.EncodeToString(public[:]) +} + func TestWireguard(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, @@ -28,18 +40,44 @@ func TestWireguard(t *testing.T) { common.Must(err) defer tcpServer.Close() - serverPrivate, _ := conf.ParseWireGuardKey("EGs4lTSJPmgELx6YiJAmPR2meWi6bY+e9rTdCipSj10=") - serverPublic, _ := conf.ParseWireGuardKey("osAMIyil18HeZXGGBDC9KpZoM+L2iGyXWVSYivuM9B0=") - clientPrivate, _ := conf.ParseWireGuardKey("CPQSpgxgdQRZa5SUbT3HLv+mmDVHLW5YR/rQlzum/2I=") - clientPublic, _ := conf.ParseWireGuardKey("MmLJ5iHFVVBp7VsB0hxfpQ0wEzAbT2KQnpQpj0+RtBw=") + udpServer := udp.Server{ + MsgProcessor: xor, + } + udpDest, err := udpServer.Start() + common.Must(err) + defer udpServer.Close() + serverPrivate, _ := conf.ParseWireGuardKey("EGs4lTSJPmgELx6YiJAmPR2meWi6bY+e9rTdCipSj10=") + clientPrivate, _ := conf.ParseWireGuardKey("CPQSpgxgdQRZa5SUbT3HLv+mmDVHLW5YR/rQlzum/2I=") + + // What the client asks for is somewhere else, the stack of the server takes nothing that is for this machine. + redirect := func(tag string, dest net.Destination) *core.OutboundHandlerConfig { + return &core.OutboundHandlerConfig{ + Tag: tag, + ProxySettings: serial.ToTypedMessage(&freedom.Config{ + DestinationOverride: &freedom.DestinationOverride{ + Server: &protocol.ServerEndpoint{ + Address: net.NewIPOrDomain(dest.Address), + Port: uint32(dest.Port), + }, + }, + FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}, + }), + } + } serverPort := udp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ - ErrorLogLevel: clog.Severity_Debug, + ErrorLogLevel: clog.Severity_Warning, ErrorLogType: log.LogType_Console, }), + serial.ToTypedMessage(&router.Config{ + Rule: []*router.RoutingRule{ + {Networks: []net.Network{net.Network_TCP}, TargetTag: &router.RoutingRule_Tag{Tag: "tcp"}}, + {Networks: []net.Network{net.Network_UDP}, TargetTag: &router.RoutingRule_Tag{Tag: "udp"}}, + }, + }), }, Inbound: []*core.InboundHandlerConfig{ { @@ -48,60 +86,65 @@ func TestWireguard(t *testing.T) { Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&wireguard.DeviceConfig{ - IsClient: false, - NoKernelTun: false, - Endpoint: []string{"10.0.0.1"}, - Mtu: 1420, - SecretKey: serverPrivate, - Peers: []*wireguard.PeerConfig{{ - PublicKey: serverPublic, - AllowedIps: []string{"0.0.0.0/0", "::0/0"}, + IsClient: false, + Endpoint: []string{"10.0.0.1", "fd00::1"}, + Mtu: 1420, + SecretKey: serverPrivate, + Users: []*protocol.User{{ + Email: "love@xray.com", + Account: serial.ToTypedMessage(&wireguard.PeerConfig{ + PublicKey: wireguardPublicKey(clientPrivate), + AllowedIps: []string{"10.0.0.2/32", "fd00::2/128"}, + }), }}, }), }, }, Outbound: []*core.OutboundHandlerConfig{ - { - ProxySettings: serial.ToTypedMessage(&freedom.Config{ - FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}, - }), - SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{}), - }, + redirect("tcp", dest), + redirect("udp", udpDest), }, } clientPort := tcp.PickPort() + client6Port := tcp.PickPort() + clientUDPPort := udp.PickPort() + dokodemoInbound := func(port net.Port, dest net.Destination) *core.InboundHandlerConfig { + return &core.InboundHandlerConfig{ + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(port)}}, + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ + RewriteAddress: net.NewIPOrDomain(dest.Address), + RewritePort: uint32(dest.Port), + AllowedNetworks: []net.Network{dest.Network}, + }), + } + } clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ - ErrorLogLevel: clog.Severity_Debug, + ErrorLogLevel: clog.Severity_Warning, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ - { - ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ - PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}}, - Listen: net.NewIPOrDomain(net.LocalHostIP), - }), - ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ - RewriteAddress: net.NewIPOrDomain(dest.Address), - RewritePort: uint32(dest.Port), - AllowedNetworks: []net.Network{net.Network_TCP}, - }), - }, + dokodemoInbound(clientPort, net.TCPDestination(net.ParseAddress("192.0.2.1"), 7)), + dokodemoInbound(client6Port, net.TCPDestination(net.ParseAddress("2001:db8::1"), 7)), + dokodemoInbound(clientUDPPort, net.UDPDestination(net.ParseAddress("192.0.2.1"), 7)), }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&wireguard.DeviceConfig{ IsClient: true, - NoKernelTun: false, - Endpoint: []string{"10.0.0.2"}, + NoKernelTun: true, + Endpoint: []string{"10.0.0.2", "fd00::2"}, Mtu: 1420, SecretKey: clientPrivate, Peers: []*wireguard.PeerConfig{{ Endpoint: "127.0.0.1:" + serverPort.String(), - PublicKey: clientPublic, + PublicKey: wireguardPublicKey(serverPrivate), AllowedIps: []string{"0.0.0.0/0", "::0/0"}, }}, }), @@ -114,13 +157,13 @@ func TestWireguard(t *testing.T) { common.Must(err) defer CloseAllServers(servers) - // FIXME: for some reason wg server does not receive - - // var errg errgroup.Group - // for i := 0; i < 1; i++ { - // errg.Go(testTCPConn(clientPort, 1024, time.Second*2)) - // } - // if err := errg.Wait(); err != nil { - // t.Error(err) - // } + var errg errgroup.Group + for range 3 { + errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*20)) + } + errg.Go(testTCPConn(client6Port, 1024*1024, time.Second*20)) + errg.Go(testUDPConn(clientUDPPort, 1024, time.Second*5)) + if err := errg.Wait(); err != nil { + t.Error(err) + } }