From 0a5dead15426f978cc15de9fe94cd08960b54b59 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Sat, 25 Apr 2026 14:00:00 +0600 Subject: [PATCH 01/42] TLSSpoof: Add core package for fake ClientHello injection --- .../internet/tls/tlsspoof/client_hello.go | 75 ++++++++ transport/internet/tls/tlsspoof/endpoints.go | 27 +++ transport/internet/tls/tlsspoof/packet.go | 163 ++++++++++++++++ transport/internet/tls/tlsspoof/spoof.go | 182 ++++++++++++++++++ transport/internet/tls/tlsspoof/spoof_test.go | 111 +++++++++++ transport/internet/tls/tlsspoof/tcpip.go | 155 +++++++++++++++ 6 files changed, 713 insertions(+) create mode 100644 transport/internet/tls/tlsspoof/client_hello.go create mode 100644 transport/internet/tls/tlsspoof/endpoints.go create mode 100644 transport/internet/tls/tlsspoof/packet.go create mode 100644 transport/internet/tls/tlsspoof/spoof.go create mode 100644 transport/internet/tls/tlsspoof/spoof_test.go create mode 100644 transport/internet/tls/tlsspoof/tcpip.go diff --git a/transport/internet/tls/tlsspoof/client_hello.go b/transport/internet/tls/tlsspoof/client_hello.go new file mode 100644 index 000000000000..b078697c97cc --- /dev/null +++ b/transport/internet/tls/tlsspoof/client_hello.go @@ -0,0 +1,75 @@ +package tlsspoof + +import ( + "bytes" + "context" + "crypto/tls" + + "errors" + "net" + "time" +) + +type writeOnlyConn struct { + net.Conn + w *bytes.Buffer +} + +func (c *writeOnlyConn) Write(b []byte) (int, error) { + return c.w.Write(b) +} + +func (c *writeOnlyConn) Read(b []byte) (int, error) { + return 0, errors.New("read from write-only conn") +} + +func (c *writeOnlyConn) Close() error { + return nil +} + +func (c *writeOnlyConn) LocalAddr() net.Addr { + return nil +} + +func (c *writeOnlyConn) RemoteAddr() net.Addr { + return nil +} + +func (c *writeOnlyConn) SetDeadline(t time.Time) error { + return nil +} + +func (c *writeOnlyConn) SetReadDeadline(t time.Time) error { + return nil +} + +func (c *writeOnlyConn) SetWriteDeadline(t time.Time) error { + return nil +} + +// buildFakeClientHello drives crypto/tls against a write-only in-memory conn +// to capture a generated ClientHello. CurvePreferences pins classical groups +// to suppress Go's default X25519MLKEM768 hybrid key share; without this the +// post-quantum public key alone (~1184 bytes) pushes the record past one MSS, +// and middleboxes do not reassemble fragmented ClientHellos. The handshake +// error is discarded because the stub conn's Read returns immediately. +func buildFakeClientHello(sni string) ([]byte, error) { + if sni == "" { + return nil, errors.New("empty sni") + } + var buf bytes.Buffer + tlsConn := tls.Client(&writeOnlyConn{w: &buf}, &tls.Config{ + ServerName: sni, + // Order matches what browsers advertised before post-quantum. + CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP384}, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS13, + NextProtos: []string{"h2", "http/1.1"}, + InsecureSkipVerify: true, + }) + _ = tlsConn.HandshakeContext(context.Background()) + if buf.Len() == 0 { + return nil, errors.New("tls ClientHello not produced") + } + return buf.Bytes(), nil +} diff --git a/transport/internet/tls/tlsspoof/endpoints.go b/transport/internet/tls/tlsspoof/endpoints.go new file mode 100644 index 000000000000..ac0c30484226 --- /dev/null +++ b/transport/internet/tls/tlsspoof/endpoints.go @@ -0,0 +1,27 @@ +package tlsspoof + +import ( + "net" + "net/netip" + + "errors" +) + +// The returned addresses are v4-unmapped and share the same family. +func tcpEndpoints(conn net.Conn) (*net.TCPConn, netip.AddrPort, netip.AddrPort, error) { + tcpConn, isTCP := conn.(*net.TCPConn) + if !isTCP { + return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("tls_spoof: underlying conn is not *net.TCPConn") + } + local := tcpConn.LocalAddr().(*net.TCPAddr).AddrPort() + remote := tcpConn.RemoteAddr().(*net.TCPAddr).AddrPort() + if !local.IsValid() || !remote.IsValid() { + return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("tls_spoof: invalid conn address") + } + local = netip.AddrPortFrom(local.Addr().Unmap(), local.Port()) + remote = netip.AddrPortFrom(remote.Addr().Unmap(), remote.Port()) + if local.Addr().Is4() != remote.Addr().Is4() { + return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("tls_spoof: local/remote address family mismatch") + } + return tcpConn, local, remote, nil +} diff --git a/transport/internet/tls/tlsspoof/packet.go b/transport/internet/tls/tlsspoof/packet.go new file mode 100644 index 000000000000..5c23c0631ab8 --- /dev/null +++ b/transport/internet/tls/tlsspoof/packet.go @@ -0,0 +1,163 @@ +package tlsspoof + +import ( + "encoding/binary" + "net/netip" + + "fmt" +) + +const ( + defaultTTL uint8 = 64 + defaultWindowSize uint16 = 0xFFFF + tcpHeaderLen = TCPMinimumSize + + tcpOptionMD5Signature = 19 + tcpOptionMD5SignatureLength = 18 + tcpTimestampBackdate = 3600000 +) + +type spoofPacketInfo struct { + seqNum uint32 + ackNum uint32 + corrupt bool + options []byte +} + +func buildTCPSegment( + src netip.AddrPort, + dst netip.AddrPort, + packetInfo spoofPacketInfo, + payload []byte, +) []byte { + if src.Addr().Is4() != dst.Addr().Is4() { + panic("tlsspoof: mixed IPv4/IPv6 address family") + } + var ( + frame []byte + ipHeaderLen int + ) + ipPayloadLen := tcpHeaderLen + len(packetInfo.options) + len(payload) + if src.Addr().Is4() { + ipHeaderLen = IPv4MinimumSize + frame = make([]byte, ipHeaderLen+ipPayloadLen) + ip := IPv4(frame[:ipHeaderLen]) + ip.Encode(uint16(len(frame)), 0, defaultTTL, TCPProtocolNumber, src.Addr(), dst.Addr()) + } else { + ipHeaderLen = IPv6MinimumSize + frame = make([]byte, ipHeaderLen+ipPayloadLen) + ip := IPv6(frame[:ipHeaderLen]) + ip.Encode(uint16(ipPayloadLen), TCPProtocolNumber, defaultTTL, src.Addr(), dst.Addr()) + } + encodeTCP(frame, ipHeaderLen, src, dst, packetInfo, payload) + return frame +} + +func encodeTCP(frame []byte, ipHeaderLen int, src, dst netip.AddrPort, packetInfo spoofPacketInfo, payload []byte) { + tcp := TCP(frame[ipHeaderLen:]) + copy(frame[ipHeaderLen+tcpHeaderLen:], packetInfo.options) + optionsLen := len(packetInfo.options) + copy(frame[ipHeaderLen+tcpHeaderLen+optionsLen:], payload) + tcp.Encode(src.Port(), dst.Port(), packetInfo.seqNum, packetInfo.ackNum, uint8(tcpHeaderLen+optionsLen), TCPFlagAck|TCPFlagPsh, defaultWindowSize) + applyTCPChecksum(tcp, src.Addr(), dst.Addr(), payload, packetInfo.corrupt) +} + +func buildSpoofFrame(method Method, src, dst netip.AddrPort, sendNext, receiveNext, timestamp uint32, tcpOptions, payload []byte) ([]byte, error) { + packetInfo, err := resolveSpoofPacketInfo(method, sendNext, receiveNext, timestamp, tcpOptions, payload) + if err != nil { + return nil, err + } + return buildTCPSegment(src, dst, packetInfo, payload), nil +} + +// buildSpoofTCPSegment returns a TCP segment without an IP header, for +// platforms where the kernel synthesises the IP header (darwin IPv6). +func buildSpoofTCPSegment(method Method, src, dst netip.AddrPort, sendNext, receiveNext, timestamp uint32, payload []byte) ([]byte, error) { + packetInfo, err := resolveSpoofPacketInfo(method, sendNext, receiveNext, timestamp, nil, payload) + if err != nil { + return nil, err + } + segment := make([]byte, tcpHeaderLen+len(packetInfo.options)+len(payload)) + encodeTCP(segment, 0, src, dst, packetInfo, payload) + return segment, nil +} + +func resolveSpoofPacketInfo(method Method, sendNext, receiveNext, timestamp uint32, tcpOptions, payload []byte) (spoofPacketInfo, error) { + packetInfo := spoofPacketInfo{seqNum: sendNext, ackNum: receiveNext} + switch method { + case MethodWrongSequence: + packetInfo.seqNum = sendNext - uint32(len(payload)) + case MethodWrongChecksum: + packetInfo.corrupt = true + case MethodWrongAcknowledgment: + packetInfo.ackNum = receiveNext - uint32(defaultWindowSize/2) + case MethodWrongMD5Sig: + packetInfo.options = buildMD5SignatureOptions() + case MethodWrongTimestamp: + packetInfo.options = buildWrongTimestampOptions(timestamp, tcpOptions) + default: + return packetInfo, fmt.Errorf("tls_spoof: unknown method %v", method) + } + return packetInfo, nil +} + +func buildMD5SignatureOptions() []byte { + options := make([]byte, tcpOptionMD5SignatureLength+2) + options[0] = tcpOptionMD5Signature + options[1] = tcpOptionMD5SignatureLength + return options +} + +func buildWrongTimestampOptions(timestamp uint32, tcpOptions []byte) []byte { + spoofedTimestamp := timestamp + if spoofedTimestamp > tcpTimestampBackdate { + spoofedTimestamp -= tcpTimestampBackdate + } else { + spoofedTimestamp = 0 + } + if rewriteTCPOptionTimestamp(tcpOptions, spoofedTimestamp) { + return tcpOptions + } + options := make([]byte, TCPOptionTSLength+2) + EncodeTSOption(spoofedTimestamp, 0, options) + return options +} + +// rewriteTCPOptionTimestamp finds the TS option in tcpOptions and writes +// timestamp into its TSVal field in place. The caller must own tcpOptions +// (parseTCPPacket already returns a private copy on Windows). +func rewriteTCPOptionTimestamp(tcpOptions []byte, timestamp uint32) bool { + for i := 0; i < len(tcpOptions); { + switch tcpOptions[i] { + case TCPOptionEOL: + return false + case TCPOptionNOP: + i++ + continue + } + if i+1 >= len(tcpOptions) { + return false + } + optionLen := int(tcpOptions[i+1]) + if optionLen < 2 || i+optionLen > len(tcpOptions) { + return false + } + if tcpOptions[i] == TCPOptionTS && optionLen == TCPOptionTSLength { + binary.BigEndian.PutUint32(tcpOptions[i+2:], timestamp) + return true + } + i += optionLen + } + return false +} + +func applyTCPChecksum(tcp TCP, srcAddr, dstAddr netip.Addr, payload []byte, corrupt bool) { + tcpLen := int(tcp.DataOffset()) + len(payload) + pseudo := PseudoHeaderChecksum(TCPProtocolNumber, srcAddr.AsSlice(), dstAddr.AsSlice(), uint16(tcpLen)) + payloadChecksum := Checksum(payload, 0) + tcpChecksum := ^tcp.CalculateChecksum(CombineChecksum(pseudo, payloadChecksum)) + if corrupt { + tcpChecksum ^= 0xFFFF + } + tcp.SetChecksum(tcpChecksum) +} diff --git a/transport/internet/tls/tlsspoof/spoof.go b/transport/internet/tls/tlsspoof/spoof.go new file mode 100644 index 000000000000..6a9eae93a45b --- /dev/null +++ b/transport/internet/tls/tlsspoof/spoof.go @@ -0,0 +1,182 @@ +package tlsspoof + +import ( + "errors" + "fmt" + "net" + "runtime" + "syscall" +) + +type Method int + +const ( + MethodWrongSequence Method = iota + MethodWrongChecksum + MethodWrongAcknowledgment + MethodWrongMD5Sig + MethodWrongTimestamp +) + +const ( + MethodNameWrongSequence = "wrong-sequence" + MethodNameWrongChecksum = "wrong-checksum" + MethodNameWrongAcknowledgment = "wrong-ack" + MethodNameWrongMD5Sig = "wrong-md5" + MethodNameWrongTimestamp = "wrong-timestamp" +) + +func ParseOptions(spoof, method string) (string, Method, error) { + if spoof == "" { + if method != "" { + return "", 0, errors.New("spoof_method requires spoof") + } + return "", 0, nil + } + if net.ParseIP(spoof) != nil { + return "", 0, errors.New("tls_spoof: IP-literal server names are not allowed") + } + if !PlatformSupported { + return "", 0, errors.New("tls_spoof is not supported on this platform") + } + parsedMethod, err := ParseMethod(method) + if err != nil { + return "", 0, err + } + return spoof, parsedMethod, nil +} + +func ParseMethod(s string) (Method, error) { + switch s { + case "", MethodNameWrongSequence: + return MethodWrongSequence, nil + case MethodNameWrongChecksum: + return MethodWrongChecksum, nil + case MethodNameWrongAcknowledgment: + return MethodWrongAcknowledgment, nil + case MethodNameWrongMD5Sig: + return MethodWrongMD5Sig, nil + case MethodNameWrongTimestamp: + return MethodWrongTimestamp, nil + default: + return 0, fmt.Errorf("tls_spoof: unknown method: %s", s) + } +} + +func (m Method) String() string { + switch m { + case MethodWrongSequence: + return MethodNameWrongSequence + case MethodWrongChecksum: + return MethodNameWrongChecksum + case MethodWrongAcknowledgment: + return MethodNameWrongAcknowledgment + case MethodWrongMD5Sig: + return MethodNameWrongMD5Sig + case MethodWrongTimestamp: + return MethodNameWrongTimestamp + default: + return "unknown" + } +} + +type rawSpoofer interface { + Inject(payload []byte) error + Close() error +} + +type Conn struct { + net.Conn + spoofer rawSpoofer + fakeHello []byte + injectionCount int + maxInjections int // how many times to inject; default 1 +} + +// NewConn wraps a connection with TLS spoofing. maxInjections controls how +// many Write() calls will trigger a fake ClientHello injection (0 or 1 = single-shot). +func NewConn(conn net.Conn, method Method, fakeSNI string, maxInjections int) (*Conn, error) { + spoofer, err := newRawSpoofer(conn, method) + if err != nil { + return nil, wrapPermissionError(err) + } + result, err := newConn(conn, spoofer, fakeSNI, maxInjections) + if err != nil { + spoofer.Close() + return nil, err + } + return result, nil +} + +func newConn(conn net.Conn, spoofer rawSpoofer, fakeSNI string, maxInjections int) (*Conn, error) { + fakeHello, err := buildFakeClientHello(fakeSNI) + if err != nil { + return nil, func(err error, m string) error { return err }(err, "tls_spoof: build fake ClientHello") + } + if maxInjections <= 0 { + maxInjections = 1 + } + return &Conn{ + Conn: conn, + spoofer: spoofer, + fakeHello: fakeHello, + maxInjections: maxInjections, + }, nil +} + +func (c *Conn) Write(b []byte) (n int, err error) { + if c.injectionCount >= c.maxInjections { + return c.Conn.Write(b) + } + err = c.spoofer.Inject(c.fakeHello) + if err != nil { + return 0, func(err error, m string) error { return err }(err, "tls_spoof: inject") + } + c.injectionCount++ + if c.injectionCount >= c.maxInjections { + closeErr := c.spoofer.Close() + if closeErr != nil { + return 0, func(err error, m string) error { return err }(closeErr, "tls_spoof: close spoofer") + } + } + return c.Conn.Write(b) +} + +func (c *Conn) Close() error { + return func(e1, e2 error) error { + if e1 != nil { + return e1 + } + return e2 + }(c.Conn.Close(), c.spoofer.Close()) +} + +func (c *Conn) ReaderReplaceable() bool { + return true +} + +func (c *Conn) WriterReplaceable() bool { + return c.injectionCount >= c.maxInjections +} + +func (c *Conn) Upstream() any { + return c.Conn +} + +// wrapPermissionError adds platform-specific hints when the spoofer fails +// due to insufficient privileges. +func wrapPermissionError(err error) error { + if !errors.Is(err, syscall.EPERM) && !errors.Is(err, syscall.EACCES) { + return err + } + switch runtime.GOOS { + case "linux": + return fmt.Errorf("%w\n Hint: run as root, or grant capabilities:\n sudo setcap cap_net_raw,cap_net_admin+ep /path/to/xray", err) + case "darwin": + return fmt.Errorf("%w\n Hint: TLS spoofing requires root on macOS. Run with: sudo ./xray", err) + case "freebsd": + return fmt.Errorf("%w\n Hint: TLS spoofing requires root on FreeBSD. Run with: sudo ./xray", err) + default: + return err + } +} diff --git a/transport/internet/tls/tlsspoof/spoof_test.go b/transport/internet/tls/tlsspoof/spoof_test.go new file mode 100644 index 000000000000..c51e4fddceb0 --- /dev/null +++ b/transport/internet/tls/tlsspoof/spoof_test.go @@ -0,0 +1,111 @@ +package tlsspoof + +import ( + "testing" +) + +func TestBuildFakeClientHello(t *testing.T) { + hello, err := buildFakeClientHello("www.example.com") + if err != nil { + t.Fatal("buildFakeClientHello returned error:", err) + } + if len(hello) == 0 { + t.Fatal("buildFakeClientHello returned empty payload") + } + // TLS record header: content type 0x16 (handshake) + if hello[0] != 0x16 { + t.Fatalf("expected TLS handshake record type 0x16, got 0x%02x", hello[0]) + } + // TLS version: 0x0301 (TLS 1.0 record layer) + if hello[1] != 0x03 || hello[2] != 0x01 { + t.Fatalf("unexpected TLS record version: 0x%02x%02x", hello[1], hello[2]) + } + t.Logf("ClientHello payload length: %d bytes", len(hello)) +} + +func TestBuildFakeClientHelloEmptySNI(t *testing.T) { + _, err := buildFakeClientHello("") + if err == nil { + t.Fatal("expected error for empty SNI") + } +} + +func TestParseMethod(t *testing.T) { + tests := []struct { + input string + expected Method + hasErr bool + }{ + {"", MethodWrongSequence, false}, + {"wrong-sequence", MethodWrongSequence, false}, + {"wrong-checksum", MethodWrongChecksum, false}, + {"wrong-ack", MethodWrongAcknowledgment, false}, + {"wrong-md5", MethodWrongMD5Sig, false}, + {"wrong-timestamp", MethodWrongTimestamp, false}, + {"invalid", 0, true}, + } + for _, tt := range tests { + m, err := ParseMethod(tt.input) + if tt.hasErr { + if err == nil { + t.Errorf("ParseMethod(%q): expected error, got nil", tt.input) + } + continue + } + if err != nil { + t.Errorf("ParseMethod(%q): unexpected error: %v", tt.input, err) + continue + } + if m != tt.expected { + t.Errorf("ParseMethod(%q) = %v, want %v", tt.input, m, tt.expected) + } + } +} + +func TestParseOptions(t *testing.T) { + // Empty spoof should be a no-op + sni, _, err := ParseOptions("", "") + if err != nil { + t.Fatal("ParseOptions(\"\", \"\"): unexpected error:", err) + } + if sni != "" { + t.Fatalf("expected empty SNI, got %q", sni) + } + + // spoof_method without spoof should error + _, _, err = ParseOptions("", "wrong-checksum") + if err == nil { + t.Fatal("expected error when spoof_method set without spoof") + } + + // Valid combo + sni, method, err := ParseOptions("fake.example.com", "wrong-checksum") + if err != nil { + t.Fatal("ParseOptions: unexpected error:", err) + } + if sni != "fake.example.com" { + t.Fatalf("expected SNI 'fake.example.com', got %q", sni) + } + if method != MethodWrongChecksum { + t.Fatalf("expected MethodWrongChecksum, got %v", method) + } + + // IP-literal should be rejected + _, _, err = ParseOptions("1.2.3.4", "wrong-checksum") + if err == nil { + t.Fatal("expected error for IP-literal spoof") + } + _, _, err = ParseOptions("::1", "wrong-checksum") + if err == nil { + t.Fatal("expected error for IP-literal spoof") + } +} + +func TestMethodString(t *testing.T) { + if MethodWrongSequence.String() != "wrong-sequence" { + t.Fatalf("unexpected method string: %s", MethodWrongSequence.String()) + } + if MethodWrongChecksum.String() != "wrong-checksum" { + t.Fatalf("unexpected method string: %s", MethodWrongChecksum.String()) + } +} diff --git a/transport/internet/tls/tlsspoof/tcpip.go b/transport/internet/tls/tlsspoof/tcpip.go new file mode 100644 index 000000000000..62657ccefd68 --- /dev/null +++ b/transport/internet/tls/tlsspoof/tcpip.go @@ -0,0 +1,155 @@ +package tlsspoof + +import ( + "encoding/binary" + "net/netip" +) + +const ( + IPv4MinimumSize = 20 + IPv6MinimumSize = 40 + TCPMinimumSize = 20 + TCPProtocolNumber = 6 + + TCPOptionEOL = 0 + TCPOptionNOP = 1 + TCPOptionTS = 8 + TCPOptionTSLength = 10 + + TCPFlagFin = 0x01 + TCPFlagSyn = 0x02 + TCPFlagRst = 0x04 + TCPFlagPsh = 0x08 + TCPFlagAck = 0x10 +) + +func Checksum(data []byte, initial uint16) uint16 { + var csum uint32 = uint32(initial) + for i := 0; i < len(data)-1; i += 2 { + csum += uint32(binary.BigEndian.Uint16(data[i:])) + } + if len(data)%2 == 1 { + csum += uint32(data[len(data)-1]) << 8 + } + for csum > 0xffff { + csum = (csum >> 16) + (csum & 0xffff) + } + return uint16(csum) +} + +func PseudoHeaderChecksum(protocol uint8, srcAddr, dstAddr []byte, totalLen uint16) uint16 { + var csum uint32 + for i := 0; i < len(srcAddr); i += 2 { + csum += uint32(binary.BigEndian.Uint16(srcAddr[i:])) + } + for i := 0; i < len(dstAddr); i += 2 { + csum += uint32(binary.BigEndian.Uint16(dstAddr[i:])) + } + csum += uint32(protocol) + csum += uint32(totalLen) + for csum > 0xffff { + csum = (csum >> 16) + (csum & 0xffff) + } + return uint16(csum) +} + +func CombineChecksum(c1, c2 uint16) uint16 { + csum := uint32(c1) + uint32(c2) + for csum > 0xffff { + csum = (csum >> 16) + (csum & 0xffff) + } + return uint16(csum) +} + +func EncodeTSOption(val uint32, ecr uint32, b []byte) { + b[0] = TCPOptionTS + b[1] = TCPOptionTSLength + binary.BigEndian.PutUint32(b[2:], val) + binary.BigEndian.PutUint32(b[6:], ecr) +} + +func ParseTCPOptions(b []byte) (tsVal uint32, hasTS bool) { + for i := 0; i < len(b); { + if b[i] == TCPOptionEOL { + break + } + if b[i] == TCPOptionNOP { + i++ + continue + } + if i+1 >= len(b) { + break + } + optLen := int(b[i+1]) + if optLen < 2 || i+optLen > len(b) { + break + } + if b[i] == TCPOptionTS && optLen == TCPOptionTSLength { + return binary.BigEndian.Uint32(b[i+2:]), true + } + i += optLen + } + return 0, false +} + +// IPv4 header representation +type IPv4 []byte + +func (b IPv4) TotalLength() uint16 { return binary.BigEndian.Uint16(b[2:]) } +func (b IPv4) Flags() uint8 { return uint8(binary.BigEndian.Uint16(b[6:]) >> 13) } +func (b IPv4) FragmentOffset() uint16 { return binary.BigEndian.Uint16(b[6:]) & 0x1fff } +func (b IPv4) Protocol() uint8 { return b[9] } +func (b IPv4) HeaderLength() uint8 { return (b[0] & 0x0f) * 4 } + +func (b IPv4) Encode(totalLength uint16, id uint16, ttl uint8, protocol uint8, src, dst netip.Addr) { + b[0] = (4 << 4) | 5 // IPv4, Header Length = 20 + b[1] = 0 // TOS + binary.BigEndian.PutUint16(b[2:], totalLength) + binary.BigEndian.PutUint16(b[4:], id) + binary.BigEndian.PutUint16(b[6:], 0) // Flags and Fragment Offset + b[8] = ttl + b[9] = protocol + b[10] = 0 // Checksum (0 for calculation) + copy(b[12:16], src.AsSlice()) + copy(b[16:20], dst.AsSlice()) + csum := Checksum(b[:20], 0) + binary.BigEndian.PutUint16(b[10:], ^csum) +} + +type IPv6 []byte + +func (b IPv6) PayloadLength() uint16 { return binary.BigEndian.Uint16(b[4:]) } +func (b IPv6) TransportProtocol() uint8 { return b[6] } + +func (b IPv6) Encode(payloadLength uint16, transportProtocol uint8, hopLimit uint8, src, dst netip.Addr) { + binary.BigEndian.PutUint32(b[0:], 6<<28) // Version 6, Traffic Class 0, Flow Label 0 + binary.BigEndian.PutUint16(b[4:], payloadLength) + b[6] = transportProtocol + b[7] = hopLimit + copy(b[8:24], src.AsSlice()) + copy(b[24:40], dst.AsSlice()) +} + +type TCP []byte + +func (b TCP) DataOffset() uint8 { return (b[12] >> 4) * 4 } +func (b TCP) SequenceNumber() uint32 { return binary.BigEndian.Uint32(b[4:]) } +func (b TCP) AckNumber() uint32 { return binary.BigEndian.Uint32(b[8:]) } +func (b TCP) Options() []byte { return b[TCPMinimumSize:b.DataOffset()] } +func (b TCP) SetChecksum(csum uint16) { binary.BigEndian.PutUint16(b[16:], csum) } + +func (b TCP) Encode(srcPort, dstPort uint16, seqNum, ackNum uint32, dataOffset uint8, flags uint8, windowSize uint16) { + binary.BigEndian.PutUint16(b[0:], srcPort) + binary.BigEndian.PutUint16(b[2:], dstPort) + binary.BigEndian.PutUint32(b[4:], seqNum) + binary.BigEndian.PutUint32(b[8:], ackNum) + b[12] = (dataOffset / 4) << 4 + b[13] = flags + binary.BigEndian.PutUint16(b[14:], windowSize) + b[16] = 0 // Checksum + binary.BigEndian.PutUint16(b[18:], 0) // Urgent pointer +} + +func (b TCP) CalculateChecksum(initial uint16) uint16 { + return Checksum(b, initial) +} From 97d6adf1064e5172d3aa17b8be1ac2e9b9595e26 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:30:00 +0600 Subject: [PATCH 02/42] TLSSpoof: Add raw socket spoofers for Linux, Darwin and FreeBSD --- transport/internet/tls/tlsspoof/raw_darwin.go | 198 ++++++++++++++++++ .../internet/tls/tlsspoof/raw_freebsd.go | 172 +++++++++++++++ transport/internet/tls/tlsspoof/raw_linux.go | 166 +++++++++++++++ transport/internet/tls/tlsspoof/raw_stub.go | 15 ++ transport/internet/tls/tlsspoof/raw_unix.go | 25 +++ .../tls/tlsspoof/spoof_freebsd_test.go | 82 ++++++++ 6 files changed, 658 insertions(+) create mode 100644 transport/internet/tls/tlsspoof/raw_darwin.go create mode 100644 transport/internet/tls/tlsspoof/raw_freebsd.go create mode 100644 transport/internet/tls/tlsspoof/raw_linux.go create mode 100644 transport/internet/tls/tlsspoof/raw_stub.go create mode 100644 transport/internet/tls/tlsspoof/raw_unix.go create mode 100644 transport/internet/tls/tlsspoof/spoof_freebsd_test.go diff --git a/transport/internet/tls/tlsspoof/raw_darwin.go b/transport/internet/tls/tlsspoof/raw_darwin.go new file mode 100644 index 000000000000..3b45d17023be --- /dev/null +++ b/transport/internet/tls/tlsspoof/raw_darwin.go @@ -0,0 +1,198 @@ +package tlsspoof + +import ( + "encoding/binary" + "net" + "net/netip" + "strconv" + "strings" + "sync" + "syscall" + + "errors" + "fmt" + + "golang.org/x/sys/unix" +) + +const PlatformSupported = true + +// Offsets into xinpcb_n within each net.inet.tcp.pcblist_n record, identical +// to the values used by common/process/searcher_darwin_shared.go. +const ( + darwinXinpgenSize = 24 + darwinXsocketOffset = 104 + darwinXinpcbForeignPort = 16 + darwinXinpcbLocalPort = 18 + darwinXinpcbVFlag = 44 + darwinXinpcbForeignAddr = 48 + darwinXinpcbLocalAddr = 64 + darwinXinpcbIPv4Offset = 12 + + darwinTCPExtraSize = 208 + + darwinXtcpcbSndNxtOffset = 56 + darwinXtcpcbRcvNxtOffset = 80 +) + +// darwinStructSize returns the size of xinpcb_n for the running Darwin kernel. +// Darwin 22 (macOS 13 Ventura) grew the struct from 384 to 408 bytes; there is +// no ABI-stable way to read it, so we key off the kernel version. +var darwinStructSize = sync.OnceValues(func() (int, error) { + value, err := syscall.Sysctl("kern.osrelease") + if err != nil { + return 0, func(err error, m string) error { return err }(err, "sysctl kern.osrelease") + } + major, _, ok := strings.Cut(value, ".") + if !ok { + return 0, fmt.Errorf("unexpected kern.osrelease format: %s", value) + } + n, err := strconv.ParseInt(major, 10, 64) + if err != nil { + return 0, func(err error, m string) error { return err }(err, "parse kern.osrelease major version: ") + } + if n >= 22 { + return 408, nil + } + return 384, nil +}) + +type darwinSpoofer struct { + method Method + src netip.AddrPort + dst netip.AddrPort + rawFD int + rawSockAddr unix.Sockaddr + sendNext uint32 + receiveNext uint32 +} + +func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { + if method == MethodWrongTimestamp { + return nil, errors.New("tls_spoof: wrong-timestamp is not supported on macOS") + } + _, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + fd, sockaddr, err := openDarwinRawSocket(src, dst) + if err != nil { + return nil, err + } + sendNext, receiveNext, err := readDarwinTCPSequence(src, dst) + if err != nil { + unix.Close(fd) + return nil, err + } + return &darwinSpoofer{ + method: method, + src: src, + dst: dst, + rawFD: fd, + rawSockAddr: sockaddr, + sendNext: sendNext, + receiveNext: receiveNext, + }, nil +} + +// readDarwinTCPSequence scans net.inet.tcp.pcblist_n for the PCB that matches +// src -> dst and returns (snd_nxt, rcv_nxt). These live in xtcpcb_n at the end +// of each record; see darwin-xnu bsd/netinet/in_pcblist.c:get_pcblist_n. +func readDarwinTCPSequence(src, dst netip.AddrPort) (uint32, uint32, error) { + buffer, err := unix.SysctlRaw("net.inet.tcp.pcblist_n") + if err != nil { + return 0, 0, func(err error, m string) error { return err }(err, "sysctl net.inet.tcp.pcblist_n") + } + structSize, err := darwinStructSize() + if err != nil { + return 0, 0, err + } + itemSize := structSize + darwinTCPExtraSize + for i := darwinXinpgenSize; i+itemSize <= len(buffer); i += itemSize { + inpcb := buffer[i : i+darwinXsocketOffset] + xtcpcb := buffer[i+structSize : i+itemSize] + localPort := binary.BigEndian.Uint16(inpcb[darwinXinpcbLocalPort : darwinXinpcbLocalPort+2]) + remotePort := binary.BigEndian.Uint16(inpcb[darwinXinpcbForeignPort : darwinXinpcbForeignPort+2]) + if localPort != src.Port() || remotePort != dst.Port() { + continue + } + versionFlag := inpcb[darwinXinpcbVFlag] + var localAddr, remoteAddr netip.Addr + switch { + case versionFlag&0x1 != 0: + localAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset : darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset+4])) + remoteAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset : darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset+4])) + case versionFlag&0x2 != 0: + localAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbLocalAddr : darwinXinpcbLocalAddr+16])) + remoteAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbForeignAddr : darwinXinpcbForeignAddr+16])) + default: + continue + } + if localAddr.Unmap() != src.Addr() || remoteAddr.Unmap() != dst.Addr() { + continue + } + sendNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbSndNxtOffset : darwinXtcpcbSndNxtOffset+4]) + receiveNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbRcvNxtOffset : darwinXtcpcbRcvNxtOffset+4]) + return sendNext, receiveNext, nil + } + return 0, 0, fmt.Errorf("tls_spoof: connection %v->%v not found in pcblist_n", src, dst) +} + +func openDarwinRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) { + if dst.Addr().Is4() { + return openIPv4RawSocket(dst) + } + // macOS does not accept IPV6_HDRINCL on AF_INET6 SOCK_RAW IPPROTO_TCP + // sockets, so the kernel builds the IPv6 header itself. Bind to the real + // connection's source address so in6_selectsrc returns it, and rely on + // in6p_cksum defaulting to -1 so the user-supplied TCP checksum is + // preserved (including deliberately corrupted ones). + fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, func(err error, m string) error { return err }(err, "open AF_INET6 SOCK_RAW") + } + err = unix.Bind(fd, &unix.SockaddrInet6{Addr: src.Addr().As16()}) + if err != nil { + unix.Close(fd) + return -1, nil, func(err error, m string) error { return err }(err, "bind AF_INET6 SOCK_RAW") + } + sockaddr := &unix.SockaddrInet6{Port: int(dst.Port()), Addr: dst.Addr().As16()} + return fd, sockaddr, nil +} + +func (s *darwinSpoofer) Inject(payload []byte) error { + if !s.src.Addr().Is4() { + segment, err := buildSpoofTCPSegment(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, payload) + if err != nil { + return err + } + err = unix.Sendto(s.rawFD, segment, 0, s.rawSockAddr) + if err != nil { + return func(err error, m string) error { return err }(err, "sendto raw socket") + } + return nil + } + frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, nil, payload) + if err != nil { + return err + } + // Darwin inherits the historical BSD quirk: with IP_HDRINCL the kernel + // expects ip_len and ip_off in host byte order, not network byte order. + ip := IPv4(frame) + binary.NativeEndian.PutUint16(ip[2:4], ip.TotalLength()) + binary.NativeEndian.PutUint16(ip[6:8], uint16(ip.Flags())<<13|ip.FragmentOffset()) + err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) + if err != nil { + return func(err error, m string) error { return err }(err, "sendto raw socket") + } + return nil +} + +func (s *darwinSpoofer) Close() error { + if s.rawFD < 0 { + return nil + } + err := unix.Close(s.rawFD) + s.rawFD = -1 + return err +} diff --git a/transport/internet/tls/tlsspoof/raw_freebsd.go b/transport/internet/tls/tlsspoof/raw_freebsd.go new file mode 100644 index 000000000000..c38a249bf721 --- /dev/null +++ b/transport/internet/tls/tlsspoof/raw_freebsd.go @@ -0,0 +1,172 @@ +package tlsspoof + +import ( + "encoding/binary" + "errors" + "fmt" + "net" + "net/netip" + "syscall" + "unsafe" + + "golang.org/x/sys/unix" +) + +const PlatformSupported = true + +// FreeBSD tcp_info offsets for snd_nxt and rcv_nxt. +// Derived from FreeBSD sys/netinet/tcp.h struct tcp_info layout. +// +// struct tcp_info { +// u8 state, __ca, __retrans, __probes, __backoff, opts, wscale = 8 bytes (with pad) +// u32 rto, __ato, snd_mss, rcv_mss = 16 bytes (offset 8) +// u32 __unacked, __sacked, __lost, __retrans, __fackets = 20 bytes (offset 24) +// u32 __last_data_sent, __last_ack_sent, last_data_recv, __last_ack_recv = 16 bytes (offset 44) +// u32 __pmtu, __rcv_ssthresh, rtt, rttvar, snd_ssthresh, snd_cwnd, __advmss, __reordering = 32 bytes (offset 60) +// u32 __rcv_rtt, rcv_space = 8 bytes (offset 92) +// u32 snd_wnd, snd_bwnd = 8 bytes (offset 100) +// u32 snd_nxt, rcv_nxt = 8 bytes (offset 108) +// ... remaining fields +// } +const ( + freebsdTCPInfoSndNxtOffset = 108 + freebsdTCPInfoRcvNxtOffset = 112 + freebsdTCPInfoMinSize = 116 // must read at least through rcv_nxt +) + +type freebsdSpoofer struct { + method Method + src netip.AddrPort + dst netip.AddrPort + rawFD int + rawSockAddr unix.Sockaddr + sendNext uint32 + receiveNext uint32 +} + +func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { + if method == MethodWrongTimestamp { + return nil, errors.New("tls_spoof: wrong-timestamp is not supported on FreeBSD") + } + tcpConn, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + fd, sockaddr, err := openFreeBSDRawSocket(src, dst) + if err != nil { + return nil, err + } + sendNext, receiveNext, err := readFreeBSDTCPSequence(tcpConn) + if err != nil { + unix.Close(fd) + return nil, err + } + return &freebsdSpoofer{ + method: method, + src: src, + dst: dst, + rawFD: fd, + rawSockAddr: sockaddr, + sendNext: sendNext, + receiveNext: receiveNext, + }, nil +} + +// readFreeBSDTCPSequence retrieves snd_nxt and rcv_nxt via TCP_INFO getsockopt. +func readFreeBSDTCPSequence(conn *net.TCPConn) (uint32, uint32, error) { + raw, err := conn.SyscallConn() + if err != nil { + return 0, 0, fmt.Errorf("tls_spoof: SyscallConn: %w", err) + } + var sendNext, receiveNext uint32 + var sockErr error + err = raw.Control(func(fd uintptr) { + buf := make([]byte, 256) // generous buffer for tcp_info + bufLen := uint32(len(buf)) + _, _, errno := syscall.Syscall6( + syscall.SYS_GETSOCKOPT, + fd, + uintptr(syscall.IPPROTO_TCP), + uintptr(0x20), // TCP_INFO = 0x20 + uintptr(unsafe.Pointer(&buf[0])), + uintptr(unsafe.Pointer(&bufLen)), + 0, + ) + if errno != 0 { + sockErr = fmt.Errorf("tls_spoof: getsockopt TCP_INFO: %w", errno) + return + } + if bufLen < freebsdTCPInfoMinSize { + sockErr = fmt.Errorf("tls_spoof: TCP_INFO too short: %d < %d", bufLen, freebsdTCPInfoMinSize) + return + } + sendNext = binary.NativeEndian.Uint32(buf[freebsdTCPInfoSndNxtOffset : freebsdTCPInfoSndNxtOffset+4]) + receiveNext = binary.NativeEndian.Uint32(buf[freebsdTCPInfoRcvNxtOffset : freebsdTCPInfoRcvNxtOffset+4]) + }) + if err != nil { + return 0, 0, err + } + if sockErr != nil { + return 0, 0, sockErr + } + return sendNext, receiveNext, nil +} + +func openFreeBSDRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) { + if dst.Addr().Is4() { + return openIPv4RawSocket(dst) + } + // FreeBSD, like macOS, does not support IPV6_HDRINCL on SOCK_RAW/IPPROTO_TCP. + // The kernel constructs the IPv6 header. Bind to the source address + // and let the kernel fill in the IPv6 header automatically. + fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, fmt.Errorf("tls_spoof: open AF_INET6 SOCK_RAW: %w", err) + } + err = unix.Bind(fd, &unix.SockaddrInet6{Addr: src.Addr().As16()}) + if err != nil { + unix.Close(fd) + return -1, nil, fmt.Errorf("tls_spoof: bind AF_INET6 SOCK_RAW: %w", err) + } + sockaddr := &unix.SockaddrInet6{Port: int(dst.Port()), Addr: dst.Addr().As16()} + return fd, sockaddr, nil +} + +func (s *freebsdSpoofer) Inject(payload []byte) error { + if !s.src.Addr().Is4() { + // IPv6: kernel builds the IP header, we supply TCP segment only. + segment, err := buildSpoofTCPSegment(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, payload) + if err != nil { + return err + } + err = unix.Sendto(s.rawFD, segment, 0, s.rawSockAddr) + if err != nil { + return fmt.Errorf("tls_spoof: sendto raw socket: %w", err) + } + return nil + } + // IPv4: we build the full IP+TCP frame with IP_HDRINCL. + frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, nil, payload) + if err != nil { + return err + } + // FreeBSD inherits the historical BSD quirk: with IP_HDRINCL the kernel + // expects ip_len and ip_off in host byte order, not network byte order. + ip := IPv4(frame) + binary.NativeEndian.PutUint16(ip[2:4], ip.TotalLength()) + binary.NativeEndian.PutUint16(ip[6:8], uint16(ip.Flags())<<13|ip.FragmentOffset()) + err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) + if err != nil { + return fmt.Errorf("tls_spoof: sendto raw socket: %w", err) + } + return nil +} + +func (s *freebsdSpoofer) Close() error { + if s.rawFD < 0 { + return nil + } + err := unix.Close(s.rawFD) + s.rawFD = -1 + return err +} diff --git a/transport/internet/tls/tlsspoof/raw_linux.go b/transport/internet/tls/tlsspoof/raw_linux.go new file mode 100644 index 000000000000..dc5c7311869c --- /dev/null +++ b/transport/internet/tls/tlsspoof/raw_linux.go @@ -0,0 +1,166 @@ +package tlsspoof + +import ( + "fmt" + "net" + "net/netip" + + "golang.org/x/sys/unix" +) + +const PlatformSupported = true + +const ( + // Values of enum { TCP_NO_QUEUE, TCP_RECV_QUEUE, TCP_SEND_QUEUE } from + // include/net/tcp.h; not exported by golang.org/x/sys/unix. + tcpRecvQueue = 1 + tcpSendQueue = 2 +) + +type linuxSpoofer struct { + method Method + src netip.AddrPort + dst netip.AddrPort + rawFD int + rawSockAddr unix.Sockaddr + sendNext uint32 + receiveNext uint32 + timestamp uint32 +} + +func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { + tcpConn, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + fd, sockaddr, err := openLinuxRawSocket(dst) + if err != nil { + return nil, err + } + spoofer := &linuxSpoofer{ + method: method, + src: src, + dst: dst, + rawFD: fd, + rawSockAddr: sockaddr, + } + err = spoofer.loadSequenceNumbers(tcpConn) + if err != nil { + unix.Close(fd) + return nil, err + } + return spoofer, nil +} + +func openLinuxRawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) { + if dst.Addr().Is4() { + return openIPv4RawSocket(dst) + } + fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, func(err error, m string) error { return err }(err, "open AF_INET6 SOCK_RAW") + } + err = unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_HDRINCL, 1) + if err != nil { + unix.Close(fd) + return -1, nil, func(err error, m string) error { return err }(err, "set IPV6_HDRINCL") + } + // Linux raw IPv6 sockets interpret sin6_port as a nexthdr protocol number + // (see raw(7)); any value other than 0 or the socket's IPPROTO_TCP causes + // sendto to fail with EINVAL. The destination is already encoded in the + // user-supplied IPv6 header under IPV6_HDRINCL. + sockaddr := &unix.SockaddrInet6{Addr: dst.Addr().As16()} + return fd, sockaddr, nil +} + +// loadSequenceNumbers puts the socket briefly into TCP_REPAIR mode to read +// snd_nxt and rcv_nxt from the kernel. TCP_REPAIR requires CAP_NET_ADMIN; +// callers must run as root or grant both CAP_NET_RAW and CAP_NET_ADMIN. +// +// If the TCP_REPAIR_OFF revert fails, the socket would stay in TCP_REPAIR +// state and subsequent Write() calls would silently buffer instead of sending. +// Surface that error so callers can abort. +func (s *linuxSpoofer) loadSequenceNumbers(tcpConn *net.TCPConn) error { + rawConn, err := tcpConn.SyscallConn() + if err != nil { + return err + } + var ctrlErr error + err = rawConn.Control(func(raw uintptr) { + fd := int(raw) + + if s.method == MethodWrongTimestamp { + timestamp, tsErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_TIMESTAMP) + if tsErr != nil { + ctrlErr = fmt.Errorf("tls_spoof: read timestamp: %w", tsErr) + return + } + s.timestamp = uint32(timestamp) + } + + ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR, unix.TCP_REPAIR_ON) + if ctrlErr != nil { + ctrlErr = fmt.Errorf("tls_spoof: enter TCP_REPAIR (need CAP_NET_ADMIN): %w", ctrlErr) + return + } + defer func() { + offErr := unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR, unix.TCP_REPAIR_OFF) + if offErr != nil { + offErr = fmt.Errorf("tls_spoof: leave TCP_REPAIR: %w", offErr) + if ctrlErr == nil { + ctrlErr = offErr + } else { + ctrlErr = fmt.Errorf("%v; also %w", ctrlErr, offErr) + } + } + }() + + ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR_QUEUE, tcpSendQueue) + if ctrlErr != nil { + ctrlErr = fmt.Errorf("tls_spoof: select TCP_SEND_QUEUE: %w", ctrlErr) + return + } + sendSequence, seqErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_QUEUE_SEQ) + if seqErr != nil { + ctrlErr = fmt.Errorf("tls_spoof: read send queue sequence: %w", seqErr) + return + } + ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR_QUEUE, tcpRecvQueue) + if ctrlErr != nil { + ctrlErr = fmt.Errorf("tls_spoof: select TCP_RECV_QUEUE: %w", ctrlErr) + return + } + receiveSequence, seqErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_QUEUE_SEQ) + if seqErr != nil { + ctrlErr = fmt.Errorf("tls_spoof: read recv queue sequence: %w", seqErr) + return + } + s.sendNext = uint32(sendSequence) + s.receiveNext = uint32(receiveSequence) + }) + if err != nil { + return err + } + return ctrlErr +} + +func (s *linuxSpoofer) Inject(payload []byte) error { + frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, s.timestamp, nil, payload) + if err != nil { + return err + } + err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) + if err != nil { + return func(err error, m string) error { return err }(err, "sendto raw socket") + } + return nil +} + +func (s *linuxSpoofer) Close() error { + if s.rawFD < 0 { + return nil + } + err := unix.Close(s.rawFD) + s.rawFD = -1 + return err +} diff --git a/transport/internet/tls/tlsspoof/raw_stub.go b/transport/internet/tls/tlsspoof/raw_stub.go new file mode 100644 index 000000000000..78be3c23391d --- /dev/null +++ b/transport/internet/tls/tlsspoof/raw_stub.go @@ -0,0 +1,15 @@ +//go:build !linux && !darwin && !freebsd && !(windows && (amd64 || 386)) + +package tlsspoof + +import ( + "net" + + "errors" +) + +const PlatformSupported = false + +func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { + return nil, errors.New("tls_spoof: unsupported platform") +} diff --git a/transport/internet/tls/tlsspoof/raw_unix.go b/transport/internet/tls/tlsspoof/raw_unix.go new file mode 100644 index 000000000000..ae6c8b9f8b04 --- /dev/null +++ b/transport/internet/tls/tlsspoof/raw_unix.go @@ -0,0 +1,25 @@ +//go:build linux || darwin || freebsd + +package tlsspoof + +import ( + "net/netip" + + + "golang.org/x/sys/unix" +) + +func openIPv4RawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) { + fd, err := unix.Socket(unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, func(err error, m string) error { return err }(err, "open AF_INET SOCK_RAW") + } + err = unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_HDRINCL, 1) + if err != nil { + unix.Close(fd) + return -1, nil, func(err error, m string) error { return err }(err, "set IP_HDRINCL") + } + sockaddr := &unix.SockaddrInet4{Port: int(dst.Port())} + sockaddr.Addr = dst.Addr().As4() + return fd, sockaddr, nil +} diff --git a/transport/internet/tls/tlsspoof/spoof_freebsd_test.go b/transport/internet/tls/tlsspoof/spoof_freebsd_test.go new file mode 100644 index 000000000000..a8ab2ccae823 --- /dev/null +++ b/transport/internet/tls/tlsspoof/spoof_freebsd_test.go @@ -0,0 +1,82 @@ +package tlsspoof + +import ( + "net" + "net/netip" + "os/user" + "testing" + + "golang.org/x/sys/unix" +) + +func TestFreeBSDTCPSequence(t *testing.T) { + u, err := user.Current() + if err == nil && u.Uid != "0" { + t.Skip("skipping test; must be root to use raw sockets / TCP_INFO on FreeBSD") + } + + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen failed: %v", err) + } + defer ln.Close() + + serverDone := make(chan struct{}) + go func() { + conn, err := ln.Accept() + if err == nil { + conn.Write([]byte("hello")) + conn.Close() + } + close(serverDone) + }() + + client, err := net.Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dial failed: %v", err) + } + defer client.Close() + + tcpConn, ok := client.(*net.TCPConn) + if !ok { + t.Fatalf("expected *net.TCPConn, got %T", client) + } + + sndNxt, rcvNxt, err := readFreeBSDTCPSequence(tcpConn) + if err != nil { + t.Fatalf("readFreeBSDTCPSequence failed: %v", err) + } + + if sndNxt == 0 && rcvNxt == 0 { + t.Errorf("expected non-zero sequence numbers, got sndNxt=%d rcvNxt=%d", sndNxt, rcvNxt) + } + t.Logf("FreeBSD TCP sequence retrieved: snd_nxt=%d, rcv_nxt=%d", sndNxt, rcvNxt) + <-serverDone +} + +func TestFreeBSDRawSocket(t *testing.T) { + u, err := user.Current() + if err == nil && u.Uid != "0" { + t.Skip("skipping test; must be root to open raw sockets") + } + + dst := netip.MustParseAddrPort("8.8.8.8:443") + src := netip.MustParseAddrPort("127.0.0.1:12345") + + fd, sockaddr, err := openFreeBSDRawSocket(src, dst) + if err != nil { + t.Fatalf("openFreeBSDRawSocket failed: %v", err) + } + defer func() { + if fd >= 0 { + unix.Close(fd) + } + }() + + if fd < 0 { + t.Errorf("expected valid fd, got %d", fd) + } + if sockaddr == nil { + t.Error("expected valid sockaddr, got nil") + } +} From 650a37fb0381e1926718bd3e990feb45fe59a8f1 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Fri, 1 May 2026 11:00:00 +0600 Subject: [PATCH 03/42] TLSSpoof: Add Windows WinDivert spoofer --- .../internet/tls/tlsspoof/raw_windows.go | 234 ++++ .../tls/tlsspoof/windivert/assets/LICENSE.txt | 1191 +++++++++++++++++ .../tlsspoof/windivert/assets/WinDivert32.sys | Bin 0 -> 79792 bytes .../tlsspoof/windivert/assets/WinDivert64.sys | Bin 0 -> 94144 bytes .../tls/tlsspoof/windivert/assets_386.go | 14 + .../tls/tlsspoof/windivert/assets_amd64.go | 14 + .../tlsspoof/windivert/assets_unsupported.go | 7 + .../tls/tlsspoof/windivert/driver_windows.go | 211 +++ .../internet/tls/tlsspoof/windivert/filter.go | 181 +++ .../tls/tlsspoof/windivert/handle_windows.go | 323 +++++ .../tls/tlsspoof/windivert/windivert.go | 78 ++ 11 files changed, 2253 insertions(+) create mode 100644 transport/internet/tls/tlsspoof/raw_windows.go create mode 100644 transport/internet/tls/tlsspoof/windivert/assets/LICENSE.txt create mode 100644 transport/internet/tls/tlsspoof/windivert/assets/WinDivert32.sys create mode 100644 transport/internet/tls/tlsspoof/windivert/assets/WinDivert64.sys create mode 100644 transport/internet/tls/tlsspoof/windivert/assets_386.go create mode 100644 transport/internet/tls/tlsspoof/windivert/assets_amd64.go create mode 100644 transport/internet/tls/tlsspoof/windivert/assets_unsupported.go create mode 100644 transport/internet/tls/tlsspoof/windivert/driver_windows.go create mode 100644 transport/internet/tls/tlsspoof/windivert/filter.go create mode 100644 transport/internet/tls/tlsspoof/windivert/handle_windows.go create mode 100644 transport/internet/tls/tlsspoof/windivert/windivert.go diff --git a/transport/internet/tls/tlsspoof/raw_windows.go b/transport/internet/tls/tlsspoof/raw_windows.go new file mode 100644 index 000000000000..17878ffce3dd --- /dev/null +++ b/transport/internet/tls/tlsspoof/raw_windows.go @@ -0,0 +1,234 @@ +//go:build windows && (amd64 || 386) + +package tlsspoof + +import ( + "errors" + "net" + "net/netip" + "slices" + "sync" + "sync/atomic" + "time" + + "github.com/xtls/xray-core/transport/internet/tls/tlsspoof/windivert" + "golang.org/x/sys/windows" +) + +const PlatformSupported = true + +// closeGracePeriod caps how long Close() waits for the divert goroutine to +// observe the kernel-emitted real ClientHello and perform the reorder +// (fake → real). In practice this completes in microseconds; the cap +// bounds the pathological case where the kernel buffers the packet. +const closeGracePeriod = 2 * time.Second + +// windowsSpoofer uses a single WinDivert handle for both capture and +// injection. Sequential Send() calls on one handle traverse one driver queue, +// so the fake provably precedes the released real on the wire — a guarantee +// two separate handles cannot make because cross-handle order depends on the +// scheduler. +type windowsSpoofer struct { + method Method + src, dst netip.AddrPort + divertH *windivert.Handle + + fakeReady chan []byte // buffered(1): staged by Inject + done chan struct{} // closed by run() on exit + closeOnce sync.Once + runErr atomic.Pointer[error] +} + +func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { + _, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + filter, err := windivert.OutboundTCP(src, dst) + if err != nil { + return nil, err + } + divertH, err := windivert.Open(filter, windivert.LayerNetwork, 0, 0) + if err != nil { + return nil, err + } + s := &windowsSpoofer{ + method: method, + src: src, + dst: dst, + divertH: divertH, + fakeReady: make(chan []byte, 1), + done: make(chan struct{}), + } + go s.run() + return s, nil +} + +func (s *windowsSpoofer) Inject(payload []byte) error { + select { + case s.fakeReady <- payload: + return nil + case <-s.done: + if p := s.runErr.Load(); p != nil { + return *p + } + return errors.New("tls_spoof: spoofer closed before Inject") + } +} + +func (s *windowsSpoofer) Close() error { + s.closeOnce.Do(func() { + // Give run() a grace window to finish handling the real packet. + select { + case <-s.done: + case <-time.After(closeGracePeriod): + // Force Recv() to return by closing the divert handle. + s.divertH.Close() + <-s.done + } + }) + if p := s.runErr.Load(); p != nil { + return *p + } + return nil +} + +func (s *windowsSpoofer) recordErr(err error) { s.runErr.Store(&err) } + +func (s *windowsSpoofer) run() { + defer close(s.done) + defer s.divertH.Close() + + buf := make([]byte, windivert.MTUMax) + for { + n, addr, err := s.divertH.Recv(buf) + if err != nil { + if errors.Is(err, windows.ERROR_OPERATION_ABORTED) || + errors.Is(err, windows.ERROR_NO_DATA) { + return + } + s.recordErr(err) + return + } + pkt := buf[:n] + seq, ack, tcpOptions, payloadLen, ok := parseTCPPacket(pkt, addr.IPv6()) + if !ok { + // Our filter is OutboundTCP(src, dst); a non-TCP or truncated + // match means driver state is suspect. Re-inject so the kernel + // still sees the byte stream, then abort — continuing would risk + // reordering against an unknown reference point. + _, sendErr := s.divertH.Send(pkt, &addr) + if sendErr != nil { + s.recordErr(sendErr) + return + } + s.recordErr(errors.New("windivert received malformed packet matching spoof filter")) + return + } + if payloadLen == 0 { + // Handshake ACK, keepalive, FIN — pass through unchanged. + _, err := s.divertH.Send(pkt, &addr) + if err != nil { + s.recordErr(err) + return + } + continue + } + + // Non-empty outbound TCP payload = the real ClientHello. + var fake []byte + select { + case fake = <-s.fakeReady: + default: + // Inject() not yet called — pass through and keep observing. + _, err := s.divertH.Send(pkt, &addr) + if err != nil { + s.recordErr(err) + return + } + continue + } + + var timestamp uint32 + if tsVal, hasTS := ParseTCPOptions(tcpOptions); hasTS { + timestamp = tsVal + } + frame, err := buildSpoofFrame(s.method, s.src, s.dst, seq, ack, timestamp, tcpOptions, fake) + if err != nil { + s.recordErr(err) + return + } + fakeAddr := addr // inherit Outbound, IfIdx + // buildSpoofFrame emits ready-to-wire bytes. The driver recomputes + // checksums on Send when TCPChecksum/IPChecksum are 0 — which would + // overwrite the intentionally corrupt checksum in WrongChecksum mode. + // Force both to 1 to keep our bytes intact. + fakeAddr.SetIPChecksum(true) + fakeAddr.SetTCPChecksum(true) + _, err = s.divertH.Send(frame, &fakeAddr) + if err != nil { + s.recordErr(err) + return + } + _, err = s.divertH.Send(pkt, &addr) + if err != nil { + s.recordErr(err) + return + } + return // single-shot reorder complete + } +} + +func parseTCPPacket(pkt []byte, isV6 bool) (seq, ack uint32, options []byte, payloadLen int, ok bool) { + if isV6 { + if len(pkt) < IPv6MinimumSize+TCPMinimumSize { + return 0, 0, nil, 0, false + } + ip := IPv6(pkt) + if ip.TransportProtocol() != TCPProtocolNumber { + return 0, 0, nil, 0, false + } + tcp := TCP(pkt[IPv6MinimumSize:]) + tcpHdr := int(tcp.DataOffset()) + if tcpHdr < TCPMinimumSize || IPv6MinimumSize+tcpHdr > len(pkt) { + return 0, 0, nil, 0, false + } + total := IPv6MinimumSize + int(ip.PayloadLength()) + if total == IPv6MinimumSize || total > len(pkt) { + total = len(pkt) + } + if total < IPv6MinimumSize+tcpHdr { + return 0, 0, nil, 0, false + } + return tcp.SequenceNumber(), tcp.AckNumber(), slices.Clone(tcp.Options()), + total - IPv6MinimumSize - tcpHdr, true + } + if len(pkt) < IPv4MinimumSize+TCPMinimumSize { + return 0, 0, nil, 0, false + } + ip := IPv4(pkt) + if ip.Protocol() != TCPProtocolNumber { + return 0, 0, nil, 0, false + } + ihl := int(ip.HeaderLength()) + // ihl+TCPMinimumSize guards the TCP-header field reads below; without + // this, an IPv4 packet with options (ihl>20) against a 40-byte buffer + // reads past the TCP slice when calling DataOffset. + if ihl < IPv4MinimumSize || ihl+TCPMinimumSize > len(pkt) { + return 0, 0, nil, 0, false + } + tcp := TCP(pkt[ihl:]) + tcpHdr := int(tcp.DataOffset()) + if tcpHdr < TCPMinimumSize || ihl+tcpHdr > len(pkt) { + return 0, 0, nil, 0, false + } + total := int(ip.TotalLength()) + if total == 0 || total > len(pkt) { + total = len(pkt) + } + if total < ihl+tcpHdr { + return 0, 0, nil, 0, false + } + return tcp.SequenceNumber(), tcp.AckNumber(), slices.Clone(tcp.Options()), + total - ihl - tcpHdr, true +} diff --git a/transport/internet/tls/tlsspoof/windivert/assets/LICENSE.txt b/transport/internet/tls/tlsspoof/windivert/assets/LICENSE.txt new file mode 100644 index 000000000000..8489a8e773c3 --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/assets/LICENSE.txt @@ -0,0 +1,1191 @@ +WinDivert is dual-licensed under your choice of the GNU Lesser General Public +License (LGPL) Version 3 or the GNU General Public License (GPL) Version 2. +Copies of the LGPLv3, GPLv3 and GPLv2 are provided below. + +============================================================================== + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + +============================================================================== + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + +============================================================================== + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + diff --git a/transport/internet/tls/tlsspoof/windivert/assets/WinDivert32.sys b/transport/internet/tls/tlsspoof/windivert/assets/WinDivert32.sys new file mode 100644 index 0000000000000000000000000000000000000000..d06738cbb78351cc57754fd484b77fac0df52cea GIT binary patch literal 79792 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%0DB0KXuLbP(03=gskd! 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�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%0A~=3F;g8l!;0U`QAv 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 E0GFf)F8}}l literal 0 HcmV?d00001 diff --git a/transport/internet/tls/tlsspoof/windivert/assets/WinDivert64.sys b/transport/internet/tls/tlsspoof/windivert/assets/WinDivert64.sys new file mode 100644 index 0000000000000000000000000000000000000000..218ccaf423ef0a67696226f9ef3a09149e4441d0 GIT binary patch literal 94144 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 Mok_m={2+h)7u^}>0RR91 literal 0 HcmV?d00001 diff --git a/transport/internet/finalmask/rawpacket/windivert/assets_386.go b/transport/internet/finalmask/rawpacket/windivert/assets_386.go new file mode 100644 index 000000000000..0cbf35ed5cbf --- /dev/null +++ b/transport/internet/finalmask/rawpacket/windivert/assets_386.go @@ -0,0 +1,14 @@ +//go:build windows && 386 + +package windivert + +import _ "embed" + +//go:embed assets/WinDivert32.sys +var sysBytes []byte + +func assetFiles() []assetFile { + return []assetFile{{"WinDivert32.sys", sysBytes}} +} + +func driverSysName() string { return "WinDivert32.sys" } diff --git a/transport/internet/finalmask/rawpacket/windivert/assets_amd64.go b/transport/internet/finalmask/rawpacket/windivert/assets_amd64.go new file mode 100644 index 000000000000..2c9fb6c6ad19 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/windivert/assets_amd64.go @@ -0,0 +1,14 @@ +//go:build windows && amd64 + +package windivert + +import _ "embed" + +//go:embed assets/WinDivert64.sys +var sysBytes []byte + +func assetFiles() []assetFile { + return []assetFile{{"WinDivert64.sys", sysBytes}} +} + +func driverSysName() string { return "WinDivert64.sys" } diff --git a/transport/internet/finalmask/rawpacket/windivert/assets_unsupported.go b/transport/internet/finalmask/rawpacket/windivert/assets_unsupported.go new file mode 100644 index 000000000000..04698953fa6b --- /dev/null +++ b/transport/internet/finalmask/rawpacket/windivert/assets_unsupported.go @@ -0,0 +1,7 @@ +//go:build windows && !amd64 && !386 + +package windivert + +func assetFiles() []assetFile { return nil } + +func driverSysName() string { return "" } diff --git a/transport/internet/finalmask/rawpacket/windivert/driver_windows.go b/transport/internet/finalmask/rawpacket/windivert/driver_windows.go new file mode 100644 index 000000000000..50e94c578422 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/windivert/driver_windows.go @@ -0,0 +1,211 @@ +//go:build windows + +package windivert + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "strconv" + "sync" + + "golang.org/x/sys/windows" +) + +const ( + driverServiceName = "WinDivert" + driverDeviceName = `\\.\WinDivert` +) + +var ( + driverOnce sync.Once + driverErr error + // driverDevName is ASCII-safe and must be available before ensureDriver + // so Open can try CreateFile first and only install on FILE_NOT_FOUND. + driverDevName, _ = windows.UTF16PtrFromString(driverDeviceName) +) + +// Requires SeLoadDriverPrivilege (Administrator). Running the 386 build +// under WOW64 on a 64-bit kernel is rejected — use the amd64 build. +func ensureDriver() error { + driverOnce.Do(func() { + driverErr = installDriver() + }) + return driverErr +} + +func installDriver() error { + if runtime.GOARCH == "386" { + var isWow64 bool + err := windows.IsWow64Process(windows.CurrentProcess(), &isWow64) + if err == nil && isWow64 { + return errors.New("windivert: 386 build detected running under WOW64 on a 64-bit kernel; use the amd64 build") + } + } + + dir, err := ensureExtracted() + if err != nil { + return err + } + sysPath := filepath.Join(dir, driverSysName()) + sysPathW, err := windows.UTF16PtrFromString(sysPath) + if err != nil { + return fmt.Errorf("windivert: utf16 driver path: %w", err) + } + + // Serialize driver install across concurrent processes. + mutexName, _ := windows.UTF16PtrFromString("WinDivertDriverInstallMutex") + mutex, err := windows.CreateMutex(nil, false, mutexName) + if err != nil { + return fmt.Errorf("windivert: create install mutex: %w", err) + } + defer windows.CloseHandle(mutex) + _, err = windows.WaitForSingleObject(mutex, windows.INFINITE) + if err != nil { + return fmt.Errorf("windivert: wait install mutex: %w", err) + } + defer windows.ReleaseMutex(mutex) + + manager, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_ALL_ACCESS) + if err != nil { + return fmt.Errorf("windivert: open SCM: %w", err) + } + defer windows.CloseServiceHandle(manager) + + serviceNameW, _ := windows.UTF16PtrFromString(driverServiceName) + service, err := windows.OpenService(manager, serviceNameW, windows.SERVICE_ALL_ACCESS) + if err != nil { + service, err = windows.CreateService( + manager, + serviceNameW, + serviceNameW, + windows.SERVICE_ALL_ACCESS, + windows.SERVICE_KERNEL_DRIVER, + windows.SERVICE_DEMAND_START, + windows.SERVICE_ERROR_NORMAL, + sysPathW, + nil, nil, nil, nil, nil, + ) + if err != nil { + if errors.Is(err, windows.ERROR_SERVICE_EXISTS) { + service, err = windows.OpenService(manager, serviceNameW, windows.SERVICE_ALL_ACCESS) + } + if err != nil { + return wrapDriverInstallError(err) + } + } + } + defer windows.CloseServiceHandle(service) + + err = windows.StartService(service, 0, nil) + if err != nil && errors.Is(err, windows.ERROR_SERVICE_DISABLED) { + // A prior process called DeleteService on a still-running kernel + // driver: SCM marks the record for deletion and flips START_TYPE + // to DISABLED until the last handle closes. Re-enable so we can + // start it instead of waiting for a reboot. + err = windows.ChangeServiceConfig( + service, + windows.SERVICE_NO_CHANGE, + windows.SERVICE_DEMAND_START, + windows.SERVICE_NO_CHANGE, + nil, nil, nil, nil, nil, nil, nil, + ) + if err != nil { + return fmt.Errorf("windivert: re-enable disabled service: %w", err) + } + err = windows.StartService(service, 0, nil) + } + if err == nil { + // Mark for deletion so the driver unregisters when the last handle + // closes or on next reboot. Matches the upstream DLL's behavior: + // only the process that actually started the service takes on the + // cleanup responsibility. If another process already started it, + // we leave DeleteService to them. + _ = windows.DeleteService(service) + } else if !errors.Is(err, windows.ERROR_SERVICE_ALREADY_RUNNING) { + return fmt.Errorf("windivert: start service: %w", err) + } + return nil +} + +func wrapDriverInstallError(err error) error { + if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + return fmt.Errorf("windivert: installing the kernel driver requires Administrator privileges: %w", err) + } + return fmt.Errorf("windivert: create service: %w", err) +} + +type assetFile struct { + name string + data []byte +} + +var ( + extractOnce sync.Once + extractErr error + extractDir string +) + +// The on-disk copy is protected by Windows Authenticode signature +// enforcement, which rejects any tampered .sys at StartService time. +func ensureExtracted() (string, error) { + extractOnce.Do(func() { + extractDir, extractErr = extractImpl() + }) + return extractDir, extractErr +} + +func extractImpl() (string, error) { + files := assetFiles() + if len(files) == 0 { + return "", fmt.Errorf("windivert: unsupported architecture %s", runtime.GOARCH) + } + + base, err := os.UserCacheDir() + if err != nil { + return "", fmt.Errorf("windivert: locate user cache dir: %w", err) + } + dir := filepath.Join(base, "xray-core", "windivert", "v"+AssetVersion) + err = os.MkdirAll(dir, 0o755) + if err != nil { + return "", fmt.Errorf("windivert: mkdir %s: %w", dir, err) + } + + for _, asset := range files { + err = ensureAsset(dir, asset) + if err != nil { + return "", err + } + } + return dir, nil +} + +// Concurrent sing-box processes race on os.Rename (atomic on NTFS); +// whichever wins creates the final file. Writers that lose the race +// silently discard their temp copy. +func ensureAsset(dir string, asset assetFile) error { + target := filepath.Join(dir, asset.name) + _, err := os.Stat(target) + if err == nil { + return nil + } + if !os.IsNotExist(err) { + return fmt.Errorf("windivert: stat %s: %w", asset.name, err) + } + tmp := target + ".tmp-" + strconv.Itoa(os.Getpid()) + err = os.WriteFile(tmp, asset.data, 0o644) + if err != nil { + return fmt.Errorf("windivert: write %s: %w", asset.name, err) + } + err = os.Rename(tmp, target) + if err != nil { + os.Remove(tmp) + if _, statErr := os.Stat(target); statErr == nil { + return nil + } + return fmt.Errorf("windivert: rename %s: %w", asset.name, err) + } + return nil +} diff --git a/transport/internet/finalmask/rawpacket/windivert/filter.go b/transport/internet/finalmask/rawpacket/windivert/filter.go new file mode 100644 index 000000000000..d63adae2b630 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/windivert/filter.go @@ -0,0 +1,181 @@ +package windivert + +import ( + "encoding/binary" + "errors" + "net/netip" +) + +// WINDIVERT_FILTER VM instruction layout (24 bytes, #pragma pack(1)): +// +// word 0 (LE): field:11 | test:5 | success:16 +// word 1 (LE): failure:16 | neg:1 | reserved:15 +// words 2..5: arg[4] (native-endian uint32 each) +// +// The driver walks this as a decision tree: evaluate the test at inst i; +// on success jump to success; on failure jump to failure. Continuations +// 0x7FFE and 0x7FFF are ACCEPT and REJECT terminals. +const ( + filterInstBytes = 24 + filterMaxInsts = 256 + + fieldZero = 0 + fieldOutbound = 2 + fieldIP = 5 + fieldIPv6 = 6 + fieldTCP = 8 + fieldIPSrcAddr = 21 + fieldIPDstAddr = 22 + fieldIPv6SrcAddr = 28 + fieldIPv6DstAddr = 29 + fieldTCPSrcPort = 38 + fieldTCPDstPort = 39 + + testEQ = 0 + + resultAccept uint16 = 0x7FFE + resultReject uint16 = 0x7FFF +) + +// Filter flags passed to IOCTL_WINDIVERT_STARTUP alongside the compiled +// filter. These tell the driver what *kinds* of packets the filter might +// match, used as a kernel-side fast-reject. +const ( + filterFlagOutbound uint64 = 0x0020 + filterFlagIP uint64 = 0x0040 + filterFlagIPv6 uint64 = 0x0080 +) + +type filterInst struct { + field uint16 // 11 bits used + test uint8 // 5 bits used + success uint16 + failure uint16 + neg bool + arg [4]uint32 +} + +// Filter is a typed specification of packets to capture. It replaces +// WinDivert's filter string language. +// +// Zero value = "reject all" (match nothing), suitable for send-only handles. +type Filter struct { + insts []filterInst + flags uint64 // filter flags for STARTUP ioctl +} + +// reject returns a filter that matches no packet. The empty insts slice +// is encoded as a single rejecting instruction by encode(). +func reject() *Filter { + return &Filter{} +} + +// OutboundTCP returns a filter matching outbound TCP packets on the given +// 5-tuple. Both addresses must share an address family (IPv4 or IPv6). +func OutboundTCP(src, dst netip.AddrPort) (*Filter, error) { + if !src.IsValid() || !dst.IsValid() { + return nil, errors.New("windivert: filter: invalid address port") + } + if src.Addr().Is4() != dst.Addr().Is4() { + return nil, errors.New("windivert: filter: mixed IPv4/IPv6") + } + f := &Filter{ + flags: filterFlagOutbound, + } + // Insts chain as AND: each test's failure = REJECT, success = next inst. + // The final inst's success = ACCEPT. + f.add(fieldOutbound, testEQ, argUint32(1)) + if src.Addr().Is4() { + f.flags |= filterFlagIP + f.add(fieldIP, testEQ, argUint32(1)) + f.add(fieldTCP, testEQ, argUint32(1)) + f.add(fieldIPSrcAddr, testEQ, argIPv4(src.Addr())) + f.add(fieldIPDstAddr, testEQ, argIPv4(dst.Addr())) + } else { + f.flags |= filterFlagIPv6 + f.add(fieldIPv6, testEQ, argUint32(1)) + f.add(fieldTCP, testEQ, argUint32(1)) + f.add(fieldIPv6SrcAddr, testEQ, argIPv6(src.Addr())) + f.add(fieldIPv6DstAddr, testEQ, argIPv6(dst.Addr())) + } + f.add(fieldTCPSrcPort, testEQ, argUint32(uint32(src.Port()))) + f.add(fieldTCPDstPort, testEQ, argUint32(uint32(dst.Port()))) + return f, nil +} + +func (f *Filter) add(field uint16, test uint8, arg [4]uint32) { + f.insts = append(f.insts, filterInst{field: field, test: test, arg: arg}) +} + +func argUint32(v uint32) [4]uint32 { return [4]uint32{v, 0, 0, 0} } + +// argIPv4 encodes an IPv4 address for IP_SRCADDR/IP_DSTADDR. The driver +// compares against an IPv4-mapped-IPv6 form: {host_order_u32, 0x0000FFFF, +// 0, 0} (see sys/windivert.c windivert_get_ipv4_addr and the IPv4_SRCADDR +// val-word construction). Omitting the 0x0000FFFF marker causes the EQ +// test to fail for every packet. +func argIPv4(addr netip.Addr) [4]uint32 { + b := addr.As4() + return [4]uint32{binary.BigEndian.Uint32(b[:]), 0x0000FFFF, 0, 0} +} + +// argIPv6 encodes an IPv6 address for IPV6_SRCADDR/IPV6_DSTADDR. The +// driver stores the address as four host-order uint32s in REVERSED word +// order: val[0]=low (bytes 12..15), val[3]=high (bytes 0..3). See +// sys/windivert.c windivert_outbound_network_v6_classify val-word +// construction. +func argIPv6(addr netip.Addr) [4]uint32 { + b := addr.As16() + return [4]uint32{ + binary.BigEndian.Uint32(b[12:16]), + binary.BigEndian.Uint32(b[8:12]), + binary.BigEndian.Uint32(b[4:8]), + binary.BigEndian.Uint32(b[0:4]), + } +} + +// encode serializes the Filter to the on-wire WINDIVERT_FILTER[] format +// plus the filter_flags for STARTUP ioctl. +func (f *Filter) encode() ([]byte, uint64, error) { + if len(f.insts) == 0 { + // "Reject all" — one instruction, ZERO == 0 is always true, but we + // invert by setting both success and failure to REJECT. + return encodeInst(filterInst{ + field: fieldZero, + test: testEQ, + success: resultReject, + failure: resultReject, + }), 0, nil + } + if len(f.insts) > filterMaxInsts-1 { + return nil, 0, errors.New("windivert: filter too long") + } + buf := make([]byte, 0, filterInstBytes*len(f.insts)) + for i, inst := range f.insts { + if i == len(f.insts)-1 { + inst.success = resultAccept + } else { + inst.success = uint16(i + 1) + } + inst.failure = resultReject + buf = append(buf, encodeInst(inst)...) + } + return buf, f.flags, nil +} + +func encodeInst(inst filterInst) []byte { + out := make([]byte, filterInstBytes) + word0 := uint32(inst.field&0x7FF) | uint32(inst.test&0x1F)<<11 | + uint32(inst.success)<<16 + word1 := uint32(inst.failure) + if inst.neg { + word1 |= 1 << 16 + } + binary.LittleEndian.PutUint32(out[0:4], word0) + binary.LittleEndian.PutUint32(out[4:8], word1) + binary.LittleEndian.PutUint32(out[8:12], inst.arg[0]) + binary.LittleEndian.PutUint32(out[12:16], inst.arg[1]) + binary.LittleEndian.PutUint32(out[16:20], inst.arg[2]) + binary.LittleEndian.PutUint32(out[20:24], inst.arg[3]) + return out +} diff --git a/transport/internet/finalmask/rawpacket/windivert/handle_windows.go b/transport/internet/finalmask/rawpacket/windivert/handle_windows.go new file mode 100644 index 000000000000..c48e6214c11b --- /dev/null +++ b/transport/internet/finalmask/rawpacket/windivert/handle_windows.go @@ -0,0 +1,323 @@ +//go:build windows + +package windivert + +import ( + "encoding/binary" + "errors" + "fmt" + "runtime" + "sync" + "unsafe" + + "golang.org/x/sys/windows" +) + +// Handle owns a WinDivert kernel device handle plus a private event for +// overlapped I/O. Methods on *Handle are not safe for concurrent use +// across goroutines (there is a single shared event per Handle). +// +// addr is a per-Handle Address buffer the IOCTL struct embeds a pointer +// to. It lives on the heap (as a field of a heap-allocated Handle) so +// the pointer value stored as bytes in the ioctl buffer remains valid +// across stack growth between buildIoctl* and the DeviceIoControl +// syscall — stack-local Address values are not safe for this pattern +// because Go's escape analysis does not see the pointer through the +// unsafe.Pointer → uintptr → bytes conversion. +type Handle struct { + device windows.Handle + event windows.Handle + closing sync.Once + closeErr error + addr Address +} + +// Filter may be nil for "reject all", suitable for send-only handles. +// Requires Administrator on first call per process (installs the kernel +// driver via SCM); subsequent calls reuse the running driver. +func Open(filter *Filter, layer Layer, priority int16, flags Flag) (*Handle, error) { + err := validateOpenArgs(layer, priority, flags) + if err != nil { + return nil, err + } + if filter == nil { + filter = reject() + } + filterBin, filterFlags, err := filter.encode() + if err != nil { + return nil, err + } + device, err := openDevice() + if err != nil { + if !errors.Is(err, windows.ERROR_FILE_NOT_FOUND) && + !errors.Is(err, windows.ERROR_PATH_NOT_FOUND) { + if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + return nil, fmt.Errorf("windivert: open device (administrator required): %w", err) + } + return nil, fmt.Errorf("windivert: open device: %w", err) + } + // Device node missing: kernel driver not loaded. Install + retry. + // Matches WinDivertOpen's lazy-install path; avoids racing StartService + // against a still-loaded driver whose SCM record is marked for deletion. + err = ensureDriver() + if err != nil { + return nil, err + } + device, err = openDevice() + if err != nil { + if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + return nil, fmt.Errorf("windivert: open device (administrator required): %w", err) + } + return nil, fmt.Errorf("windivert: open device: %w", err) + } + } + event, err := windows.CreateEvent(nil, 1, 0, nil) // manual reset, unsignaled + if err != nil { + windows.CloseHandle(device) + return nil, fmt.Errorf("windivert: create event: %w", err) + } + h := &Handle{device: device, event: event} + + err = h.initialize(layer, priority, flags) + if err != nil { + h.Close() + return nil, err + } + err = h.startup(filterBin, filterFlags) + if err != nil { + h.Close() + return nil, err + } + return h, nil +} + +func openDevice() (windows.Handle, error) { + return windows.CreateFile( + driverDevName, + windows.GENERIC_READ|windows.GENERIC_WRITE, + 0, nil, + windows.OPEN_EXISTING, + windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_OVERLAPPED, + 0, + ) +} + +func validateOpenArgs(layer Layer, priority int16, flags Flag) error { + if layer != LayerNetwork { + return fmt.Errorf("windivert: invalid layer %d", uint32(layer)) + } + if priority < PriorityLowest || priority > PriorityHighest { + return errors.New("windivert: priority out of range") + } + const supportedFlags = FlagSniff | FlagSendOnly + if flags&^supportedFlags != 0 { + return errors.New("windivert: unknown flag bits") + } + if flags&FlagSniff != 0 && flags&FlagSendOnly != 0 { + return errors.New("windivert: FlagSniff and FlagSendOnly are mutually exclusive") + } + return nil +} + +func (h *Handle) initialize(layer Layer, priority int16, flags Flag) error { + in := buildIoctlInitialize(layer, priority, flags) + // WINDIVERT_VERSION is a 64-byte packed struct; only the first 20 + // bytes (magic, major, minor, bits) carry data, the rest is reserved. + var outBuf [versionStructSize]byte + binary.LittleEndian.PutUint64(outBuf[0:8], magicDLL) + binary.LittleEndian.PutUint32(outBuf[8:12], versionMajor) + binary.LittleEndian.PutUint32(outBuf[12:16], versionMinor) + binary.LittleEndian.PutUint32(outBuf[16:20], uint32(unsafe.Sizeof(uintptr(0))*8)) + _, err := doIoctl(h.device, ioctlInitialize, in[:], outBuf[:], h.event) + if err != nil { + return fmt.Errorf("windivert: initialize ioctl: %w", err) + } + gotMagic := binary.LittleEndian.Uint64(outBuf[0:8]) + if gotMagic != magicSYS { + return fmt.Errorf("windivert: driver magic mismatch (got %d)", gotMagic) + } + gotMajor := binary.LittleEndian.Uint32(outBuf[8:12]) + if gotMajor < versionMajor { + gotMinor := binary.LittleEndian.Uint32(outBuf[12:16]) + return fmt.Errorf("windivert: driver version too old: %d.%d", gotMajor, gotMinor) + } + return nil +} + +func (h *Handle) startup(filterBin []byte, filterFlags uint64) error { + in := buildIoctlStartup(filterFlags) + _, err := doIoctl(h.device, ioctlStartup, in[:], filterBin, h.event) + if err != nil { + return fmt.Errorf("windivert: startup ioctl: %w", err) + } + return nil +} + +// If the handle is closed mid-Recv the error wraps ERROR_OPERATION_ABORTED. +func (h *Handle) Recv(buf []byte) (int, Address, error) { + if len(buf) == 0 { + return 0, Address{}, errors.New("windivert: recv: zero-length buffer") + } + h.addr = Address{} + in := buildIoctlRecv(&h.addr) + n, err := doIoctl(h.device, ioctlRecv, in[:], buf, h.event) + runtime.KeepAlive(h) + if err != nil { + return 0, Address{}, err + } + return int(n), h.addr, nil +} + +// The address's Outbound flag controls whether the packet is sent toward +// the wire (outbound=true) or delivered up the stack (outbound=false). +// IfIdx and SubIfIdx can stay zero — the driver uses the routing table +// when IfIdx=0. +func (h *Handle) Send(packet []byte, addr *Address) (int, error) { + if len(packet) == 0 { + return 0, errors.New("windivert: send: empty packet") + } + if addr == nil { + return 0, errors.New("windivert: send: nil address") + } + h.addr = *addr + in := buildIoctlSend(&h.addr) + n, err := doIoctl(h.device, ioctlSend, in[:], packet, h.event) + runtime.KeepAlive(h) + if err != nil { + return 0, err + } + return int(n), nil +} + +// Idempotent. Aborts any in-flight I/O on the handle. +func (h *Handle) Close() error { + h.closing.Do(func() { + var errs []error + if h.device != 0 { + err := windows.CloseHandle(h.device) + if err != nil { + errs = append(errs, err) + } + h.device = 0 + } + if h.event != 0 { + err := windows.CloseHandle(h.event) + if err != nil { + errs = append(errs, err) + } + h.event = 0 + } + h.closeErr = errors.Join(errs...) + }) + return h.closeErr +} + +// IOCTL codes from windivert_device.h. CTL_CODE macro layout: +// +// (DeviceType << 16) | (Access << 14) | (Function << 2) | Method +const ( + fileDeviceNetwork uint32 = 0x12 + accessReadWrite uint32 = 3 // FILE_READ_DATA | FILE_WRITE_DATA + accessRead uint32 = 1 + + methodInDirect uint32 = 1 + methodOutDirect uint32 = 2 +) + +func ctlCode(deviceType, access, function, method uint32) uint32 { + return (deviceType << 16) | (access << 14) | (function << 2) | method +} + +var ( + ioctlInitialize = ctlCode(fileDeviceNetwork, accessReadWrite, 0x921, methodOutDirect) + ioctlStartup = ctlCode(fileDeviceNetwork, accessReadWrite, 0x922, methodInDirect) + ioctlRecv = ctlCode(fileDeviceNetwork, accessRead, 0x923, methodOutDirect) + ioctlSend = ctlCode(fileDeviceNetwork, accessReadWrite, 0x924, methodInDirect) +) + +// Magic numbers exchanged during INITIALIZE. DLL sends magicDLL in the +// version struct; driver returns magicSYS on success. +const ( + magicDLL uint64 = 0x4C4C447669645724 // "$WdivDLL" in LE bytes + magicSYS uint64 = 0x5359537669645723 // "#WdivSYS" in LE bytes +) + +const ( + versionMajor uint32 = 2 + versionMinor uint32 = 2 +) + +// Size of the WINDIVERT_IOCTL union on wire (packed). +const ioctlSize = 16 + +// Size of WINDIVERT_VERSION on wire (packed). Only the first 20 bytes +// carry data; the rest is reserved zero padding. +const versionStructSize = 64 + +// doIoctl performs a single synchronous (blocking) overlapped +// DeviceIoControl. The handle is opened with FILE_FLAG_OVERLAPPED so +// DeviceIoControl returns ERROR_IO_PENDING; we then wait for completion +// via GetOverlappedResult. Event is passed in so callers can reuse it +// across calls on the same handle (avoids per-call CreateEvent). +func doIoctl(handle windows.Handle, code uint32, in []byte, out []byte, event windows.Handle) (uint32, error) { + var overlapped windows.Overlapped + overlapped.HEvent = event + _ = windows.ResetEvent(event) + + var inPtr *byte + var inLen uint32 + if len(in) > 0 { + inPtr = &in[0] + inLen = uint32(len(in)) + } + var outPtr *byte + var outLen uint32 + if len(out) > 0 { + outPtr = &out[0] + outLen = uint32(len(out)) + } + var returned uint32 + err := windows.DeviceIoControl(handle, code, inPtr, inLen, outPtr, outLen, &returned, &overlapped) + if err != nil && !errors.Is(err, windows.ERROR_IO_PENDING) { + return 0, err + } + err = windows.GetOverlappedResult(handle, &overlapped, &returned, true) + if err != nil { + return 0, err + } + return returned, nil +} + +func buildIoctlInitialize(layer Layer, priority int16, flags Flag) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint32(buf[0:4], uint32(layer)) + // The driver expects priority + WINDIVERT_PRIORITY_HIGHEST (30000) so + // the low range maps to non-negative integers. + binary.LittleEndian.PutUint32(buf[4:8], uint32(int32(priority)+int32(PriorityHighest))) + binary.LittleEndian.PutUint64(buf[8:16], uint64(flags)) + return buf +} + +func buildIoctlStartup(filterFlags uint64) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint64(buf[0:8], filterFlags) + return buf +} + +// buildIoctlRecv packs a user-space pointer to a WINDIVERT_ADDRESS into +// the ioctl struct. The driver dereferences it to write the address for +// the received packet. Caller must keep the Address alive via +// runtime.KeepAlive. +func buildIoctlRecv(addr *Address) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint64(buf[0:8], uint64(uintptr(unsafe.Pointer(addr)))) + binary.LittleEndian.PutUint64(buf[8:16], 0) + return buf +} + +func buildIoctlSend(addr *Address) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint64(buf[0:8], uint64(uintptr(unsafe.Pointer(addr)))) + binary.LittleEndian.PutUint64(buf[8:16], uint64(unsafe.Sizeof(Address{}))) + return buf +} diff --git a/transport/internet/finalmask/rawpacket/windivert/windivert.go b/transport/internet/finalmask/rawpacket/windivert/windivert.go new file mode 100644 index 000000000000..9d309886cbe3 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/windivert/windivert.go @@ -0,0 +1,78 @@ +// Package windivert provides a pure-Go binding to the WinDivert kernel +// driver on Windows (amd64 and 386). User-mode WinDivert calls are +// reimplemented in Go; only the signed kernel driver is embedded as an +// asset, since SCM-installed drivers must live on disk and their +// Authenticode signature forbids modification. +// +// Administrator is required for the first Open in a process so SCM can +// load the driver. Upstream: https://github.com/basil00/WinDivert v2.2.2, +// redistributed under its LGPL v3 option; see assets/LICENSE.txt. +package windivert + +import "unsafe" + +const AssetVersion = "2.2.2" + +// MTUMax is WINDIVERT_MTU_MAX from windivert.h (40 + 0xFFFF). Suitable as +// a single-packet receive buffer size. +const MTUMax = 40 + 0xFFFF + +type Layer uint32 + +const LayerNetwork Layer = 0 + +type Flag uint64 + +const ( + // FlagSniff opens a passive observer: the driver copies matching packets + // to userspace without removing them from the network stack. Send is not + // required (and not allowed) on a sniffing handle. + FlagSniff Flag = 0x0001 + // FlagSendOnly opens a write-only injection handle; Recv is not allowed. + FlagSendOnly Flag = 0x0008 +) + +const ( + PriorityHighest int16 = 30000 + PriorityLowest int16 = -30000 +) + +// Address mirrors WINDIVERT_ADDRESS from windivert.h (80 bytes, +// little-endian on both amd64 and 386): +// +// 0: INT64 Timestamp +// 8: UINT32 bitfield: Layer:8 | Event:8 | flags | Reserved1:8 +// 12: UINT32 Reserved2 +// 16: 64 bytes union (WINDIVERT_DATA_NETWORK / FLOW / SOCKET / REFLECT) +type Address struct { + Timestamp int64 + bits uint32 + Reserved2 uint32 + union [64]byte +} + +var _ [80]byte = [unsafe.Sizeof(Address{})]byte{} + +// Bit positions inside the Address's packed flags word. +const ( + addrBitIPv6 = 20 + addrBitIPChecksum = 21 + addrBitTCPChecksum = 22 +) + +func getFlagBit(bits uint32, pos uint) bool { return bits&(1< Date: Sat, 9 May 2026 14:45:00 +0600 Subject: [PATCH 08/42] Rawpacket: Add tcpmask config, proto and conn wrapper with configurable TTL --- .../internet/finalmask/rawpacket/config.go | 14 ++ .../internet/finalmask/rawpacket/config.pb.go | 156 ++++++++++++++++ .../internet/finalmask/rawpacket/config.proto | 23 +++ .../internet/finalmask/rawpacket/conn.go | 170 ++++++++++++++++++ .../internet/finalmask/rawpacket/conn_test.go | 46 +++++ 5 files changed, 409 insertions(+) create mode 100644 transport/internet/finalmask/rawpacket/config.go create mode 100644 transport/internet/finalmask/rawpacket/config.pb.go create mode 100644 transport/internet/finalmask/rawpacket/config.proto create mode 100644 transport/internet/finalmask/rawpacket/conn.go create mode 100644 transport/internet/finalmask/rawpacket/conn_test.go diff --git a/transport/internet/finalmask/rawpacket/config.go b/transport/internet/finalmask/rawpacket/config.go new file mode 100644 index 000000000000..e4ee5d717d25 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/config.go @@ -0,0 +1,14 @@ +package rawpacket + +import "net" + +func (c *Config) TCP() {} + +func (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) { + return NewConnClient(c, raw) +} + +func (c *Config) WrapConnServer(raw net.Conn) (net.Conn, error) { + // Raw packet injection is client-side only. + return raw, nil +} diff --git a/transport/internet/finalmask/rawpacket/config.pb.go b/transport/internet/finalmask/rawpacket/config.pb.go new file mode 100644 index 000000000000..f1e3982bb2d4 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/config.pb.go @@ -0,0 +1,156 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v7.34.1 +// source: transport/internet/finalmask/rawpacket/config.proto + +package rawpacket + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Config struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Base64-encoded fake payload bytes to inject before the real traffic. + Payload string `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + // Corruption method to make the fake packet dropped by the server. + // Available: wrong-sequence, wrong-checksum, wrong-ack, wrong-md5, wrong-timestamp. + Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"` + // TTL of the fake packet. A low value (e.g. 3-5) ensures the packet + // is seen by middleboxes but does not reach the destination server. + Ttl uint32 `protobuf:"varint,3,opt,name=ttl,proto3" json:"ttl,omitempty"` + // How many Write() calls trigger injection. 0 or 1 = single-shot (default). + Count int32 `protobuf:"varint,4,opt,name=count,proto3" json:"count,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Config) Reset() { + *x = Config{} + mi := &file_transport_internet_finalmask_rawpacket_config_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Config) ProtoMessage() {} + +func (x *Config) ProtoReflect() protoreflect.Message { + mi := &file_transport_internet_finalmask_rawpacket_config_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Config.ProtoReflect.Descriptor instead. +func (*Config) Descriptor() ([]byte, []int) { + return file_transport_internet_finalmask_rawpacket_config_proto_rawDescGZIP(), []int{0} +} + +func (x *Config) GetPayload() string { + if x != nil { + return x.Payload + } + return "" +} + +func (x *Config) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +func (x *Config) GetTtl() uint32 { + if x != nil { + return x.Ttl + } + return 0 +} + +func (x *Config) GetCount() int32 { + if x != nil { + return x.Count + } + return 0 +} + +var File_transport_internet_finalmask_rawpacket_config_proto protoreflect.FileDescriptor + +const file_transport_internet_finalmask_rawpacket_config_proto_rawDesc = "" + + "\n" + + "3transport/internet/finalmask/rawpacket/config.proto\x12+xray.transport.internet.finalmask.rawpacket\"b\n" + + "\x06Config\x12\x18\n" + + "\apayload\x18\x01 \x01(\tR\apayload\x12\x16\n" + + "\x06method\x18\x02 \x01(\tR\x06method\x12\x10\n" + + "\x03ttl\x18\x03 \x01(\rR\x03ttl\x12\x14\n" + + "\x05count\x18\x04 \x01(\x05R\x05countB\xa3\x01\n" + + "/com.xray.transport.internet.finalmask.rawpacketP\x01Z@github.com/xtls/xray-core/transport/internet/finalmask/rawpacket\xaa\x02+Xray.Transport.Internet.Finalmask.Rawpacketb\x06proto3" + +var ( + file_transport_internet_finalmask_rawpacket_config_proto_rawDescOnce sync.Once + file_transport_internet_finalmask_rawpacket_config_proto_rawDescData []byte +) + +func file_transport_internet_finalmask_rawpacket_config_proto_rawDescGZIP() []byte { + file_transport_internet_finalmask_rawpacket_config_proto_rawDescOnce.Do(func() { + file_transport_internet_finalmask_rawpacket_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_rawpacket_config_proto_rawDesc), len(file_transport_internet_finalmask_rawpacket_config_proto_rawDesc))) + }) + return file_transport_internet_finalmask_rawpacket_config_proto_rawDescData +} + +var file_transport_internet_finalmask_rawpacket_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_transport_internet_finalmask_rawpacket_config_proto_goTypes = []any{ + (*Config)(nil), // 0: xray.transport.internet.finalmask.rawpacket.Config +} +var file_transport_internet_finalmask_rawpacket_config_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_transport_internet_finalmask_rawpacket_config_proto_init() } +func file_transport_internet_finalmask_rawpacket_config_proto_init() { + if File_transport_internet_finalmask_rawpacket_config_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_rawpacket_config_proto_rawDesc), len(file_transport_internet_finalmask_rawpacket_config_proto_rawDesc)), + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_transport_internet_finalmask_rawpacket_config_proto_goTypes, + DependencyIndexes: file_transport_internet_finalmask_rawpacket_config_proto_depIdxs, + MessageInfos: file_transport_internet_finalmask_rawpacket_config_proto_msgTypes, + }.Build() + File_transport_internet_finalmask_rawpacket_config_proto = out.File + file_transport_internet_finalmask_rawpacket_config_proto_goTypes = nil + file_transport_internet_finalmask_rawpacket_config_proto_depIdxs = nil +} diff --git a/transport/internet/finalmask/rawpacket/config.proto b/transport/internet/finalmask/rawpacket/config.proto new file mode 100644 index 000000000000..f6b3e8dcb10e --- /dev/null +++ b/transport/internet/finalmask/rawpacket/config.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package xray.transport.internet.finalmask.rawpacket; +option csharp_namespace = "Xray.Transport.Internet.Finalmask.Rawpacket"; +option go_package = "github.com/xtls/xray-core/transport/internet/finalmask/rawpacket"; +option java_package = "com.xray.transport.internet.finalmask.rawpacket"; +option java_multiple_files = true; + +message Config { + // Base64-encoded fake payload bytes to inject before the real traffic. + string payload = 1; + + // Corruption method to make the fake packet dropped by the server. + // Available: wrong-sequence, wrong-checksum, wrong-ack, wrong-md5, wrong-timestamp. + string method = 2; + + // TTL of the fake packet. A low value (e.g. 3-5) ensures the packet + // is seen by middleboxes but does not reach the destination server. + uint32 ttl = 3; + + // How many Write() calls trigger injection. 0 or 1 = single-shot (default). + int32 count = 4; +} diff --git a/transport/internet/finalmask/rawpacket/conn.go b/transport/internet/finalmask/rawpacket/conn.go new file mode 100644 index 000000000000..188b145ea2f7 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/conn.go @@ -0,0 +1,170 @@ +package rawpacket + +import ( + "encoding/base64" + "errors" + "fmt" + "net" + "runtime" + "syscall" +) + +type Method int + +const ( + MethodWrongSequence Method = iota + MethodWrongChecksum + MethodWrongAcknowledgment + MethodWrongMD5Sig + MethodWrongTimestamp +) + +const ( + MethodNameWrongSequence = "wrong-sequence" + MethodNameWrongChecksum = "wrong-checksum" + MethodNameWrongAcknowledgment = "wrong-ack" + MethodNameWrongMD5Sig = "wrong-md5" + MethodNameWrongTimestamp = "wrong-timestamp" +) + +func ParseMethod(s string) (Method, error) { + switch s { + case "", MethodNameWrongSequence: + return MethodWrongSequence, nil + case MethodNameWrongChecksum: + return MethodWrongChecksum, nil + case MethodNameWrongAcknowledgment: + return MethodWrongAcknowledgment, nil + case MethodNameWrongMD5Sig: + return MethodWrongMD5Sig, nil + case MethodNameWrongTimestamp: + return MethodWrongTimestamp, nil + default: + return 0, fmt.Errorf("rawpacket: unknown method: %s", s) + } +} + +func (m Method) String() string { + switch m { + case MethodWrongSequence: + return MethodNameWrongSequence + case MethodWrongChecksum: + return MethodNameWrongChecksum + case MethodWrongAcknowledgment: + return MethodNameWrongAcknowledgment + case MethodWrongMD5Sig: + return MethodNameWrongMD5Sig + case MethodWrongTimestamp: + return MethodNameWrongTimestamp + default: + return "unknown" + } +} + +type rawSpoofer interface { + Inject(payload []byte) error + Close() error +} + +type Conn struct { + net.Conn + spoofer rawSpoofer + fakePayload []byte + injectionCount int + maxInjections int +} + +func NewConnClient(cfg *Config, conn net.Conn) (net.Conn, error) { + if cfg.Payload == "" { + return conn, nil + } + if !PlatformSupported { + return nil, errors.New("rawpacket is not supported on this platform") + } + payload, err := base64.StdEncoding.DecodeString(cfg.Payload) + if err != nil { + return nil, fmt.Errorf("rawpacket: invalid base64 payload: %w", err) + } + if len(payload) == 0 { + return nil, errors.New("rawpacket: payload is empty") + } + method, err := ParseMethod(cfg.Method) + if err != nil { + return nil, err + } + ttl := uint8(cfg.Ttl) + if ttl == 0 { + ttl = 3 + } + spoofer, err := newRawSpoofer(conn, method, ttl) + if err != nil { + return nil, wrapPermissionError(err) + } + maxInjections := int(cfg.Count) + if maxInjections <= 0 { + maxInjections = 1 + } + return &Conn{ + Conn: conn, + spoofer: spoofer, + fakePayload: payload, + maxInjections: maxInjections, + }, nil +} + +func NewConnServer(_ *Config, conn net.Conn) (net.Conn, error) { + return conn, nil +} + +func (c *Conn) Write(b []byte) (n int, err error) { + if c.injectionCount >= c.maxInjections { + return c.Conn.Write(b) + } + err = c.spoofer.Inject(c.fakePayload) + if err != nil { + return 0, fmt.Errorf("rawpacket: inject: %w", err) + } + c.injectionCount++ + if c.injectionCount >= c.maxInjections { + closeErr := c.spoofer.Close() + if closeErr != nil { + return 0, fmt.Errorf("rawpacket: close spoofer: %w", closeErr) + } + } + return c.Conn.Write(b) +} + +func (c *Conn) Close() error { + connErr := c.Conn.Close() + spooferErr := c.spoofer.Close() + if connErr != nil { + return connErr + } + return spooferErr +} + +func (c *Conn) TcpMaskConn() {} + +func (c *Conn) RawConn() net.Conn { + return c.Conn +} + +func (c *Conn) Splice() bool { + return c.injectionCount >= c.maxInjections +} + +func wrapPermissionError(err error) error { + if !errors.Is(err, syscall.EPERM) && !errors.Is(err, syscall.EACCES) { + return err + } + switch runtime.GOOS { + case "linux": + return fmt.Errorf("%w\n Hint: run as root, or grant capabilities:\n sudo setcap cap_net_raw,cap_net_admin+ep /path/to/xray", err) + case "darwin": + return fmt.Errorf("%w\n Hint: rawpacket requires root on macOS. Run with: sudo ./xray", err) + case "freebsd": + return fmt.Errorf("%w\n Hint: rawpacket requires root on FreeBSD. Run with: sudo ./xray", err) + default: + return err + } +} diff --git a/transport/internet/finalmask/rawpacket/conn_test.go b/transport/internet/finalmask/rawpacket/conn_test.go new file mode 100644 index 000000000000..ab77ae584e26 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/conn_test.go @@ -0,0 +1,46 @@ +package rawpacket + +import ( + "testing" +) + +func TestParseMethod(t *testing.T) { + tests := []struct { + input string + expected Method + hasErr bool + }{ + {"", MethodWrongSequence, false}, + {"wrong-sequence", MethodWrongSequence, false}, + {"wrong-checksum", MethodWrongChecksum, false}, + {"wrong-ack", MethodWrongAcknowledgment, false}, + {"wrong-md5", MethodWrongMD5Sig, false}, + {"wrong-timestamp", MethodWrongTimestamp, false}, + {"invalid", 0, true}, + } + for _, tt := range tests { + m, err := ParseMethod(tt.input) + if tt.hasErr { + if err == nil { + t.Errorf("ParseMethod(%q): expected error, got nil", tt.input) + } + continue + } + if err != nil { + t.Errorf("ParseMethod(%q): unexpected error: %v", tt.input, err) + continue + } + if m != tt.expected { + t.Errorf("ParseMethod(%q) = %v, want %v", tt.input, m, tt.expected) + } + } +} + +func TestMethodString(t *testing.T) { + if MethodWrongSequence.String() != "wrong-sequence" { + t.Fatalf("unexpected method string: %s", MethodWrongSequence.String()) + } + if MethodWrongChecksum.String() != "wrong-checksum" { + t.Fatalf("unexpected method string: %s", MethodWrongChecksum.String()) + } +} From 1e230bb00383b0fa05327ba20c52addc1faab21e Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Sun, 10 May 2026 09:15:00 +0600 Subject: [PATCH 09/42] TLS: Remove spoof, spoof_method and spoof_count options --- transport/internet/tls/config.pb.go | 39 +- transport/internet/tls/config.proto | 7 - transport/internet/tls/tls.go | 33 - .../internet/tls/tlsspoof/client_hello.go | 75 -- transport/internet/tls/tlsspoof/endpoints.go | 27 - transport/internet/tls/tlsspoof/packet.go | 163 --- transport/internet/tls/tlsspoof/raw_darwin.go | 198 --- .../internet/tls/tlsspoof/raw_freebsd.go | 172 --- transport/internet/tls/tlsspoof/raw_linux.go | 166 --- transport/internet/tls/tlsspoof/raw_stub.go | 15 - transport/internet/tls/tlsspoof/raw_unix.go | 25 - .../internet/tls/tlsspoof/raw_windows.go | 234 ---- transport/internet/tls/tlsspoof/spoof.go | 182 --- .../tls/tlsspoof/spoof_freebsd_test.go | 82 -- transport/internet/tls/tlsspoof/spoof_test.go | 111 -- transport/internet/tls/tlsspoof/tcpip.go | 155 --- .../tls/tlsspoof/windivert/assets/LICENSE.txt | 1191 ----------------- .../tlsspoof/windivert/assets/WinDivert32.sys | Bin 79792 -> 0 bytes .../tlsspoof/windivert/assets/WinDivert64.sys | Bin 94144 -> 0 bytes .../tls/tlsspoof/windivert/assets_386.go | 14 - .../tls/tlsspoof/windivert/assets_amd64.go | 14 - .../tlsspoof/windivert/assets_unsupported.go | 7 - .../tls/tlsspoof/windivert/driver_windows.go | 211 --- .../internet/tls/tlsspoof/windivert/filter.go | 181 --- .../tls/tlsspoof/windivert/handle_windows.go | 323 ----- .../tls/tlsspoof/windivert/windivert.go | 78 -- 26 files changed, 5 insertions(+), 3698 deletions(-) delete mode 100644 transport/internet/tls/tlsspoof/client_hello.go delete mode 100644 transport/internet/tls/tlsspoof/endpoints.go delete mode 100644 transport/internet/tls/tlsspoof/packet.go delete mode 100644 transport/internet/tls/tlsspoof/raw_darwin.go delete mode 100644 transport/internet/tls/tlsspoof/raw_freebsd.go delete mode 100644 transport/internet/tls/tlsspoof/raw_linux.go delete mode 100644 transport/internet/tls/tlsspoof/raw_stub.go delete mode 100644 transport/internet/tls/tlsspoof/raw_unix.go delete mode 100644 transport/internet/tls/tlsspoof/raw_windows.go delete mode 100644 transport/internet/tls/tlsspoof/spoof.go delete mode 100644 transport/internet/tls/tlsspoof/spoof_freebsd_test.go delete mode 100644 transport/internet/tls/tlsspoof/spoof_test.go delete mode 100644 transport/internet/tls/tlsspoof/tcpip.go delete mode 100644 transport/internet/tls/tlsspoof/windivert/assets/LICENSE.txt delete mode 100644 transport/internet/tls/tlsspoof/windivert/assets/WinDivert32.sys delete mode 100644 transport/internet/tls/tlsspoof/windivert/assets/WinDivert64.sys delete mode 100644 transport/internet/tls/tlsspoof/windivert/assets_386.go delete mode 100644 transport/internet/tls/tlsspoof/windivert/assets_amd64.go delete mode 100644 transport/internet/tls/tlsspoof/windivert/assets_unsupported.go delete mode 100644 transport/internet/tls/tlsspoof/windivert/driver_windows.go delete mode 100644 transport/internet/tls/tlsspoof/windivert/filter.go delete mode 100644 transport/internet/tls/tlsspoof/windivert/handle_windows.go delete mode 100644 transport/internet/tls/tlsspoof/windivert/windivert.go diff --git a/transport/internet/tls/config.pb.go b/transport/internet/tls/config.pb.go index 700c70883ab1..5f7688a5c512 100644 --- a/transport/internet/tls/config.pb.go +++ b/transport/internet/tls/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v6.33.5 +// protoc v7.34.1 // source: transport/internet/tls/config.proto package tls @@ -209,12 +209,8 @@ type Config struct { EchForceQuery string `protobuf:"bytes,20,opt,name=ech_force_query,json=echForceQuery,proto3" json:"ech_force_query,omitempty"` EchSocketSettings *internet.SocketConfig `protobuf:"bytes,21,opt,name=ech_socket_settings,json=echSocketSettings,proto3" json:"ech_socket_settings,omitempty"` PinnedPeerCertSha256 [][]byte `protobuf:"bytes,22,rep,name=pinned_peer_cert_sha256,json=pinnedPeerCertSha256,proto3" json:"pinned_peer_cert_sha256,omitempty"` - Spoof string `protobuf:"bytes,23,opt,name=spoof,proto3" json:"spoof,omitempty"` - SpoofMethod string `protobuf:"bytes,24,opt,name=spoof_method,json=spoofMethod,proto3" json:"spoof_method,omitempty"` - // Number of times to inject the fake ClientHello (0 or 1 = single-shot). - SpoofCount int32 `protobuf:"varint,25,opt,name=spoof_count,json=spoofCount,proto3" json:"spoof_count,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Config) Reset() { @@ -380,27 +376,6 @@ func (x *Config) GetPinnedPeerCertSha256() [][]byte { return nil } -func (x *Config) GetSpoof() string { - if x != nil { - return x.Spoof - } - return "" -} - -func (x *Config) GetSpoofMethod() string { - if x != nil { - return x.SpoofMethod - } - return "" -} - -func (x *Config) GetSpoofCount() int32 { - if x != nil { - return x.SpoofCount - } - return 0 -} - var File_transport_internet_tls_config_proto protoreflect.FileDescriptor const file_transport_internet_tls_config_proto_rawDesc = "" + @@ -419,7 +394,7 @@ const file_transport_internet_tls_config_proto_rawDesc = "" + "\x05Usage\x12\x10\n" + "\fENCIPHERMENT\x10\x00\x12\x14\n" + "\x10AUTHORITY_VERIFY\x10\x01\x12\x13\n" + - "\x0fAUTHORITY_ISSUE\x10\x02\"\xcf\a\n" + + "\x0fAUTHORITY_ISSUE\x10\x02\"\xf5\x06\n" + "\x06Config\x12%\n" + "\x0eallow_insecure\x18\x01 \x01(\bR\rallowInsecure\x12J\n" + "\vcertificate\x18\x02 \x03(\v2(.xray.transport.internet.tls.CertificateR\vcertificate\x12\x1f\n" + @@ -442,11 +417,7 @@ const file_transport_internet_tls_config_proto_rawDesc = "" + "\x0fech_config_list\x18\x13 \x01(\tR\rechConfigList\x12&\n" + "\x0fech_force_query\x18\x14 \x01(\tR\rechForceQuery\x12U\n" + "\x13ech_socket_settings\x18\x15 \x01(\v2%.xray.transport.internet.SocketConfigR\x11echSocketSettings\x125\n" + - "\x17pinned_peer_cert_sha256\x18\x16 \x03(\fR\x14pinnedPeerCertSha256\x12\x14\n" + - "\x05spoof\x18\x17 \x01(\tR\x05spoof\x12!\n" + - "\fspoof_method\x18\x18 \x01(\tR\vspoofMethod\x12\x1f\n" + - "\vspoof_count\x18\x19 \x01(\x05R\n" + - "spoofCountBs\n" + + "\x17pinned_peer_cert_sha256\x18\x16 \x03(\fR\x14pinnedPeerCertSha256Bs\n" + "\x1fcom.xray.transport.internet.tlsP\x01Z0github.com/xtls/xray-core/transport/internet/tls\xaa\x02\x1bXray.Transport.Internet.Tlsb\x06proto3" var ( diff --git a/transport/internet/tls/config.proto b/transport/internet/tls/config.proto index 0039d0901a7d..4592822649c3 100644 --- a/transport/internet/tls/config.proto +++ b/transport/internet/tls/config.proto @@ -87,11 +87,4 @@ message Config { SocketConfig ech_socket_settings = 21; repeated bytes pinned_peer_cert_sha256 = 22; - - string spoof = 23; - - string spoof_method = 24; - - // Number of times to inject the fake ClientHello (0 or 1 = single-shot). - int32 spoof_count = 25; } diff --git a/transport/internet/tls/tls.go b/transport/internet/tls/tls.go index b8bc4102a31f..7fa3c25be55d 100644 --- a/transport/internet/tls/tls.go +++ b/transport/internet/tls/tls.go @@ -5,17 +5,13 @@ import ( "crypto/rand" "crypto/tls" "math/big" - gonet "net" "slices" - "strings" "time" utls "github.com/refraction-networking/utls" "github.com/xtls/xray-core/common/buf" - "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/utils" - "github.com/xtls/xray-core/transport/internet/tls/tlsspoof" ) type Interface interface { @@ -68,35 +64,6 @@ func Client(c net.Conn, config *tls.Config) net.Conn { return &Conn{Conn: tlsConn} } -// WrapWithSpoof wraps a connection with TLS spoofing if the config has -// spoof settings. The spoofed ClientHello is injected via raw sockets -// before the real TLS handshake, causing DPI middleboxes to see the -// forged SNI while the actual connection proceeds normally. -// spoofCount controls how many Write() calls trigger injection (0 = single-shot). -func WrapWithSpoof(c net.Conn, spoofSNI string, spoofMethodStr string, spoofCount int32, serverName string) (net.Conn, error) { - spoofSNI, method, err := tlsspoof.ParseOptions(spoofSNI, spoofMethodStr) - if err != nil { - return nil, errors.New("tls_spoof: invalid options").Base(err) - } - if spoofSNI == "" { - return c, nil - } - if serverName == "" { - return nil, errors.New("tls_spoof: requires a TLS server name (SNI)") - } - if gonet.ParseIP(serverName) != nil { - return nil, errors.New("tls_spoof: cannot spoof when server name is an IP literal") - } - if strings.EqualFold(spoofSNI, serverName) { - return nil, errors.New("tls_spoof: spoof must differ from server_name") - } - wrapped, err := tlsspoof.NewConn(c, method, spoofSNI, int(spoofCount)) - if err != nil { - return nil, errors.New("tls_spoof: failed to create spoof conn").Base(err) - } - return wrapped, nil -} - // Server initiates a TLS server handshake on the given connection. func Server(c net.Conn, config *tls.Config) net.Conn { tlsConn := tls.Server(c, config) diff --git a/transport/internet/tls/tlsspoof/client_hello.go b/transport/internet/tls/tlsspoof/client_hello.go deleted file mode 100644 index b078697c97cc..000000000000 --- a/transport/internet/tls/tlsspoof/client_hello.go +++ /dev/null @@ -1,75 +0,0 @@ -package tlsspoof - -import ( - "bytes" - "context" - "crypto/tls" - - "errors" - "net" - "time" -) - -type writeOnlyConn struct { - net.Conn - w *bytes.Buffer -} - -func (c *writeOnlyConn) Write(b []byte) (int, error) { - return c.w.Write(b) -} - -func (c *writeOnlyConn) Read(b []byte) (int, error) { - return 0, errors.New("read from write-only conn") -} - -func (c *writeOnlyConn) Close() error { - return nil -} - -func (c *writeOnlyConn) LocalAddr() net.Addr { - return nil -} - -func (c *writeOnlyConn) RemoteAddr() net.Addr { - return nil -} - -func (c *writeOnlyConn) SetDeadline(t time.Time) error { - return nil -} - -func (c *writeOnlyConn) SetReadDeadline(t time.Time) error { - return nil -} - -func (c *writeOnlyConn) SetWriteDeadline(t time.Time) error { - return nil -} - -// buildFakeClientHello drives crypto/tls against a write-only in-memory conn -// to capture a generated ClientHello. CurvePreferences pins classical groups -// to suppress Go's default X25519MLKEM768 hybrid key share; without this the -// post-quantum public key alone (~1184 bytes) pushes the record past one MSS, -// and middleboxes do not reassemble fragmented ClientHellos. The handshake -// error is discarded because the stub conn's Read returns immediately. -func buildFakeClientHello(sni string) ([]byte, error) { - if sni == "" { - return nil, errors.New("empty sni") - } - var buf bytes.Buffer - tlsConn := tls.Client(&writeOnlyConn{w: &buf}, &tls.Config{ - ServerName: sni, - // Order matches what browsers advertised before post-quantum. - CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP384}, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS13, - NextProtos: []string{"h2", "http/1.1"}, - InsecureSkipVerify: true, - }) - _ = tlsConn.HandshakeContext(context.Background()) - if buf.Len() == 0 { - return nil, errors.New("tls ClientHello not produced") - } - return buf.Bytes(), nil -} diff --git a/transport/internet/tls/tlsspoof/endpoints.go b/transport/internet/tls/tlsspoof/endpoints.go deleted file mode 100644 index ac0c30484226..000000000000 --- a/transport/internet/tls/tlsspoof/endpoints.go +++ /dev/null @@ -1,27 +0,0 @@ -package tlsspoof - -import ( - "net" - "net/netip" - - "errors" -) - -// The returned addresses are v4-unmapped and share the same family. -func tcpEndpoints(conn net.Conn) (*net.TCPConn, netip.AddrPort, netip.AddrPort, error) { - tcpConn, isTCP := conn.(*net.TCPConn) - if !isTCP { - return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("tls_spoof: underlying conn is not *net.TCPConn") - } - local := tcpConn.LocalAddr().(*net.TCPAddr).AddrPort() - remote := tcpConn.RemoteAddr().(*net.TCPAddr).AddrPort() - if !local.IsValid() || !remote.IsValid() { - return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("tls_spoof: invalid conn address") - } - local = netip.AddrPortFrom(local.Addr().Unmap(), local.Port()) - remote = netip.AddrPortFrom(remote.Addr().Unmap(), remote.Port()) - if local.Addr().Is4() != remote.Addr().Is4() { - return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("tls_spoof: local/remote address family mismatch") - } - return tcpConn, local, remote, nil -} diff --git a/transport/internet/tls/tlsspoof/packet.go b/transport/internet/tls/tlsspoof/packet.go deleted file mode 100644 index 5c23c0631ab8..000000000000 --- a/transport/internet/tls/tlsspoof/packet.go +++ /dev/null @@ -1,163 +0,0 @@ -package tlsspoof - -import ( - "encoding/binary" - "net/netip" - - "fmt" -) - -const ( - defaultTTL uint8 = 64 - defaultWindowSize uint16 = 0xFFFF - tcpHeaderLen = TCPMinimumSize - - tcpOptionMD5Signature = 19 - tcpOptionMD5SignatureLength = 18 - tcpTimestampBackdate = 3600000 -) - -type spoofPacketInfo struct { - seqNum uint32 - ackNum uint32 - corrupt bool - options []byte -} - -func buildTCPSegment( - src netip.AddrPort, - dst netip.AddrPort, - packetInfo spoofPacketInfo, - payload []byte, -) []byte { - if src.Addr().Is4() != dst.Addr().Is4() { - panic("tlsspoof: mixed IPv4/IPv6 address family") - } - var ( - frame []byte - ipHeaderLen int - ) - ipPayloadLen := tcpHeaderLen + len(packetInfo.options) + len(payload) - if src.Addr().Is4() { - ipHeaderLen = IPv4MinimumSize - frame = make([]byte, ipHeaderLen+ipPayloadLen) - ip := IPv4(frame[:ipHeaderLen]) - ip.Encode(uint16(len(frame)), 0, defaultTTL, TCPProtocolNumber, src.Addr(), dst.Addr()) - } else { - ipHeaderLen = IPv6MinimumSize - frame = make([]byte, ipHeaderLen+ipPayloadLen) - ip := IPv6(frame[:ipHeaderLen]) - ip.Encode(uint16(ipPayloadLen), TCPProtocolNumber, defaultTTL, src.Addr(), dst.Addr()) - } - encodeTCP(frame, ipHeaderLen, src, dst, packetInfo, payload) - return frame -} - -func encodeTCP(frame []byte, ipHeaderLen int, src, dst netip.AddrPort, packetInfo spoofPacketInfo, payload []byte) { - tcp := TCP(frame[ipHeaderLen:]) - copy(frame[ipHeaderLen+tcpHeaderLen:], packetInfo.options) - optionsLen := len(packetInfo.options) - copy(frame[ipHeaderLen+tcpHeaderLen+optionsLen:], payload) - tcp.Encode(src.Port(), dst.Port(), packetInfo.seqNum, packetInfo.ackNum, uint8(tcpHeaderLen+optionsLen), TCPFlagAck|TCPFlagPsh, defaultWindowSize) - applyTCPChecksum(tcp, src.Addr(), dst.Addr(), payload, packetInfo.corrupt) -} - -func buildSpoofFrame(method Method, src, dst netip.AddrPort, sendNext, receiveNext, timestamp uint32, tcpOptions, payload []byte) ([]byte, error) { - packetInfo, err := resolveSpoofPacketInfo(method, sendNext, receiveNext, timestamp, tcpOptions, payload) - if err != nil { - return nil, err - } - return buildTCPSegment(src, dst, packetInfo, payload), nil -} - -// buildSpoofTCPSegment returns a TCP segment without an IP header, for -// platforms where the kernel synthesises the IP header (darwin IPv6). -func buildSpoofTCPSegment(method Method, src, dst netip.AddrPort, sendNext, receiveNext, timestamp uint32, payload []byte) ([]byte, error) { - packetInfo, err := resolveSpoofPacketInfo(method, sendNext, receiveNext, timestamp, nil, payload) - if err != nil { - return nil, err - } - segment := make([]byte, tcpHeaderLen+len(packetInfo.options)+len(payload)) - encodeTCP(segment, 0, src, dst, packetInfo, payload) - return segment, nil -} - -func resolveSpoofPacketInfo(method Method, sendNext, receiveNext, timestamp uint32, tcpOptions, payload []byte) (spoofPacketInfo, error) { - packetInfo := spoofPacketInfo{seqNum: sendNext, ackNum: receiveNext} - switch method { - case MethodWrongSequence: - packetInfo.seqNum = sendNext - uint32(len(payload)) - case MethodWrongChecksum: - packetInfo.corrupt = true - case MethodWrongAcknowledgment: - packetInfo.ackNum = receiveNext - uint32(defaultWindowSize/2) - case MethodWrongMD5Sig: - packetInfo.options = buildMD5SignatureOptions() - case MethodWrongTimestamp: - packetInfo.options = buildWrongTimestampOptions(timestamp, tcpOptions) - default: - return packetInfo, fmt.Errorf("tls_spoof: unknown method %v", method) - } - return packetInfo, nil -} - -func buildMD5SignatureOptions() []byte { - options := make([]byte, tcpOptionMD5SignatureLength+2) - options[0] = tcpOptionMD5Signature - options[1] = tcpOptionMD5SignatureLength - return options -} - -func buildWrongTimestampOptions(timestamp uint32, tcpOptions []byte) []byte { - spoofedTimestamp := timestamp - if spoofedTimestamp > tcpTimestampBackdate { - spoofedTimestamp -= tcpTimestampBackdate - } else { - spoofedTimestamp = 0 - } - if rewriteTCPOptionTimestamp(tcpOptions, spoofedTimestamp) { - return tcpOptions - } - options := make([]byte, TCPOptionTSLength+2) - EncodeTSOption(spoofedTimestamp, 0, options) - return options -} - -// rewriteTCPOptionTimestamp finds the TS option in tcpOptions and writes -// timestamp into its TSVal field in place. The caller must own tcpOptions -// (parseTCPPacket already returns a private copy on Windows). -func rewriteTCPOptionTimestamp(tcpOptions []byte, timestamp uint32) bool { - for i := 0; i < len(tcpOptions); { - switch tcpOptions[i] { - case TCPOptionEOL: - return false - case TCPOptionNOP: - i++ - continue - } - if i+1 >= len(tcpOptions) { - return false - } - optionLen := int(tcpOptions[i+1]) - if optionLen < 2 || i+optionLen > len(tcpOptions) { - return false - } - if tcpOptions[i] == TCPOptionTS && optionLen == TCPOptionTSLength { - binary.BigEndian.PutUint32(tcpOptions[i+2:], timestamp) - return true - } - i += optionLen - } - return false -} - -func applyTCPChecksum(tcp TCP, srcAddr, dstAddr netip.Addr, payload []byte, corrupt bool) { - tcpLen := int(tcp.DataOffset()) + len(payload) - pseudo := PseudoHeaderChecksum(TCPProtocolNumber, srcAddr.AsSlice(), dstAddr.AsSlice(), uint16(tcpLen)) - payloadChecksum := Checksum(payload, 0) - tcpChecksum := ^tcp.CalculateChecksum(CombineChecksum(pseudo, payloadChecksum)) - if corrupt { - tcpChecksum ^= 0xFFFF - } - tcp.SetChecksum(tcpChecksum) -} diff --git a/transport/internet/tls/tlsspoof/raw_darwin.go b/transport/internet/tls/tlsspoof/raw_darwin.go deleted file mode 100644 index 3b45d17023be..000000000000 --- a/transport/internet/tls/tlsspoof/raw_darwin.go +++ /dev/null @@ -1,198 +0,0 @@ -package tlsspoof - -import ( - "encoding/binary" - "net" - "net/netip" - "strconv" - "strings" - "sync" - "syscall" - - "errors" - "fmt" - - "golang.org/x/sys/unix" -) - -const PlatformSupported = true - -// Offsets into xinpcb_n within each net.inet.tcp.pcblist_n record, identical -// to the values used by common/process/searcher_darwin_shared.go. -const ( - darwinXinpgenSize = 24 - darwinXsocketOffset = 104 - darwinXinpcbForeignPort = 16 - darwinXinpcbLocalPort = 18 - darwinXinpcbVFlag = 44 - darwinXinpcbForeignAddr = 48 - darwinXinpcbLocalAddr = 64 - darwinXinpcbIPv4Offset = 12 - - darwinTCPExtraSize = 208 - - darwinXtcpcbSndNxtOffset = 56 - darwinXtcpcbRcvNxtOffset = 80 -) - -// darwinStructSize returns the size of xinpcb_n for the running Darwin kernel. -// Darwin 22 (macOS 13 Ventura) grew the struct from 384 to 408 bytes; there is -// no ABI-stable way to read it, so we key off the kernel version. -var darwinStructSize = sync.OnceValues(func() (int, error) { - value, err := syscall.Sysctl("kern.osrelease") - if err != nil { - return 0, func(err error, m string) error { return err }(err, "sysctl kern.osrelease") - } - major, _, ok := strings.Cut(value, ".") - if !ok { - return 0, fmt.Errorf("unexpected kern.osrelease format: %s", value) - } - n, err := strconv.ParseInt(major, 10, 64) - if err != nil { - return 0, func(err error, m string) error { return err }(err, "parse kern.osrelease major version: ") - } - if n >= 22 { - return 408, nil - } - return 384, nil -}) - -type darwinSpoofer struct { - method Method - src netip.AddrPort - dst netip.AddrPort - rawFD int - rawSockAddr unix.Sockaddr - sendNext uint32 - receiveNext uint32 -} - -func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { - if method == MethodWrongTimestamp { - return nil, errors.New("tls_spoof: wrong-timestamp is not supported on macOS") - } - _, src, dst, err := tcpEndpoints(conn) - if err != nil { - return nil, err - } - fd, sockaddr, err := openDarwinRawSocket(src, dst) - if err != nil { - return nil, err - } - sendNext, receiveNext, err := readDarwinTCPSequence(src, dst) - if err != nil { - unix.Close(fd) - return nil, err - } - return &darwinSpoofer{ - method: method, - src: src, - dst: dst, - rawFD: fd, - rawSockAddr: sockaddr, - sendNext: sendNext, - receiveNext: receiveNext, - }, nil -} - -// readDarwinTCPSequence scans net.inet.tcp.pcblist_n for the PCB that matches -// src -> dst and returns (snd_nxt, rcv_nxt). These live in xtcpcb_n at the end -// of each record; see darwin-xnu bsd/netinet/in_pcblist.c:get_pcblist_n. -func readDarwinTCPSequence(src, dst netip.AddrPort) (uint32, uint32, error) { - buffer, err := unix.SysctlRaw("net.inet.tcp.pcblist_n") - if err != nil { - return 0, 0, func(err error, m string) error { return err }(err, "sysctl net.inet.tcp.pcblist_n") - } - structSize, err := darwinStructSize() - if err != nil { - return 0, 0, err - } - itemSize := structSize + darwinTCPExtraSize - for i := darwinXinpgenSize; i+itemSize <= len(buffer); i += itemSize { - inpcb := buffer[i : i+darwinXsocketOffset] - xtcpcb := buffer[i+structSize : i+itemSize] - localPort := binary.BigEndian.Uint16(inpcb[darwinXinpcbLocalPort : darwinXinpcbLocalPort+2]) - remotePort := binary.BigEndian.Uint16(inpcb[darwinXinpcbForeignPort : darwinXinpcbForeignPort+2]) - if localPort != src.Port() || remotePort != dst.Port() { - continue - } - versionFlag := inpcb[darwinXinpcbVFlag] - var localAddr, remoteAddr netip.Addr - switch { - case versionFlag&0x1 != 0: - localAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset : darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset+4])) - remoteAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset : darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset+4])) - case versionFlag&0x2 != 0: - localAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbLocalAddr : darwinXinpcbLocalAddr+16])) - remoteAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbForeignAddr : darwinXinpcbForeignAddr+16])) - default: - continue - } - if localAddr.Unmap() != src.Addr() || remoteAddr.Unmap() != dst.Addr() { - continue - } - sendNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbSndNxtOffset : darwinXtcpcbSndNxtOffset+4]) - receiveNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbRcvNxtOffset : darwinXtcpcbRcvNxtOffset+4]) - return sendNext, receiveNext, nil - } - return 0, 0, fmt.Errorf("tls_spoof: connection %v->%v not found in pcblist_n", src, dst) -} - -func openDarwinRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) { - if dst.Addr().Is4() { - return openIPv4RawSocket(dst) - } - // macOS does not accept IPV6_HDRINCL on AF_INET6 SOCK_RAW IPPROTO_TCP - // sockets, so the kernel builds the IPv6 header itself. Bind to the real - // connection's source address so in6_selectsrc returns it, and rely on - // in6p_cksum defaulting to -1 so the user-supplied TCP checksum is - // preserved (including deliberately corrupted ones). - fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) - if err != nil { - return -1, nil, func(err error, m string) error { return err }(err, "open AF_INET6 SOCK_RAW") - } - err = unix.Bind(fd, &unix.SockaddrInet6{Addr: src.Addr().As16()}) - if err != nil { - unix.Close(fd) - return -1, nil, func(err error, m string) error { return err }(err, "bind AF_INET6 SOCK_RAW") - } - sockaddr := &unix.SockaddrInet6{Port: int(dst.Port()), Addr: dst.Addr().As16()} - return fd, sockaddr, nil -} - -func (s *darwinSpoofer) Inject(payload []byte) error { - if !s.src.Addr().Is4() { - segment, err := buildSpoofTCPSegment(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, payload) - if err != nil { - return err - } - err = unix.Sendto(s.rawFD, segment, 0, s.rawSockAddr) - if err != nil { - return func(err error, m string) error { return err }(err, "sendto raw socket") - } - return nil - } - frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, nil, payload) - if err != nil { - return err - } - // Darwin inherits the historical BSD quirk: with IP_HDRINCL the kernel - // expects ip_len and ip_off in host byte order, not network byte order. - ip := IPv4(frame) - binary.NativeEndian.PutUint16(ip[2:4], ip.TotalLength()) - binary.NativeEndian.PutUint16(ip[6:8], uint16(ip.Flags())<<13|ip.FragmentOffset()) - err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) - if err != nil { - return func(err error, m string) error { return err }(err, "sendto raw socket") - } - return nil -} - -func (s *darwinSpoofer) Close() error { - if s.rawFD < 0 { - return nil - } - err := unix.Close(s.rawFD) - s.rawFD = -1 - return err -} diff --git a/transport/internet/tls/tlsspoof/raw_freebsd.go b/transport/internet/tls/tlsspoof/raw_freebsd.go deleted file mode 100644 index c38a249bf721..000000000000 --- a/transport/internet/tls/tlsspoof/raw_freebsd.go +++ /dev/null @@ -1,172 +0,0 @@ -package tlsspoof - -import ( - "encoding/binary" - "errors" - "fmt" - "net" - "net/netip" - "syscall" - "unsafe" - - "golang.org/x/sys/unix" -) - -const PlatformSupported = true - -// FreeBSD tcp_info offsets for snd_nxt and rcv_nxt. -// Derived from FreeBSD sys/netinet/tcp.h struct tcp_info layout. -// -// struct tcp_info { -// u8 state, __ca, __retrans, __probes, __backoff, opts, wscale = 8 bytes (with pad) -// u32 rto, __ato, snd_mss, rcv_mss = 16 bytes (offset 8) -// u32 __unacked, __sacked, __lost, __retrans, __fackets = 20 bytes (offset 24) -// u32 __last_data_sent, __last_ack_sent, last_data_recv, __last_ack_recv = 16 bytes (offset 44) -// u32 __pmtu, __rcv_ssthresh, rtt, rttvar, snd_ssthresh, snd_cwnd, __advmss, __reordering = 32 bytes (offset 60) -// u32 __rcv_rtt, rcv_space = 8 bytes (offset 92) -// u32 snd_wnd, snd_bwnd = 8 bytes (offset 100) -// u32 snd_nxt, rcv_nxt = 8 bytes (offset 108) -// ... remaining fields -// } -const ( - freebsdTCPInfoSndNxtOffset = 108 - freebsdTCPInfoRcvNxtOffset = 112 - freebsdTCPInfoMinSize = 116 // must read at least through rcv_nxt -) - -type freebsdSpoofer struct { - method Method - src netip.AddrPort - dst netip.AddrPort - rawFD int - rawSockAddr unix.Sockaddr - sendNext uint32 - receiveNext uint32 -} - -func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { - if method == MethodWrongTimestamp { - return nil, errors.New("tls_spoof: wrong-timestamp is not supported on FreeBSD") - } - tcpConn, src, dst, err := tcpEndpoints(conn) - if err != nil { - return nil, err - } - fd, sockaddr, err := openFreeBSDRawSocket(src, dst) - if err != nil { - return nil, err - } - sendNext, receiveNext, err := readFreeBSDTCPSequence(tcpConn) - if err != nil { - unix.Close(fd) - return nil, err - } - return &freebsdSpoofer{ - method: method, - src: src, - dst: dst, - rawFD: fd, - rawSockAddr: sockaddr, - sendNext: sendNext, - receiveNext: receiveNext, - }, nil -} - -// readFreeBSDTCPSequence retrieves snd_nxt and rcv_nxt via TCP_INFO getsockopt. -func readFreeBSDTCPSequence(conn *net.TCPConn) (uint32, uint32, error) { - raw, err := conn.SyscallConn() - if err != nil { - return 0, 0, fmt.Errorf("tls_spoof: SyscallConn: %w", err) - } - var sendNext, receiveNext uint32 - var sockErr error - err = raw.Control(func(fd uintptr) { - buf := make([]byte, 256) // generous buffer for tcp_info - bufLen := uint32(len(buf)) - _, _, errno := syscall.Syscall6( - syscall.SYS_GETSOCKOPT, - fd, - uintptr(syscall.IPPROTO_TCP), - uintptr(0x20), // TCP_INFO = 0x20 - uintptr(unsafe.Pointer(&buf[0])), - uintptr(unsafe.Pointer(&bufLen)), - 0, - ) - if errno != 0 { - sockErr = fmt.Errorf("tls_spoof: getsockopt TCP_INFO: %w", errno) - return - } - if bufLen < freebsdTCPInfoMinSize { - sockErr = fmt.Errorf("tls_spoof: TCP_INFO too short: %d < %d", bufLen, freebsdTCPInfoMinSize) - return - } - sendNext = binary.NativeEndian.Uint32(buf[freebsdTCPInfoSndNxtOffset : freebsdTCPInfoSndNxtOffset+4]) - receiveNext = binary.NativeEndian.Uint32(buf[freebsdTCPInfoRcvNxtOffset : freebsdTCPInfoRcvNxtOffset+4]) - }) - if err != nil { - return 0, 0, err - } - if sockErr != nil { - return 0, 0, sockErr - } - return sendNext, receiveNext, nil -} - -func openFreeBSDRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) { - if dst.Addr().Is4() { - return openIPv4RawSocket(dst) - } - // FreeBSD, like macOS, does not support IPV6_HDRINCL on SOCK_RAW/IPPROTO_TCP. - // The kernel constructs the IPv6 header. Bind to the source address - // and let the kernel fill in the IPv6 header automatically. - fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) - if err != nil { - return -1, nil, fmt.Errorf("tls_spoof: open AF_INET6 SOCK_RAW: %w", err) - } - err = unix.Bind(fd, &unix.SockaddrInet6{Addr: src.Addr().As16()}) - if err != nil { - unix.Close(fd) - return -1, nil, fmt.Errorf("tls_spoof: bind AF_INET6 SOCK_RAW: %w", err) - } - sockaddr := &unix.SockaddrInet6{Port: int(dst.Port()), Addr: dst.Addr().As16()} - return fd, sockaddr, nil -} - -func (s *freebsdSpoofer) Inject(payload []byte) error { - if !s.src.Addr().Is4() { - // IPv6: kernel builds the IP header, we supply TCP segment only. - segment, err := buildSpoofTCPSegment(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, payload) - if err != nil { - return err - } - err = unix.Sendto(s.rawFD, segment, 0, s.rawSockAddr) - if err != nil { - return fmt.Errorf("tls_spoof: sendto raw socket: %w", err) - } - return nil - } - // IPv4: we build the full IP+TCP frame with IP_HDRINCL. - frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, nil, payload) - if err != nil { - return err - } - // FreeBSD inherits the historical BSD quirk: with IP_HDRINCL the kernel - // expects ip_len and ip_off in host byte order, not network byte order. - ip := IPv4(frame) - binary.NativeEndian.PutUint16(ip[2:4], ip.TotalLength()) - binary.NativeEndian.PutUint16(ip[6:8], uint16(ip.Flags())<<13|ip.FragmentOffset()) - err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) - if err != nil { - return fmt.Errorf("tls_spoof: sendto raw socket: %w", err) - } - return nil -} - -func (s *freebsdSpoofer) Close() error { - if s.rawFD < 0 { - return nil - } - err := unix.Close(s.rawFD) - s.rawFD = -1 - return err -} diff --git a/transport/internet/tls/tlsspoof/raw_linux.go b/transport/internet/tls/tlsspoof/raw_linux.go deleted file mode 100644 index dc5c7311869c..000000000000 --- a/transport/internet/tls/tlsspoof/raw_linux.go +++ /dev/null @@ -1,166 +0,0 @@ -package tlsspoof - -import ( - "fmt" - "net" - "net/netip" - - "golang.org/x/sys/unix" -) - -const PlatformSupported = true - -const ( - // Values of enum { TCP_NO_QUEUE, TCP_RECV_QUEUE, TCP_SEND_QUEUE } from - // include/net/tcp.h; not exported by golang.org/x/sys/unix. - tcpRecvQueue = 1 - tcpSendQueue = 2 -) - -type linuxSpoofer struct { - method Method - src netip.AddrPort - dst netip.AddrPort - rawFD int - rawSockAddr unix.Sockaddr - sendNext uint32 - receiveNext uint32 - timestamp uint32 -} - -func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { - tcpConn, src, dst, err := tcpEndpoints(conn) - if err != nil { - return nil, err - } - fd, sockaddr, err := openLinuxRawSocket(dst) - if err != nil { - return nil, err - } - spoofer := &linuxSpoofer{ - method: method, - src: src, - dst: dst, - rawFD: fd, - rawSockAddr: sockaddr, - } - err = spoofer.loadSequenceNumbers(tcpConn) - if err != nil { - unix.Close(fd) - return nil, err - } - return spoofer, nil -} - -func openLinuxRawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) { - if dst.Addr().Is4() { - return openIPv4RawSocket(dst) - } - fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) - if err != nil { - return -1, nil, func(err error, m string) error { return err }(err, "open AF_INET6 SOCK_RAW") - } - err = unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_HDRINCL, 1) - if err != nil { - unix.Close(fd) - return -1, nil, func(err error, m string) error { return err }(err, "set IPV6_HDRINCL") - } - // Linux raw IPv6 sockets interpret sin6_port as a nexthdr protocol number - // (see raw(7)); any value other than 0 or the socket's IPPROTO_TCP causes - // sendto to fail with EINVAL. The destination is already encoded in the - // user-supplied IPv6 header under IPV6_HDRINCL. - sockaddr := &unix.SockaddrInet6{Addr: dst.Addr().As16()} - return fd, sockaddr, nil -} - -// loadSequenceNumbers puts the socket briefly into TCP_REPAIR mode to read -// snd_nxt and rcv_nxt from the kernel. TCP_REPAIR requires CAP_NET_ADMIN; -// callers must run as root or grant both CAP_NET_RAW and CAP_NET_ADMIN. -// -// If the TCP_REPAIR_OFF revert fails, the socket would stay in TCP_REPAIR -// state and subsequent Write() calls would silently buffer instead of sending. -// Surface that error so callers can abort. -func (s *linuxSpoofer) loadSequenceNumbers(tcpConn *net.TCPConn) error { - rawConn, err := tcpConn.SyscallConn() - if err != nil { - return err - } - var ctrlErr error - err = rawConn.Control(func(raw uintptr) { - fd := int(raw) - - if s.method == MethodWrongTimestamp { - timestamp, tsErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_TIMESTAMP) - if tsErr != nil { - ctrlErr = fmt.Errorf("tls_spoof: read timestamp: %w", tsErr) - return - } - s.timestamp = uint32(timestamp) - } - - ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR, unix.TCP_REPAIR_ON) - if ctrlErr != nil { - ctrlErr = fmt.Errorf("tls_spoof: enter TCP_REPAIR (need CAP_NET_ADMIN): %w", ctrlErr) - return - } - defer func() { - offErr := unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR, unix.TCP_REPAIR_OFF) - if offErr != nil { - offErr = fmt.Errorf("tls_spoof: leave TCP_REPAIR: %w", offErr) - if ctrlErr == nil { - ctrlErr = offErr - } else { - ctrlErr = fmt.Errorf("%v; also %w", ctrlErr, offErr) - } - } - }() - - ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR_QUEUE, tcpSendQueue) - if ctrlErr != nil { - ctrlErr = fmt.Errorf("tls_spoof: select TCP_SEND_QUEUE: %w", ctrlErr) - return - } - sendSequence, seqErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_QUEUE_SEQ) - if seqErr != nil { - ctrlErr = fmt.Errorf("tls_spoof: read send queue sequence: %w", seqErr) - return - } - ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR_QUEUE, tcpRecvQueue) - if ctrlErr != nil { - ctrlErr = fmt.Errorf("tls_spoof: select TCP_RECV_QUEUE: %w", ctrlErr) - return - } - receiveSequence, seqErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_QUEUE_SEQ) - if seqErr != nil { - ctrlErr = fmt.Errorf("tls_spoof: read recv queue sequence: %w", seqErr) - return - } - s.sendNext = uint32(sendSequence) - s.receiveNext = uint32(receiveSequence) - }) - if err != nil { - return err - } - return ctrlErr -} - -func (s *linuxSpoofer) Inject(payload []byte) error { - frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, s.timestamp, nil, payload) - if err != nil { - return err - } - err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) - if err != nil { - return func(err error, m string) error { return err }(err, "sendto raw socket") - } - return nil -} - -func (s *linuxSpoofer) Close() error { - if s.rawFD < 0 { - return nil - } - err := unix.Close(s.rawFD) - s.rawFD = -1 - return err -} diff --git a/transport/internet/tls/tlsspoof/raw_stub.go b/transport/internet/tls/tlsspoof/raw_stub.go deleted file mode 100644 index 78be3c23391d..000000000000 --- a/transport/internet/tls/tlsspoof/raw_stub.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build !linux && !darwin && !freebsd && !(windows && (amd64 || 386)) - -package tlsspoof - -import ( - "net" - - "errors" -) - -const PlatformSupported = false - -func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { - return nil, errors.New("tls_spoof: unsupported platform") -} diff --git a/transport/internet/tls/tlsspoof/raw_unix.go b/transport/internet/tls/tlsspoof/raw_unix.go deleted file mode 100644 index ae6c8b9f8b04..000000000000 --- a/transport/internet/tls/tlsspoof/raw_unix.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build linux || darwin || freebsd - -package tlsspoof - -import ( - "net/netip" - - - "golang.org/x/sys/unix" -) - -func openIPv4RawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) { - fd, err := unix.Socket(unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_TCP) - if err != nil { - return -1, nil, func(err error, m string) error { return err }(err, "open AF_INET SOCK_RAW") - } - err = unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_HDRINCL, 1) - if err != nil { - unix.Close(fd) - return -1, nil, func(err error, m string) error { return err }(err, "set IP_HDRINCL") - } - sockaddr := &unix.SockaddrInet4{Port: int(dst.Port())} - sockaddr.Addr = dst.Addr().As4() - return fd, sockaddr, nil -} diff --git a/transport/internet/tls/tlsspoof/raw_windows.go b/transport/internet/tls/tlsspoof/raw_windows.go deleted file mode 100644 index 17878ffce3dd..000000000000 --- a/transport/internet/tls/tlsspoof/raw_windows.go +++ /dev/null @@ -1,234 +0,0 @@ -//go:build windows && (amd64 || 386) - -package tlsspoof - -import ( - "errors" - "net" - "net/netip" - "slices" - "sync" - "sync/atomic" - "time" - - "github.com/xtls/xray-core/transport/internet/tls/tlsspoof/windivert" - "golang.org/x/sys/windows" -) - -const PlatformSupported = true - -// closeGracePeriod caps how long Close() waits for the divert goroutine to -// observe the kernel-emitted real ClientHello and perform the reorder -// (fake → real). In practice this completes in microseconds; the cap -// bounds the pathological case where the kernel buffers the packet. -const closeGracePeriod = 2 * time.Second - -// windowsSpoofer uses a single WinDivert handle for both capture and -// injection. Sequential Send() calls on one handle traverse one driver queue, -// so the fake provably precedes the released real on the wire — a guarantee -// two separate handles cannot make because cross-handle order depends on the -// scheduler. -type windowsSpoofer struct { - method Method - src, dst netip.AddrPort - divertH *windivert.Handle - - fakeReady chan []byte // buffered(1): staged by Inject - done chan struct{} // closed by run() on exit - closeOnce sync.Once - runErr atomic.Pointer[error] -} - -func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { - _, src, dst, err := tcpEndpoints(conn) - if err != nil { - return nil, err - } - filter, err := windivert.OutboundTCP(src, dst) - if err != nil { - return nil, err - } - divertH, err := windivert.Open(filter, windivert.LayerNetwork, 0, 0) - if err != nil { - return nil, err - } - s := &windowsSpoofer{ - method: method, - src: src, - dst: dst, - divertH: divertH, - fakeReady: make(chan []byte, 1), - done: make(chan struct{}), - } - go s.run() - return s, nil -} - -func (s *windowsSpoofer) Inject(payload []byte) error { - select { - case s.fakeReady <- payload: - return nil - case <-s.done: - if p := s.runErr.Load(); p != nil { - return *p - } - return errors.New("tls_spoof: spoofer closed before Inject") - } -} - -func (s *windowsSpoofer) Close() error { - s.closeOnce.Do(func() { - // Give run() a grace window to finish handling the real packet. - select { - case <-s.done: - case <-time.After(closeGracePeriod): - // Force Recv() to return by closing the divert handle. - s.divertH.Close() - <-s.done - } - }) - if p := s.runErr.Load(); p != nil { - return *p - } - return nil -} - -func (s *windowsSpoofer) recordErr(err error) { s.runErr.Store(&err) } - -func (s *windowsSpoofer) run() { - defer close(s.done) - defer s.divertH.Close() - - buf := make([]byte, windivert.MTUMax) - for { - n, addr, err := s.divertH.Recv(buf) - if err != nil { - if errors.Is(err, windows.ERROR_OPERATION_ABORTED) || - errors.Is(err, windows.ERROR_NO_DATA) { - return - } - s.recordErr(err) - return - } - pkt := buf[:n] - seq, ack, tcpOptions, payloadLen, ok := parseTCPPacket(pkt, addr.IPv6()) - if !ok { - // Our filter is OutboundTCP(src, dst); a non-TCP or truncated - // match means driver state is suspect. Re-inject so the kernel - // still sees the byte stream, then abort — continuing would risk - // reordering against an unknown reference point. - _, sendErr := s.divertH.Send(pkt, &addr) - if sendErr != nil { - s.recordErr(sendErr) - return - } - s.recordErr(errors.New("windivert received malformed packet matching spoof filter")) - return - } - if payloadLen == 0 { - // Handshake ACK, keepalive, FIN — pass through unchanged. - _, err := s.divertH.Send(pkt, &addr) - if err != nil { - s.recordErr(err) - return - } - continue - } - - // Non-empty outbound TCP payload = the real ClientHello. - var fake []byte - select { - case fake = <-s.fakeReady: - default: - // Inject() not yet called — pass through and keep observing. - _, err := s.divertH.Send(pkt, &addr) - if err != nil { - s.recordErr(err) - return - } - continue - } - - var timestamp uint32 - if tsVal, hasTS := ParseTCPOptions(tcpOptions); hasTS { - timestamp = tsVal - } - frame, err := buildSpoofFrame(s.method, s.src, s.dst, seq, ack, timestamp, tcpOptions, fake) - if err != nil { - s.recordErr(err) - return - } - fakeAddr := addr // inherit Outbound, IfIdx - // buildSpoofFrame emits ready-to-wire bytes. The driver recomputes - // checksums on Send when TCPChecksum/IPChecksum are 0 — which would - // overwrite the intentionally corrupt checksum in WrongChecksum mode. - // Force both to 1 to keep our bytes intact. - fakeAddr.SetIPChecksum(true) - fakeAddr.SetTCPChecksum(true) - _, err = s.divertH.Send(frame, &fakeAddr) - if err != nil { - s.recordErr(err) - return - } - _, err = s.divertH.Send(pkt, &addr) - if err != nil { - s.recordErr(err) - return - } - return // single-shot reorder complete - } -} - -func parseTCPPacket(pkt []byte, isV6 bool) (seq, ack uint32, options []byte, payloadLen int, ok bool) { - if isV6 { - if len(pkt) < IPv6MinimumSize+TCPMinimumSize { - return 0, 0, nil, 0, false - } - ip := IPv6(pkt) - if ip.TransportProtocol() != TCPProtocolNumber { - return 0, 0, nil, 0, false - } - tcp := TCP(pkt[IPv6MinimumSize:]) - tcpHdr := int(tcp.DataOffset()) - if tcpHdr < TCPMinimumSize || IPv6MinimumSize+tcpHdr > len(pkt) { - return 0, 0, nil, 0, false - } - total := IPv6MinimumSize + int(ip.PayloadLength()) - if total == IPv6MinimumSize || total > len(pkt) { - total = len(pkt) - } - if total < IPv6MinimumSize+tcpHdr { - return 0, 0, nil, 0, false - } - return tcp.SequenceNumber(), tcp.AckNumber(), slices.Clone(tcp.Options()), - total - IPv6MinimumSize - tcpHdr, true - } - if len(pkt) < IPv4MinimumSize+TCPMinimumSize { - return 0, 0, nil, 0, false - } - ip := IPv4(pkt) - if ip.Protocol() != TCPProtocolNumber { - return 0, 0, nil, 0, false - } - ihl := int(ip.HeaderLength()) - // ihl+TCPMinimumSize guards the TCP-header field reads below; without - // this, an IPv4 packet with options (ihl>20) against a 40-byte buffer - // reads past the TCP slice when calling DataOffset. - if ihl < IPv4MinimumSize || ihl+TCPMinimumSize > len(pkt) { - return 0, 0, nil, 0, false - } - tcp := TCP(pkt[ihl:]) - tcpHdr := int(tcp.DataOffset()) - if tcpHdr < TCPMinimumSize || ihl+tcpHdr > len(pkt) { - return 0, 0, nil, 0, false - } - total := int(ip.TotalLength()) - if total == 0 || total > len(pkt) { - total = len(pkt) - } - if total < ihl+tcpHdr { - return 0, 0, nil, 0, false - } - return tcp.SequenceNumber(), tcp.AckNumber(), slices.Clone(tcp.Options()), - total - ihl - tcpHdr, true -} diff --git a/transport/internet/tls/tlsspoof/spoof.go b/transport/internet/tls/tlsspoof/spoof.go deleted file mode 100644 index 6a9eae93a45b..000000000000 --- a/transport/internet/tls/tlsspoof/spoof.go +++ /dev/null @@ -1,182 +0,0 @@ -package tlsspoof - -import ( - "errors" - "fmt" - "net" - "runtime" - "syscall" -) - -type Method int - -const ( - MethodWrongSequence Method = iota - MethodWrongChecksum - MethodWrongAcknowledgment - MethodWrongMD5Sig - MethodWrongTimestamp -) - -const ( - MethodNameWrongSequence = "wrong-sequence" - MethodNameWrongChecksum = "wrong-checksum" - MethodNameWrongAcknowledgment = "wrong-ack" - MethodNameWrongMD5Sig = "wrong-md5" - MethodNameWrongTimestamp = "wrong-timestamp" -) - -func ParseOptions(spoof, method string) (string, Method, error) { - if spoof == "" { - if method != "" { - return "", 0, errors.New("spoof_method requires spoof") - } - return "", 0, nil - } - if net.ParseIP(spoof) != nil { - return "", 0, errors.New("tls_spoof: IP-literal server names are not allowed") - } - if !PlatformSupported { - return "", 0, errors.New("tls_spoof is not supported on this platform") - } - parsedMethod, err := ParseMethod(method) - if err != nil { - return "", 0, err - } - return spoof, parsedMethod, nil -} - -func ParseMethod(s string) (Method, error) { - switch s { - case "", MethodNameWrongSequence: - return MethodWrongSequence, nil - case MethodNameWrongChecksum: - return MethodWrongChecksum, nil - case MethodNameWrongAcknowledgment: - return MethodWrongAcknowledgment, nil - case MethodNameWrongMD5Sig: - return MethodWrongMD5Sig, nil - case MethodNameWrongTimestamp: - return MethodWrongTimestamp, nil - default: - return 0, fmt.Errorf("tls_spoof: unknown method: %s", s) - } -} - -func (m Method) String() string { - switch m { - case MethodWrongSequence: - return MethodNameWrongSequence - case MethodWrongChecksum: - return MethodNameWrongChecksum - case MethodWrongAcknowledgment: - return MethodNameWrongAcknowledgment - case MethodWrongMD5Sig: - return MethodNameWrongMD5Sig - case MethodWrongTimestamp: - return MethodNameWrongTimestamp - default: - return "unknown" - } -} - -type rawSpoofer interface { - Inject(payload []byte) error - Close() error -} - -type Conn struct { - net.Conn - spoofer rawSpoofer - fakeHello []byte - injectionCount int - maxInjections int // how many times to inject; default 1 -} - -// NewConn wraps a connection with TLS spoofing. maxInjections controls how -// many Write() calls will trigger a fake ClientHello injection (0 or 1 = single-shot). -func NewConn(conn net.Conn, method Method, fakeSNI string, maxInjections int) (*Conn, error) { - spoofer, err := newRawSpoofer(conn, method) - if err != nil { - return nil, wrapPermissionError(err) - } - result, err := newConn(conn, spoofer, fakeSNI, maxInjections) - if err != nil { - spoofer.Close() - return nil, err - } - return result, nil -} - -func newConn(conn net.Conn, spoofer rawSpoofer, fakeSNI string, maxInjections int) (*Conn, error) { - fakeHello, err := buildFakeClientHello(fakeSNI) - if err != nil { - return nil, func(err error, m string) error { return err }(err, "tls_spoof: build fake ClientHello") - } - if maxInjections <= 0 { - maxInjections = 1 - } - return &Conn{ - Conn: conn, - spoofer: spoofer, - fakeHello: fakeHello, - maxInjections: maxInjections, - }, nil -} - -func (c *Conn) Write(b []byte) (n int, err error) { - if c.injectionCount >= c.maxInjections { - return c.Conn.Write(b) - } - err = c.spoofer.Inject(c.fakeHello) - if err != nil { - return 0, func(err error, m string) error { return err }(err, "tls_spoof: inject") - } - c.injectionCount++ - if c.injectionCount >= c.maxInjections { - closeErr := c.spoofer.Close() - if closeErr != nil { - return 0, func(err error, m string) error { return err }(closeErr, "tls_spoof: close spoofer") - } - } - return c.Conn.Write(b) -} - -func (c *Conn) Close() error { - return func(e1, e2 error) error { - if e1 != nil { - return e1 - } - return e2 - }(c.Conn.Close(), c.spoofer.Close()) -} - -func (c *Conn) ReaderReplaceable() bool { - return true -} - -func (c *Conn) WriterReplaceable() bool { - return c.injectionCount >= c.maxInjections -} - -func (c *Conn) Upstream() any { - return c.Conn -} - -// wrapPermissionError adds platform-specific hints when the spoofer fails -// due to insufficient privileges. -func wrapPermissionError(err error) error { - if !errors.Is(err, syscall.EPERM) && !errors.Is(err, syscall.EACCES) { - return err - } - switch runtime.GOOS { - case "linux": - return fmt.Errorf("%w\n Hint: run as root, or grant capabilities:\n sudo setcap cap_net_raw,cap_net_admin+ep /path/to/xray", err) - case "darwin": - return fmt.Errorf("%w\n Hint: TLS spoofing requires root on macOS. Run with: sudo ./xray", err) - case "freebsd": - return fmt.Errorf("%w\n Hint: TLS spoofing requires root on FreeBSD. Run with: sudo ./xray", err) - default: - return err - } -} diff --git a/transport/internet/tls/tlsspoof/spoof_freebsd_test.go b/transport/internet/tls/tlsspoof/spoof_freebsd_test.go deleted file mode 100644 index a8ab2ccae823..000000000000 --- a/transport/internet/tls/tlsspoof/spoof_freebsd_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package tlsspoof - -import ( - "net" - "net/netip" - "os/user" - "testing" - - "golang.org/x/sys/unix" -) - -func TestFreeBSDTCPSequence(t *testing.T) { - u, err := user.Current() - if err == nil && u.Uid != "0" { - t.Skip("skipping test; must be root to use raw sockets / TCP_INFO on FreeBSD") - } - - ln, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("Listen failed: %v", err) - } - defer ln.Close() - - serverDone := make(chan struct{}) - go func() { - conn, err := ln.Accept() - if err == nil { - conn.Write([]byte("hello")) - conn.Close() - } - close(serverDone) - }() - - client, err := net.Dial("tcp", ln.Addr().String()) - if err != nil { - t.Fatalf("Dial failed: %v", err) - } - defer client.Close() - - tcpConn, ok := client.(*net.TCPConn) - if !ok { - t.Fatalf("expected *net.TCPConn, got %T", client) - } - - sndNxt, rcvNxt, err := readFreeBSDTCPSequence(tcpConn) - if err != nil { - t.Fatalf("readFreeBSDTCPSequence failed: %v", err) - } - - if sndNxt == 0 && rcvNxt == 0 { - t.Errorf("expected non-zero sequence numbers, got sndNxt=%d rcvNxt=%d", sndNxt, rcvNxt) - } - t.Logf("FreeBSD TCP sequence retrieved: snd_nxt=%d, rcv_nxt=%d", sndNxt, rcvNxt) - <-serverDone -} - -func TestFreeBSDRawSocket(t *testing.T) { - u, err := user.Current() - if err == nil && u.Uid != "0" { - t.Skip("skipping test; must be root to open raw sockets") - } - - dst := netip.MustParseAddrPort("8.8.8.8:443") - src := netip.MustParseAddrPort("127.0.0.1:12345") - - fd, sockaddr, err := openFreeBSDRawSocket(src, dst) - if err != nil { - t.Fatalf("openFreeBSDRawSocket failed: %v", err) - } - defer func() { - if fd >= 0 { - unix.Close(fd) - } - }() - - if fd < 0 { - t.Errorf("expected valid fd, got %d", fd) - } - if sockaddr == nil { - t.Error("expected valid sockaddr, got nil") - } -} diff --git a/transport/internet/tls/tlsspoof/spoof_test.go b/transport/internet/tls/tlsspoof/spoof_test.go deleted file mode 100644 index c51e4fddceb0..000000000000 --- a/transport/internet/tls/tlsspoof/spoof_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package tlsspoof - -import ( - "testing" -) - -func TestBuildFakeClientHello(t *testing.T) { - hello, err := buildFakeClientHello("www.example.com") - if err != nil { - t.Fatal("buildFakeClientHello returned error:", err) - } - if len(hello) == 0 { - t.Fatal("buildFakeClientHello returned empty payload") - } - // TLS record header: content type 0x16 (handshake) - if hello[0] != 0x16 { - t.Fatalf("expected TLS handshake record type 0x16, got 0x%02x", hello[0]) - } - // TLS version: 0x0301 (TLS 1.0 record layer) - if hello[1] != 0x03 || hello[2] != 0x01 { - t.Fatalf("unexpected TLS record version: 0x%02x%02x", hello[1], hello[2]) - } - t.Logf("ClientHello payload length: %d bytes", len(hello)) -} - -func TestBuildFakeClientHelloEmptySNI(t *testing.T) { - _, err := buildFakeClientHello("") - if err == nil { - t.Fatal("expected error for empty SNI") - } -} - -func TestParseMethod(t *testing.T) { - tests := []struct { - input string - expected Method - hasErr bool - }{ - {"", MethodWrongSequence, false}, - {"wrong-sequence", MethodWrongSequence, false}, - {"wrong-checksum", MethodWrongChecksum, false}, - {"wrong-ack", MethodWrongAcknowledgment, false}, - {"wrong-md5", MethodWrongMD5Sig, false}, - {"wrong-timestamp", MethodWrongTimestamp, false}, - {"invalid", 0, true}, - } - for _, tt := range tests { - m, err := ParseMethod(tt.input) - if tt.hasErr { - if err == nil { - t.Errorf("ParseMethod(%q): expected error, got nil", tt.input) - } - continue - } - if err != nil { - t.Errorf("ParseMethod(%q): unexpected error: %v", tt.input, err) - continue - } - if m != tt.expected { - t.Errorf("ParseMethod(%q) = %v, want %v", tt.input, m, tt.expected) - } - } -} - -func TestParseOptions(t *testing.T) { - // Empty spoof should be a no-op - sni, _, err := ParseOptions("", "") - if err != nil { - t.Fatal("ParseOptions(\"\", \"\"): unexpected error:", err) - } - if sni != "" { - t.Fatalf("expected empty SNI, got %q", sni) - } - - // spoof_method without spoof should error - _, _, err = ParseOptions("", "wrong-checksum") - if err == nil { - t.Fatal("expected error when spoof_method set without spoof") - } - - // Valid combo - sni, method, err := ParseOptions("fake.example.com", "wrong-checksum") - if err != nil { - t.Fatal("ParseOptions: unexpected error:", err) - } - if sni != "fake.example.com" { - t.Fatalf("expected SNI 'fake.example.com', got %q", sni) - } - if method != MethodWrongChecksum { - t.Fatalf("expected MethodWrongChecksum, got %v", method) - } - - // IP-literal should be rejected - _, _, err = ParseOptions("1.2.3.4", "wrong-checksum") - if err == nil { - t.Fatal("expected error for IP-literal spoof") - } - _, _, err = ParseOptions("::1", "wrong-checksum") - if err == nil { - t.Fatal("expected error for IP-literal spoof") - } -} - -func TestMethodString(t *testing.T) { - if MethodWrongSequence.String() != "wrong-sequence" { - t.Fatalf("unexpected method string: %s", MethodWrongSequence.String()) - } - if MethodWrongChecksum.String() != "wrong-checksum" { - t.Fatalf("unexpected method string: %s", MethodWrongChecksum.String()) - } -} diff --git a/transport/internet/tls/tlsspoof/tcpip.go b/transport/internet/tls/tlsspoof/tcpip.go deleted file mode 100644 index 62657ccefd68..000000000000 --- a/transport/internet/tls/tlsspoof/tcpip.go +++ /dev/null @@ -1,155 +0,0 @@ -package tlsspoof - -import ( - "encoding/binary" - "net/netip" -) - -const ( - IPv4MinimumSize = 20 - IPv6MinimumSize = 40 - TCPMinimumSize = 20 - TCPProtocolNumber = 6 - - TCPOptionEOL = 0 - TCPOptionNOP = 1 - TCPOptionTS = 8 - TCPOptionTSLength = 10 - - TCPFlagFin = 0x01 - TCPFlagSyn = 0x02 - TCPFlagRst = 0x04 - TCPFlagPsh = 0x08 - TCPFlagAck = 0x10 -) - -func Checksum(data []byte, initial uint16) uint16 { - var csum uint32 = uint32(initial) - for i := 0; i < len(data)-1; i += 2 { - csum += uint32(binary.BigEndian.Uint16(data[i:])) - } - if len(data)%2 == 1 { - csum += uint32(data[len(data)-1]) << 8 - } - for csum > 0xffff { - csum = (csum >> 16) + (csum & 0xffff) - } - return uint16(csum) -} - -func PseudoHeaderChecksum(protocol uint8, srcAddr, dstAddr []byte, totalLen uint16) uint16 { - var csum uint32 - for i := 0; i < len(srcAddr); i += 2 { - csum += uint32(binary.BigEndian.Uint16(srcAddr[i:])) - } - for i := 0; i < len(dstAddr); i += 2 { - csum += uint32(binary.BigEndian.Uint16(dstAddr[i:])) - } - csum += uint32(protocol) - csum += uint32(totalLen) - for csum > 0xffff { - csum = (csum >> 16) + (csum & 0xffff) - } - return uint16(csum) -} - -func CombineChecksum(c1, c2 uint16) uint16 { - csum := uint32(c1) + uint32(c2) - for csum > 0xffff { - csum = (csum >> 16) + (csum & 0xffff) - } - return uint16(csum) -} - -func EncodeTSOption(val uint32, ecr uint32, b []byte) { - b[0] = TCPOptionTS - b[1] = TCPOptionTSLength - binary.BigEndian.PutUint32(b[2:], val) - binary.BigEndian.PutUint32(b[6:], ecr) -} - -func ParseTCPOptions(b []byte) (tsVal uint32, hasTS bool) { - for i := 0; i < len(b); { - if b[i] == TCPOptionEOL { - break - } - if b[i] == TCPOptionNOP { - i++ - continue - } - if i+1 >= len(b) { - break - } - optLen := int(b[i+1]) - if optLen < 2 || i+optLen > len(b) { - break - } - if b[i] == TCPOptionTS && optLen == TCPOptionTSLength { - return binary.BigEndian.Uint32(b[i+2:]), true - } - i += optLen - } - return 0, false -} - -// IPv4 header representation -type IPv4 []byte - -func (b IPv4) TotalLength() uint16 { return binary.BigEndian.Uint16(b[2:]) } -func (b IPv4) Flags() uint8 { return uint8(binary.BigEndian.Uint16(b[6:]) >> 13) } -func (b IPv4) FragmentOffset() uint16 { return binary.BigEndian.Uint16(b[6:]) & 0x1fff } -func (b IPv4) Protocol() uint8 { return b[9] } -func (b IPv4) HeaderLength() uint8 { return (b[0] & 0x0f) * 4 } - -func (b IPv4) Encode(totalLength uint16, id uint16, ttl uint8, protocol uint8, src, dst netip.Addr) { - b[0] = (4 << 4) | 5 // IPv4, Header Length = 20 - b[1] = 0 // TOS - binary.BigEndian.PutUint16(b[2:], totalLength) - binary.BigEndian.PutUint16(b[4:], id) - binary.BigEndian.PutUint16(b[6:], 0) // Flags and Fragment Offset - b[8] = ttl - b[9] = protocol - b[10] = 0 // Checksum (0 for calculation) - copy(b[12:16], src.AsSlice()) - copy(b[16:20], dst.AsSlice()) - csum := Checksum(b[:20], 0) - binary.BigEndian.PutUint16(b[10:], ^csum) -} - -type IPv6 []byte - -func (b IPv6) PayloadLength() uint16 { return binary.BigEndian.Uint16(b[4:]) } -func (b IPv6) TransportProtocol() uint8 { return b[6] } - -func (b IPv6) Encode(payloadLength uint16, transportProtocol uint8, hopLimit uint8, src, dst netip.Addr) { - binary.BigEndian.PutUint32(b[0:], 6<<28) // Version 6, Traffic Class 0, Flow Label 0 - binary.BigEndian.PutUint16(b[4:], payloadLength) - b[6] = transportProtocol - b[7] = hopLimit - copy(b[8:24], src.AsSlice()) - copy(b[24:40], dst.AsSlice()) -} - -type TCP []byte - -func (b TCP) DataOffset() uint8 { return (b[12] >> 4) * 4 } -func (b TCP) SequenceNumber() uint32 { return binary.BigEndian.Uint32(b[4:]) } -func (b TCP) AckNumber() uint32 { return binary.BigEndian.Uint32(b[8:]) } -func (b TCP) Options() []byte { return b[TCPMinimumSize:b.DataOffset()] } -func (b TCP) SetChecksum(csum uint16) { binary.BigEndian.PutUint16(b[16:], csum) } - -func (b TCP) Encode(srcPort, dstPort uint16, seqNum, ackNum uint32, dataOffset uint8, flags uint8, windowSize uint16) { - binary.BigEndian.PutUint16(b[0:], srcPort) - binary.BigEndian.PutUint16(b[2:], dstPort) - binary.BigEndian.PutUint32(b[4:], seqNum) - binary.BigEndian.PutUint32(b[8:], ackNum) - b[12] = (dataOffset / 4) << 4 - b[13] = flags - binary.BigEndian.PutUint16(b[14:], windowSize) - b[16] = 0 // Checksum - binary.BigEndian.PutUint16(b[18:], 0) // Urgent pointer -} - -func (b TCP) CalculateChecksum(initial uint16) uint16 { - return Checksum(b, initial) -} diff --git a/transport/internet/tls/tlsspoof/windivert/assets/LICENSE.txt b/transport/internet/tls/tlsspoof/windivert/assets/LICENSE.txt deleted file mode 100644 index 8489a8e773c3..000000000000 --- a/transport/internet/tls/tlsspoof/windivert/assets/LICENSE.txt +++ /dev/null @@ -1,1191 +0,0 @@ -WinDivert is dual-licensed under your choice of the GNU Lesser General Public -License (LGPL) Version 3 or the GNU General Public License (GPL) Version 2. -Copies of the LGPLv3, GPLv3 and GPLv2 are provided below. - -============================================================================== - - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. - -============================================================================== - - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. - -============================================================================== - - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. - diff --git a/transport/internet/tls/tlsspoof/windivert/assets/WinDivert32.sys b/transport/internet/tls/tlsspoof/windivert/assets/WinDivert32.sys deleted file mode 100644 index d06738cbb78351cc57754fd484b77fac0df52cea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79792 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%0DB0KXuLbP(03=gskd! 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�fDFaN&0OjG}#mY093SN>I=&rkPX=@)ne0>Ce<3;wQg-e3KWzfbe5=9r)Ee=ZMy)^}b&;%D=Wr5V{xe~-hn)XvY3e=ZNxJf5%r|GGR( zQ@aBA4&ET=7~Zk=1bu~97g1mVY>3bR8Q+Rvmsk`0n0))z1li)wAm{E^xIU->M-lM| zB0?ABYzB6j2)J_#o9{3Tb!hvb!E*yG{Xru8nejf!Wk{o(-Ph$>mKXP1_FI-yrX=NW z@Ewusy|&@uA|ccAnq!}@xuC`StqUEv)?T+PwA=DQbJ_9K)&RQ;4kpJ|9QZi#dYqcK zQPebZW3!lsS(Q|h3O>0#Rlz%={LteK#x=1$t>WD-ocmPD53?Mg8CS>NeBPKO9~Xaf zaJ1#Mpyf98b3*nPSU28u#na1L7VFm5S7&2OV}+j`Xzi~U4b#u8b5=9mm}1Rk5`QYw zLC_+5kKO*@!6@I_+rd{`Wn#?=E-cb_-Qv3Hw$qM=B36t=npNDzH%}KNo^6*2Ir1n- z_teXEpFTcYFr`VNnIAx1G)!(dZ~x$IWx4-}vxmKLsiwOZy|+vhQt&!?z;#LTHk{sJ z)Lw3T+5N4hZWp%PjZo0f3w^|;bN5m#9D}G4Q1XJmnZWN#@SD~Tew$&)Y#A@OE<=L! zW>D4#%CZ=x|Jon;0Mg8d1I~f{4}NUz4DBAd_hV6&F{WjzvX|rdvsnPy2m=k*=~F)7 z$d4*o4g9!LgTm+@0RgTn?HmnUsTd-Dwgv{R7UJ#soh&{Cf)6l94c^fJKt2HkqIk!* zd%3_MxL)-E1mZIIX4H`byB=jAmm4&!M)mffd&uG7Ivl6rBXL0&$-01vp2hrm4z zt~1Sdco3%VA1$cyod4khSasUH3O9IMR z;JQDSF~-;R_hsJjbMA9-(xXBs)F3Jyp!EAK+zSx`;8%&D9nUa$iwA)KF9Ov9UuKqO z_VBG1xQ7vc>pvQX6#$sfEDQs~$^mQ+pRh0tK2tKn`u*8y#iWZw6o4`{S{Ths4no28 z3IME|`8$l!1`Pjy`GNZ*3i7+_z_=m4$u$g<127)&E%Xg$`BeA9X|Y;EX#i6MdJC3G zPVieMi9o>rUrV2~9=QAQWBu*fAEEzu_rHk))8{K~7%w0{2M;nh!O5aoA1=HQ#W-kr zm~k8v{13j2i;r!G;WL=vpga73kGOw3I|cX09-OTzDVv@TGyS^^V6Yx(1aoBOQwYv> z@d4QQ9GEURH)aT619M=901N`K-+lh?%-S?Rr`T=j0cK#I+LuDJ4|WKmdIft^?C3OV zkT0SKE;FIf9VxUhYH$#+cSh^=0s?})Jm?hb;NSo!D&5cC!xvEt;Knq{4=`TVm4W)< z)Bx|5-T}tJG|S*1YY$(FH~bia1j<%=gjfW7`2z?5huMWtyr@1=1|D916a*rM$t-xL z9U~eom}U{|YZOGMMIqwnlmNttc5n~33}5$yYe1|)IE_XLqFd8~y(mB}moS?JJJG0g z%8!T;tZY`EKDHDe3aIQwvGQC;@uGuQH^Wdv0Q(7Uj5J#B5Ki?rpiw~I5r`Z%>oCyP zw?@o>lAoMz-Ifv>P6?w2(`MS=$+Z^PS8&W0k;b?R3=cpIa0OBVy+VNNHw0gWQ9#R2 zm{(Br!+j0>C|>?X>k)_`dT^LOEhs>ZvL3W&i&#O?^9l{8(kNy@c61Lff17YhIK|s8 zgc=015xgY=)Uc%lP&~rstFao?08i1PtSK~~U|OID=(j;|I2d{w!c@;f%{w3f;RKJk zL2yXakAi@ZL|A%L!)C_Bl0w%H_W>+ojAk*T3?79a;o`r4#)29~XFON*>vP}Bt|8&} zY$J-P2={CfIIP<@@hso7$NwPX<8AMhGuqZ;6J@VAaW7Lks)CQ zU|fR3>9!PKKoNxo0b4p3?hr)#R-zxZf)WKGN*JZa0l|@aVPU~uR3Hcj(+M6BqV#*2 zEhR8GVzvNrkx^n61j`qgL#7@<-T{x(hUzR3*(y~JF|50xQCi7_<*h}JYEm^qLisv>+ML;Sr0 zg6L{tQNWMnKsb5(d{em&L9kbgK(Nh$=`$egKS2y&7Z`X`KR-JScQQ1#FtnYP{@+={ z|0IkF!41$!1GkLDAQ^t2^*nID-@rLpegOh}NCXT|16QyQY&fTdA#k4t zeBzl8Z0X?tF$DgFX~KeM<-z*{q`_Yd_^bHs?E`RzHXMsL175o}D8qtPHZRaMGoLhO zEVy(8>+-eWuMyD7cA%~m$RAn@zAeG2WGhD18kV_ zoN46?;E{~JdVj|Pm%+~l0$SYwp4EVMD!7Nwvcp_~aic&_aiCQf#z=Sq_y({tq=0)O z!0!zxcLq2d0M7yehb%y>1Pz|I1a0X9m=~ke4Q2}%ca}N4a0EDL3_i{9VCF3oT8IUp z9y|`yC(eTaKLh4)_*WjO0X$LQ7k*QQ1bEg0;Pz!a1)uZ}0c9}fd>MTO>+)Y<(uiZA zr?cY*^Be*_M**D*U!lPd95;gLvigP#<_}EQ%-F(r@LtvL;$yS)nInV&4r_oT81OC} z@P|I9XTN*)_u~(m0dffQ^G94ib9IKN@O%Yta{x5bzEeCa>0s>$aDf#8D23%Y4D`zb zP&O-{oGfKs@{MO7KjfLffOJJ3V}923D9O}2ecd72jxKrp&sZpGzuY6 zY$zTS9}0`oLfN34P+q8=sB}~=>IA9^bq&>tdVqR|a>5niiwOIOz9c6N1Bj_%!?>{4xA_d_Dd-o{b5GB>#|mx)-$bP zEeUOmw!ZcO?IP`R?KU&Mwf>L7iruHl6!AFLb`>u#suxNb(kP z4*4({p^HFBBSD-Ba)8{RATU-fkRoGDR-yt>VW>D%GAb37g(^hVgHgGO>Ozg85NHmx z2wD}LgDys2Kwm|FLbGE$up6*lU<4N9q;Xof&A1F)F77n$8m$>=TURCF3T1D%D=Ll>Zr z0M3@6%h2WMYV<{P9l8;4xdq*Z?m%~=AE5it1Lz_2EA%jW6g`ffL?bXP7!C{%h7Tiz z5yePgq%m?BMT`mt!eB8(j21>0V~8=uSYWI%_Lx-|SByKx8{>xwz=UAvnDv-wOgttT zvjvliNyB7dvM_m=0?ZLiF{T7lhAGEXV=jXK<+KrV71M%g!*pP}F%K|(m;uZX<`rfb zGm5dsI$}5DcH+8m1%xs}IiVViSskH~a21SO8=(Wtq6dUN!T@22U`R9tV`WXWCvG67 z6Q3|<6DP@ww272QIz%cX$!R!g_-gFZIHqw?qe-J(V?aYd6VlYu)YdZ8TBmhHt6ED> zdzW^jPLB>RS%#cM-bcO#&zles`vG5zAzqXeiiomAg`nbr7C8$f^(By0L9`;8gf>BY zp(D^+(YfeT=o<7aN1QMeOavwtlY-fa$p^h|#)x4}K#yFpUf6Zm)7Tm;6K(-+92bng zfWL?Tgcl$v5sU~Pgg79ZF9lSzwH%R=jb)}$7%wy?IMHc4Ag+g^K< z_A%}A+6~%6+VVQ;IwTzn9Uq+vo%1?3bn3}X%rS4zEfuBv^w{dqLnN$`CO`f8sG>8SxUamDo*`A}Nz>NvpxAMUnQAnm{i*Nn)BC zHO;kFYx#mPD$}aass|dWL#tcsfmWZ^fYy-KE1;c5wFI?gwNcuJU~XA!J88RVQ?vuL zBemnSleM>N@7CV0eHZj}q0V9*X`N7@ua4?m1U+RV3y_zPW63+n>EvuM8=t}BorXZ< z0evM6VIT`A2RaY+GUnh2GyzRP%qVsgFNzqKps}1l2F^hoIHR!gQ`Kb zF)Ygy)N^26#!)P2UbFyO3@wFLL7Ss(&`xMKASq$!STG}Vz%0A~=3F;g8l!;0U`QAv zFoOaxp^ zp}kBS($>@t(hk?&sJ#!^g<|baZDF0II_^3l!19#poYkqng$*ag~$lm02|aD0rVt6o6PD9!M0DhOm$+Ht>FAhiol7W^(C>Zu1 z`jml~8iI)l0VUcMuyHO=F-`f%i)3diNVMAxKs%U_NYp}zmyOds#S}?lW?@3IA|O{b zE@d_(3o?+Ly63b3z(UZOn?L!$v4uFtbl-xC~k<8joE>P zO_0e%A0-Zn!8aT{0w#FX`rw2KB#)*etQq z$Z+Q+ooJ5w%ZRa4Or^ zuzOPy>rBgvcTad6h>*4}-rej|@p8QV4MiVq)=)MeFf4CC~SduGiRcYvDcp$5}il)DJ8`_P#xkZT3#}Q3-ve zcvgkUl|iN36KSH&o_Uv&nwM?V*(Tpy_b~q{&W}$ze^+;JdqZ*_@0ua?YfL~ik%trN zpoAJo5{$Iu0u~V#p#pxcr8=4K*k3(9rSvxOXj~g~q?{o(k_(Z@uPiJ;4B4QCaCJfW zo)uySU(iMvAjLw5Xb`agUw}RrSk+Qkc~QvBk0oO0 z=^M9KObgw+c36vAEwe;Ed0KY#P3=Xxych)%fDpvFNWA854HlCxW zRI0}Kx3s@EeyHBmx2~Z9%d8TU`OxL^f+DW$de-eHc+}s!YTODSIB;(6wbKn)^>(uI z1SN=NOdR+UF2YUw@4a(2g=8qq0k59RguTjTKG;VnEf5yn_31p*%_5a!e1a++gB;6F z$nJ9=Pg_IpIFw^oGjhzY?@-X?y8)*UFWg`I((jq$$jOl>%0vy$gbM#ncQ2{FCNHi! z4jSU)fU{lJ7o?0A+TAj=|0SNkfmlxfel%9Hp_ zB_TTKmXoIgkJSq?~twLB|IQk`(Ue@G4tBU$OdZwKx7qthx5F&=VeK!_I1P^StfBI1OIH5}xc6 zcbk$D&`xBIc>0;_)1HB%ehNH`EvetI{=pvW5%1>Xsw^iO*1hOcFDiNvba&|MeVG%w zwddaO?r*xcKl^P{uhcP~h_edP2fNNs#Xm!~zkQjgv^QkO*9*e8zT94jKcr@bPNN(9 zo8SZ^Sus{#YSpU!wUi#42C&*>(u7j&@wvF#lRpd6@mSS^v~w77Y_adgP>4DrN8C7YjDUR#hLq1z?0 zNNLhFPgMDZ(TBG+oxWCzYgpf;S!{ULG}<=#sZ|GUEGUiN#`2=-IJ{n$$zP;-!mrA@2<{b2Lp)#Hc{%0Xlbx^Djt)-i$3&z6uow#%woq~@ z3y{9}-|NU(6^eejC+90J(^OqiS3g7=zuR@url&ut(3D?Qs35FDHBk@$34I=_}B# zEPc#GTx=ozxCi;M)!1s;p=h5v8+_$fCi~{eRi6V8Z@y#|s_i|&E6wg;ZFIXur# z9ntW#x%f(Mp;E`H_~ZejjV|3#BAWzIxh&sQF6J&r@orr^vByU*+##JS4f=^?(0@y- z0hbJDQw$pYU7KR?;P$&q=Ja>9sT!m*txe_sq)mapXyhnu3))CT9-7<7X?JTE>~3uX z?p6Zg!tF0hs`OW{|BzO6QYGo#4w*Yp%BGEv?zG+$Sy8g-$_3=D$Ek|^G?oaJgexZm zHt3pP;TuW-p|7=8>|>xWaX9(Gx-e~B&Wxer@s}iSht5i#?MoEKdv#>%Yu{4dguS6W zRD5_rDkc{lSC#Fs==d44VlBRP8VxdMvWpGwY9f1YyXGUyQoCJ z7e}R5Utju$`}M`PB?bmTWp=5T!h0(ArFLYtunCc9JyEH=*;=BYSCCDJ^uV=;}W)>9v0qC%kyti zIk94U#h-=(cCQG-ZVLvTMjpbfJCw@`CJB3cL%FZp^4NFEAo&e^6(?)mO9XECIbo zPg$|m_-f;!rsX7oSEk3vLcgdp0azC?JVFkL4S0mWenP+1Xp5&cnjpmgU8AwHvBDaS z8Co%|&d% zeaQAHq7o#`sS^@P}YF0e(R>!vsV5Fah#&P3mHm95Xs?wsA1 z$;Q<`#&uOMXSZ8IH$F1!dc5Ag;_=;NuLH+h?d?~@zxc4o=T1iG)cz?G)RX6nQzY+P zl5QuTwq1KBy(V=pXBKVOUgT(&!BO+iLd%Zl<7;VUlMiZ=d0)N87O&Z>Oce2A4Zk;r zQWN%noN;D(I~`Y%m2>#ZxUzcXq7@1OE>F)ztht}^@Pv2Qx)({JTf|X`AGVy$kgtVs?94Wu=a|Q9ZI$*+&VI)ZDf4i8-F%&olQ%X_g#(Mkgdn_ za8=xb;syb24P0(tzDnw%1bf5c4U$>;nQ_jm3hgOhZS=XvOq*fK{^JJ_MXgn`vDhai_FB~scPq_R<>P{1#Mf$W0Z z(*S7$Dm@Cd1d@QET!O;(eiUg3J0n|rTL(LP>2LWjs9DXG5mRnZ}Xi& zr9CweIP-LmK#()$3*r@j>BnCcN1nQ;61yck2AM^cPOHg_m$6SjcdJ0h?(JEZx`?jD zEJtseBvrg@Y%#;$cyB~GH&Ns*vW)X{C)zej?V8tR!Q9FeGLm(VPq561&QQLn&7;Q4 zTN_=j_1twcTS;?V_~6-qUjocZOuMqBVdyytqX5&6&#;LgYTetbS zoRY7_Irh~U=Otds$geGzE_08qUipES+|%}AQ|dOy(A%mR7FScX?g$;qt6BE-j-7O~ zJ2iI|m;ET~vnf=`#MMoUnyZz>jwq=cKY7@*d+h--US|R1t883_JC%9qzJ+^B@r47t zLLH)wP!>H==9Z^pg-n>hlBwg@IaY0sJR7V!&~MIOu(C%O2+&#m05lMhd^iIQ0lbeA=cb*C9ShJ1TUj zqtrm+h}m{Mk`VI}rBC8VWltV$See+i9S54SJDAiaT%P*0Kq(IQ*-IkApFNfh*4i0R#EXmA3}s4IFqg z$OJMfFeuPVA5%+)1jrPzxz_Fn3_mF^hP?~4PdJKk6X~z8OV}O$h|C|J)-^C#9!nUe0I>w{Lh+*$Hs z*zhSjSmNa(%Z!Hg6lu+cddH=nAJohLa=N;JvK-sjTRfrlv>v)pSKy^G=_-OKZflHt z!Jof`t*I~&?N(0YR5oKjKJ^N^|E@^=R@Ky}w!_Ky6!w2c`b3^{7zD-JQGoL72N?t(a-dg93h*EM^$j&tki`)f>3W&wLQ2JBt$w7ug; zq>i4mBIf#$X^K3Ysx;>wYwHv*vhCe0_dX zrUETzm@=NF^P4i*ho#V@mty|XqNSY)B}esloZGgjrD5OYv<%v77mZ+~L0p)N;sLqn z0-P?}r;Jlv2a_rV2UZBPty#BC8XLOtf_y1?HP;a-^?(>P51Y1BXM#?l+q)%h@s-_D z?oV0;)@_k$`XYKG-N11}^G)V1gtwxj?CBL^?ZcVZ45dhJd1aZeUCegG(gqCt7hjjPFdIsVDE6ubH6{S|rcm#o+8-Sc+gl~*SosRl2$w+Xo?=AT$ehSh5Wn819Va)~VN?P{d zJG83JCy&yVFP7~LoIGZlr88p8OFdl`G|Jke$J!)XXFV`p`lZCn!({hG;|gQDfGeZ@ zDGKSyq=4`~NuCYP+tqH2daLN0jM}f-L>O$Q7PZT7&#Y}K?W;LW_$c*Af8+6&63<_1 zyF46kPwO=Y+~ynIt4rsZ>{@Rb>hmIy3b><d>QQ8n=WB-}U`y(b(!B9@r`|mU2uGf)Q+$l!rt8b0bq6kU3mSn#BOpgK%s-j6JY3 zC?pe-nFE6Twr?RUCZq^sjT8p90_Lob)D5idz6Y|HAj4@ps10dCqyj<#ZjPx6_}%&p z*g)6|oN)pfrYZoU&ol~bpC9BiukD*l$G6$|JB0MvUVu>8KUwL8-GA*zyK(uP?m^Ur za^}OTLL23tm9G|#DSkdxdV3FVv^KtgdhdM>biah!MR$@T$1c>m_n{$5K496<4!`u9 z?;^L7;)+>V?_(Q;UL4@CKk>COHZwr^PR7gffIg#@DrY`wYPwX^f4;jlJTQw!8~ezv z`2NnBwwGd;gjLqRZ8a)D42-ixTyVOQb}7<6jlQ4TYGvbb!Cm`KA#ut#a{1RkADP(7 zqGkCcQNZ(Mxgk%u-sOeZ>Mi+KW#0*|>k(BCT3_18T0WG_k%(PcUshkD%I*G|KIuf=v*I2rIWUEG z=TKXnce{pu{bJ_NZLf1=s>!Dc&)C>^T3op#GCHB1*k|e20!heeI=H zF{jt*DFiWZJ52A}#+evY`(&)@;(l%=QNM(PdE_7w=M_SnoH6T)nYzq>qwZhY%$#~J8N-e+_K8zo-AV1+VP~nz>&sg+FkMx4;Yikb_Yg{D1j6Kly(V+vCDyt$@U0grr;dC>FX;sDk zZ4;@uh>xjxuUBSEAn_WL!IJ&p=vF zO;*}JWr@G1zRT0o&sE^#l*CZN_JOnDGz4Y#8 zFw?WM2@kdoQCcvjf%}eo9W~AvS^rWobqV(l&p!U!a_31x2}{y$=`(pv>hpQ6Jr^$2 z#_epJ^MR!%pY`gnv z_5))XJGbW^wz?ZSkaOBX(o}qb(AXLs1QDkoFnDKk|HVSDR~E^7%bhU}Jf`n+Fkxy^ zES3w-hB&))OGYHPb6`rY4>_8rtu)I*#SAzeKVB2HLDl`>KC=%K@0RQk?b_z+`z9yy zb&QZ~hwTb2+4FB6o}(x}Icv2OdA0ittCW6Pk!6Og!Hmu90yeY#2b=lZ_~xw5d_Wey zCtjQr7_y9ySiJgSME#cd;va1$d;Cu}^S6OdvuT3~LYqGtOg7}-HJJaoIH`f2kt2xN z|HtE`vv~ie^)!W>blZ7bNE>93+x#i*~GqX2J zS3QrIsy!XN;d*VexJ6(1J}ZjJ(8{GR%`B4yLq|~!l1{v(y>(eh?njt3`!_D%tke~L zyj$YYo%paVOn0IL1??Wn-tSiuu(9;n*?MSo)Rs*KPqHrD?(e1wO61w#UWHeMZX%5B zm*0ff;eFDflsJMY-EwB)rq#L&3TctzUdjAOt@xYq4mK9)HUrE9#O$@GM732=qMAJu z$XfFMubDX}zTX+xIXPMd79b~TCTEJ18O1XvL5l@apDkiR$+AeNpYJ7G32mLij4c=4 zEAAWR-2Qb{rohY1H_sc9tjGIu<_a3v<6=on->dKydY{YArBw`lY0Sv1a_)Ct z)iySMV1*31Sy_hvi|JT}zLBtvb!hIEg076i<7`LL1m*EA>m_`B9;zw!eDGZ)a<5dO zXPt$r<&v#hTSb(%xv@-{_BWmK3yo0p7twnD&N2z5c|6(J>9oK)8*RK)0q^-SQx~1< z0&%+{*Q{Vp!A#VS@Mzk1`szEQdS!xhUd9q{Nmkk_y>g9S5Y=15u2y>Rk!@39)bV}B z?`saqXz%P0V&N4Duq2((Uu*c5UN?Lt?K3m?LD|4dqwl0Nzufq=_oGB;aG@7OryP1eF#<2QfoyyDg>CUk`7^i>W4A=AQxiOL}$MbC6rlGsp*%p?H9 z!J?nA=YKf1!Sws!%ghvEVnq~0fjq!DQ-u5b4LcHSCVVepMJ;56Nl0KT0E@$-u`Xbh z^}Un@g=T@WKWMn7KD&zA^{GC*)70t9a8t*r4b2(nhF7KZqoj4BUG6);v8$4Ou?HVN4$-l9clrC$`nqYSmx0cyAbObD2G*cANE*uU!?p zmnCfFDacsqASB@$wE?=)vNztEj~_EcZ%8aLRfpa~)g}*7{_NeAUN? zMaVmRC%OARue#sy@Vzr4MuKbU=DH|)?d~3{_ zLcWsT4400G5tEml2B{yWM9r%L<)nqxk4X)-bJOeY9p7f!yGfO=1G`m1ak1Vr>m+_d zE4R)l(UPM z-In4V7#!q1XD4qZ)r%G!7VJX@$!xTcVA}L*iwNQ2#T2uk`fNFPHIjF5WSF$QAC=}U zZ4F*C5CxtI3<(Ybuaf}#WzuK}g@yO@K)4eRPN5MYfCbLn#eiF|tH-#75_bPr2nGS3 zX@b$e5^M@i?Sqs16b7}Rf1cC;u=^;@*yIoM18+9?tAyS{L$5*8+odnM9tAEI&YUtj z?QnJ|r^B`7<(G@zMn#F8=~fm@?PqgOn(udheQFn>X|f69Da9W)(#ZZ`0A<5pjeVK# zzLJAdBoed_vTs4%x>Z`Kp~#`%K79AJZjB_4#re#uUTpWJ&~pL3ftxlMEH0EcyWz|x z^JK@`vP^xut#8Q}*Hw6^<=&SWl4}+)yip_7CR2XaUM?=~2FoR_t5yz+yHPUao`^h7 zEzKp%I<~at9PqxdcTgsBzfGjr^X;Q!h3#BBde$u6 z?u1hl6$`SLS`BSZj&rYW3Dxvy;?hm)h`aqUcHs+)&36bA+Y)A$I4yuvR~gwmAfF5l z#?7Rtb1}2B%`E7_HF73k1`^F?*E@QVQj5&C9TnItyJh3F1XWE>wRP$YdyNH;!!?-Yo3@} z2q7%QsdEEX0e#Z1D`p_hcma*SxsmsBE*E>=sqxUpB=a2gt82pZpI4U>4U--u zK0ohfE4!g9x{jMGa>1DADQ&ujLyAG^)>~!5&Wkt;b3H?kRk~G)W<9%+?cGEOY@}~Z zLKX_5vdU8`M5pfEJx9~NHEFnn$q)0fSxq6!ec82*yORr8w)5To;%9QwIF4x%c>`5| z`&s;8<+-P%45pSCz45j?_Py+W2bAyM@IIdRIuKkBd8)F1#w6 zDAH*v_=s8I*$VA*?`@Xr);aFf7fy`J-LOos_XVx!)yk3H;WZA5+`@>7+bz7g_7V(H-e|Uef zKl5gj{9E=1xPkEGW)@VM)dP(D>e(%7%4{MDY8ZjGT*S#;*&GZ{n#qucctb!Y1rr zx}Z1Ky|25_SLlJ~IvRQPQ27V(ubp_G&>1 zOJ7dH$%5i#6qoaHr5DVuZ)Nk9y)v{%Tx=IpsMP9ux*S`{&<4e0eo064FRIJMCcKMN z@ZJ^T>(@a0Ouaieyg)@3Ga%k9eph~_`eejsRP;#Jft^z8vfWSAzC$m(K7}#OcXnM> z`dC|Oy&^=vinAPfn}Q$M?wc=Ws^qi+--N-Bxv9Y zUObwY_ZOknfKJ2lryYcN{tKsKT`VjM>Kx$ojQADlCy(pVA z#e8YV2b=LaHYY;w-L~Aj^J(|$_s4J{n>jU89fmu@aG^q9{1-S1pY_Mq(pldWXa&|#2nN5TRZ0s+c{|#H)!?uybzmj8)}IJ zi=-H^f&}5Q87l-f)MoNH=Cy-{AYWVEmJ$*iMx_VSqSXB8fp7%m8(Bh3EIdoNz$;q9 z!OL3>5PA?dOu$0M$)4Y&rNd{yX7b)U#~g3$8am|a>33T@BW?BFn>iqP$Pum}#bO1m zgjN)o6_}(Lea|xq_|;A`jC2Ss*gM>7+EM~_0h9&EVp$7o2UQ5KipDT3vCB-Cfgy&^ z)tS%S14FFyCqu06HDiV6!A<{3J2Gr)W<`T0YGkATK`G~lbQOa4>5B#F6Ynu5&nMhQ zB)5(_?SDe%JM3&N%Bm@(q*qzAV0FTwmYQV!`o>GQlP@>c@T8httl5(C*@YwXq>B2Z z+H{}8%F@@9w^5B0$YD#->Fbt0|2lO~^!3fJHU4^GJDl>y%LC1_Sf3uo9UG1fN~!ZN z=J1t2RhOOGQ^dAXH(hl5G3Te*Lgxe)B^Z`CHJsdeGGpTn&7BW7l!vZ9uiVhwwrS}K zfie#@)_i3w`iDLM$?^ zbNRxH0RoLzc%*$kEKs*j@4o&yZqV^HXLpg^>E`9s^$&*EK3gGOdCIGz|FmnoW!{Ek zk}E$d8n2gO+q)9|&|QC^a&hB`F}@cU##CQ1xb~c~v(bh<26r2udp*SOifC56UTi2n zgkJ2^I%w)@&gHIbyob#_GwZ=U%w3ttxGjMtCbav9jDt>i`zg0uW_`xB#JwU52J#Q~ zM82+Xh?QDrE;L?VB-0d{;v3T1Eq2&VsvtKf`ik31+DC>fym_!;R>zC zR3vvsi@l+$9H7f%5;=N^Ez!+Z0UG|73H*9<)XXpXqKYSoX)8ZWHtEwm3JX3QS1biC|1OX*cAS+vqDVstukk# zI3N~TkkART%(%Xt4&@YMK>_`P{8R8#;``Eo3CWFG06fo`r2-RD_@9SH9p^mGgEEDT z3q<2ZJo(b5RyVmz+N(s}_Kf%C%(S)%up2+PJYEr=#BgH3uZoNkq#7Qk9{Fu0J9oM= zLas>g%AtdbQ8AZdjlbT#=O~^rWh|Ch))aEP^!SMr$28Z6Ex_xPHumBOeRW2$@{#m` zExN-tXpi)dH$x7lIr^&{$bgKO?5g&>v_6zDcJac^e3_T-Pg5rk?`7vYYTfQim80G4 zF0f^jdW(OOJ1m*G|LcuCc1<`*`46lW)Ma0hw!wDLwi%aCn;W2cw8*bo%Z{Yb->+wWQDM|d%9i{OLB!^E{6|vcHN)$Hwx+ov5 zkvJ+*^7de&2#9}(OnhfJGcyR2h&%_d!5_jTB4EEuq#bPD{u(ApMB1=%&j<@2IK7;R z)MZITY5_Aw1ak8Yb+R)TGZJX#pU*BUhg2XMX03~>+^gHa=aXamF&&x9N;NN78f&wpxpI}a zhtQXGpO|xF4u(hv)ondt;H&ZW+!MT2RTsJPG`kszDo7pQ`LxS>xQo9c&uau(sgxJ;kZv~$D@c8`Nm@^8Odt3AQ^y!nLv?a2-mz#z99v0;~Fvo3A zMGjZ6mnZ1$`PhDA;dS|?cGVo)G(O0P=^@)z<*4j#TXTEa~?+f3h71ok0<>qTXJW-XuWl|jVI5+UL zqB+&=D!JhK^NQPX9=Y8L4;@qjZ-h1@Ewb12ha^50?jEd)cN-99s;UNOQ{G4WA4+H! z_q%g6X87fr8+Pd$6Cbk0>S|V3yO5#U8Exs8T-^ourZC@>0ZiyODm$Wo?V)aV1mXHhaOLdYT>1(Eq*HGUr6^rE%95HoqJDTa+bK~NhTT7Y5`fK(m z$KB0d=iSDcV<~b@H&E(A?y1NWv9AJemQKjlh38o*sc&Jk+{dZB|GKF69{NLZ4y6Al z>!|KjCN1%BX$~6-Q=mfP0HI{l#N+IfJJbo8Tu(c<<*IwW%oIEHu|e+s3VjLr>n1bK zJba!FJ;!xlTe;tPIIqX%`BCQ@uDw@F@yfSDeu!ZmC=zx6p7p6#Gccj zwbQ=wD##JCFR(4JPOsC5Dq6Fyj;hC)piZ171h*E%6RG z$<}|_){LJ#9a#nq{IRv415>}ra^RS2s%N5A2*C@RTZ}tQdaQ~FsCZXbH&=5TZ9)%5 z{~GU;HT{UwyYwtRb6tygld=3PzMgyMBM0Wt1q!A5dK+6;J`V&5louO2p9H*K-qKxK zZ*tSVD!b@lr&xnsMFJ1gNKN|J(eMJJE9dEb5-onIIg%0=Zj98n9&k;w&`e$HP?mG_ z6xWgF+b@=mAj_7UMcBVq6pI@#U%l{#W9@=X3(AkwjxcX2T3qrD%W7FWb=s+Bf86FO zPeHeb$G;e8Tbdu(6|>~S0%U#DqgBdNXe<8D>xxfe!$0YLcC6P{sf+p&U6N-bA7=Kl z)HvX2)W(x67i9Trjr<040;xWpu^psB&a)MT`qk|mwSvvu%2%04dt$`*y|y^A(F{LU zo_=y5fYnxI>$|EkPPfw+4{J;?6UwWO*O! zsiCd9P$Nt&u7Bv@+mXAD;*5iM=BI^7L<)Z=DvILFU z-&B(9EN(r_l5>tzd$i+{-X_cb>>Iu5Dh&@+ueX|~H#y~OJ{~%FYEtM{C&E$gx*XSA zvkL=Xu4cUPs~p?5`%2B?3@yUy1mTV*Q}r@M@^54T?~} zMGV^U`tHK+Wp>dd52g)`$ERr2Q<5i2#_*SL%e?PrX`Wxa5mOq!IyRM0BH#8)<&dZR zgLMmUxs86=X?LsZ)h5T4Zy#RVapcMFU1vxtHhIrOsJz$lsv$d3S8<9pPs%sTHS}HE z6%k{hhpa$9%^c&J*GEonP$HDvc$sAM%sA*ey0b0?aKWxnPmQ)En<_&*4I+U_p#cKM5$HTFKY*T)_D zeDy}Lt+U@jV@sbtraev>dIGOn0^J^6+?yp4mB?6pDt%Ghb&WEnN5&FG*Ox1NIP^=1 z>w0oN&q=eC?&t-}niXT?4N4|XnQ~)iz)$YZ#Ls4IrV=ggx9><_{JZIj%+c&`F+qW` zLbp|Stq)+W$~h`1TlBv7XV8&W@yQ2lL~l!(ALkI2u&-F{y6vl6d)%%5d16JG$JSME z-)@)nNN+8yHoFU~&29qs3c|*KfJ0NPU^(FA5L}E2bo`6f@}PeyM`rD7nrP!@s(D`E zzdp-5q-qdrEf)i-qk;`+Fmi>pni(C=LJXB*9nP8?TKh+9?L!^;bVfs!=!f%) zy#K7se-$IL&Cbs~rD7x710>u+3Rpth`gM* zX=U_%&&4G?d)`cJxMcgx%P!5@e|Ot2BdMFyzVv^a$oTEtv68>ve2Ve~R+kzaHQ7*g zBj6ub#9arQ;~}|olS@2wHu|~i9u#3I+@JDOWnXdbsXorVn;FABLsD)QU$2*oc46#` zlAM^$JAaABopebJm0cFzv+{l`hGb{C3c9cSxP9G;7oBnbb1z-+Dcc$z!nrY%(WS0t zVPa14Kd#+x9Mfzw|DHbR=vh}cdEe}0q>4yJu|O8RC@J6-N{yU%CnM^=&XzL?B3Qb zFf^{+bNXfVev1N^ixWPcSZ&p1#gSBhvmiOxxA$FB?ow8PgN1Ba0$XH5jUzTMEJ*vb zu!-q3u(M+V9?tr)aQOAK7b$ZA$F_h=Vzv30nWUJ3r$6snXH?3RmH+I`o6?~F$IQ+q z&w9V0vDu)pVM*NpSJOySxWFUZfa|TEU3Oh#pd`T$9~v<>F*7xaf(rl-@Bj-Klo(hc z#$kXfVSxL{47i{MfaVt1fdmti_*y7KNF5@3Wx5VX)(lB6lOgDI7uWzFXjF%30dOxW zNIx_CLWPAeHe=fY0_ssXS~~1YaHu2 zyLEGUh|m4lgortTIpz(SPiFg1OK%I%uX3LCBkyc>@bbq3a|GICmQOf-k$Ji4DW?4Y zHyzG&NOeBm=d!8eoX+My6XvCJm)a+8Xg<{~&(EXkbK6IsIcM|x>=Uacg zHnH#m$5c4M$$9d?MkjbE7Tb^x@=$COqnCjcd?XII>Xa8{tPNvq_02o`45>pdYDs?c z^ZM`D8*(=1Reb9gKCZa=!aGC#C~1SnUq}Wp=^Hk_GH85m(D(#+dK-%Zqo2cL3qg_E znNPczlJw{P)t6tsY5!T#kmnkqU+#PKCKxo%7zFtMd%ywL+&*`)EZ0rFVz}b&jeYlT z{yueYGxvqMj@jW&b+e}(*|`2Wa)Jc*DnV;*iwl=>POWe&-?{eN;*14zI(Wm%WEVNv z>ABb6{j}%BF}cRQhrfsD&7S>8He|Vq7ijmJM+4$Ho>2>qW?+2cq(Rng#^_HGm z_th+kzy;R}UF_MW>~Ne`;*|L}Ji(jsxw*yqrY9n;+UJD?>~@5{>c9U+!7f$IiX&X} z#!I=hlQFi()^1$kFH?G1;zQ<_N0Xg21gG+^p1jn9rT){}AIHx6&A1fyPyYOUy%sKR zK7mQoH5HBcJ{evuPCRMnW#ZMaM^|)$_|AK}qm1%^pDs?aqoGRZ5w-i zm64I?;&p`;pNr;S(rwZUkb7Si(WiD%&uGK)i!VjnCNw?_sLQjNkiB}}nIpT3*K!p# zZQrf!21no7`}EH|J@M|EsgHl^bM3$L4b*W2 E0GFf)F8}}l diff --git a/transport/internet/tls/tlsspoof/windivert/assets/WinDivert64.sys b/transport/internet/tls/tlsspoof/windivert/assets/WinDivert64.sys deleted file mode 100644 index 218ccaf423ef0a67696226f9ef3a09149e4441d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 94144 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 Mok_m={2+h)7u^}>0RR91 diff --git a/transport/internet/tls/tlsspoof/windivert/assets_386.go b/transport/internet/tls/tlsspoof/windivert/assets_386.go deleted file mode 100644 index 0cbf35ed5cbf..000000000000 --- a/transport/internet/tls/tlsspoof/windivert/assets_386.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build windows && 386 - -package windivert - -import _ "embed" - -//go:embed assets/WinDivert32.sys -var sysBytes []byte - -func assetFiles() []assetFile { - return []assetFile{{"WinDivert32.sys", sysBytes}} -} - -func driverSysName() string { return "WinDivert32.sys" } diff --git a/transport/internet/tls/tlsspoof/windivert/assets_amd64.go b/transport/internet/tls/tlsspoof/windivert/assets_amd64.go deleted file mode 100644 index 2c9fb6c6ad19..000000000000 --- a/transport/internet/tls/tlsspoof/windivert/assets_amd64.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build windows && amd64 - -package windivert - -import _ "embed" - -//go:embed assets/WinDivert64.sys -var sysBytes []byte - -func assetFiles() []assetFile { - return []assetFile{{"WinDivert64.sys", sysBytes}} -} - -func driverSysName() string { return "WinDivert64.sys" } diff --git a/transport/internet/tls/tlsspoof/windivert/assets_unsupported.go b/transport/internet/tls/tlsspoof/windivert/assets_unsupported.go deleted file mode 100644 index 04698953fa6b..000000000000 --- a/transport/internet/tls/tlsspoof/windivert/assets_unsupported.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build windows && !amd64 && !386 - -package windivert - -func assetFiles() []assetFile { return nil } - -func driverSysName() string { return "" } diff --git a/transport/internet/tls/tlsspoof/windivert/driver_windows.go b/transport/internet/tls/tlsspoof/windivert/driver_windows.go deleted file mode 100644 index 50e94c578422..000000000000 --- a/transport/internet/tls/tlsspoof/windivert/driver_windows.go +++ /dev/null @@ -1,211 +0,0 @@ -//go:build windows - -package windivert - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "runtime" - "strconv" - "sync" - - "golang.org/x/sys/windows" -) - -const ( - driverServiceName = "WinDivert" - driverDeviceName = `\\.\WinDivert` -) - -var ( - driverOnce sync.Once - driverErr error - // driverDevName is ASCII-safe and must be available before ensureDriver - // so Open can try CreateFile first and only install on FILE_NOT_FOUND. - driverDevName, _ = windows.UTF16PtrFromString(driverDeviceName) -) - -// Requires SeLoadDriverPrivilege (Administrator). Running the 386 build -// under WOW64 on a 64-bit kernel is rejected — use the amd64 build. -func ensureDriver() error { - driverOnce.Do(func() { - driverErr = installDriver() - }) - return driverErr -} - -func installDriver() error { - if runtime.GOARCH == "386" { - var isWow64 bool - err := windows.IsWow64Process(windows.CurrentProcess(), &isWow64) - if err == nil && isWow64 { - return errors.New("windivert: 386 build detected running under WOW64 on a 64-bit kernel; use the amd64 build") - } - } - - dir, err := ensureExtracted() - if err != nil { - return err - } - sysPath := filepath.Join(dir, driverSysName()) - sysPathW, err := windows.UTF16PtrFromString(sysPath) - if err != nil { - return fmt.Errorf("windivert: utf16 driver path: %w", err) - } - - // Serialize driver install across concurrent processes. - mutexName, _ := windows.UTF16PtrFromString("WinDivertDriverInstallMutex") - mutex, err := windows.CreateMutex(nil, false, mutexName) - if err != nil { - return fmt.Errorf("windivert: create install mutex: %w", err) - } - defer windows.CloseHandle(mutex) - _, err = windows.WaitForSingleObject(mutex, windows.INFINITE) - if err != nil { - return fmt.Errorf("windivert: wait install mutex: %w", err) - } - defer windows.ReleaseMutex(mutex) - - manager, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_ALL_ACCESS) - if err != nil { - return fmt.Errorf("windivert: open SCM: %w", err) - } - defer windows.CloseServiceHandle(manager) - - serviceNameW, _ := windows.UTF16PtrFromString(driverServiceName) - service, err := windows.OpenService(manager, serviceNameW, windows.SERVICE_ALL_ACCESS) - if err != nil { - service, err = windows.CreateService( - manager, - serviceNameW, - serviceNameW, - windows.SERVICE_ALL_ACCESS, - windows.SERVICE_KERNEL_DRIVER, - windows.SERVICE_DEMAND_START, - windows.SERVICE_ERROR_NORMAL, - sysPathW, - nil, nil, nil, nil, nil, - ) - if err != nil { - if errors.Is(err, windows.ERROR_SERVICE_EXISTS) { - service, err = windows.OpenService(manager, serviceNameW, windows.SERVICE_ALL_ACCESS) - } - if err != nil { - return wrapDriverInstallError(err) - } - } - } - defer windows.CloseServiceHandle(service) - - err = windows.StartService(service, 0, nil) - if err != nil && errors.Is(err, windows.ERROR_SERVICE_DISABLED) { - // A prior process called DeleteService on a still-running kernel - // driver: SCM marks the record for deletion and flips START_TYPE - // to DISABLED until the last handle closes. Re-enable so we can - // start it instead of waiting for a reboot. - err = windows.ChangeServiceConfig( - service, - windows.SERVICE_NO_CHANGE, - windows.SERVICE_DEMAND_START, - windows.SERVICE_NO_CHANGE, - nil, nil, nil, nil, nil, nil, nil, - ) - if err != nil { - return fmt.Errorf("windivert: re-enable disabled service: %w", err) - } - err = windows.StartService(service, 0, nil) - } - if err == nil { - // Mark for deletion so the driver unregisters when the last handle - // closes or on next reboot. Matches the upstream DLL's behavior: - // only the process that actually started the service takes on the - // cleanup responsibility. If another process already started it, - // we leave DeleteService to them. - _ = windows.DeleteService(service) - } else if !errors.Is(err, windows.ERROR_SERVICE_ALREADY_RUNNING) { - return fmt.Errorf("windivert: start service: %w", err) - } - return nil -} - -func wrapDriverInstallError(err error) error { - if errors.Is(err, windows.ERROR_ACCESS_DENIED) { - return fmt.Errorf("windivert: installing the kernel driver requires Administrator privileges: %w", err) - } - return fmt.Errorf("windivert: create service: %w", err) -} - -type assetFile struct { - name string - data []byte -} - -var ( - extractOnce sync.Once - extractErr error - extractDir string -) - -// The on-disk copy is protected by Windows Authenticode signature -// enforcement, which rejects any tampered .sys at StartService time. -func ensureExtracted() (string, error) { - extractOnce.Do(func() { - extractDir, extractErr = extractImpl() - }) - return extractDir, extractErr -} - -func extractImpl() (string, error) { - files := assetFiles() - if len(files) == 0 { - return "", fmt.Errorf("windivert: unsupported architecture %s", runtime.GOARCH) - } - - base, err := os.UserCacheDir() - if err != nil { - return "", fmt.Errorf("windivert: locate user cache dir: %w", err) - } - dir := filepath.Join(base, "xray-core", "windivert", "v"+AssetVersion) - err = os.MkdirAll(dir, 0o755) - if err != nil { - return "", fmt.Errorf("windivert: mkdir %s: %w", dir, err) - } - - for _, asset := range files { - err = ensureAsset(dir, asset) - if err != nil { - return "", err - } - } - return dir, nil -} - -// Concurrent sing-box processes race on os.Rename (atomic on NTFS); -// whichever wins creates the final file. Writers that lose the race -// silently discard their temp copy. -func ensureAsset(dir string, asset assetFile) error { - target := filepath.Join(dir, asset.name) - _, err := os.Stat(target) - if err == nil { - return nil - } - if !os.IsNotExist(err) { - return fmt.Errorf("windivert: stat %s: %w", asset.name, err) - } - tmp := target + ".tmp-" + strconv.Itoa(os.Getpid()) - err = os.WriteFile(tmp, asset.data, 0o644) - if err != nil { - return fmt.Errorf("windivert: write %s: %w", asset.name, err) - } - err = os.Rename(tmp, target) - if err != nil { - os.Remove(tmp) - if _, statErr := os.Stat(target); statErr == nil { - return nil - } - return fmt.Errorf("windivert: rename %s: %w", asset.name, err) - } - return nil -} diff --git a/transport/internet/tls/tlsspoof/windivert/filter.go b/transport/internet/tls/tlsspoof/windivert/filter.go deleted file mode 100644 index d63adae2b630..000000000000 --- a/transport/internet/tls/tlsspoof/windivert/filter.go +++ /dev/null @@ -1,181 +0,0 @@ -package windivert - -import ( - "encoding/binary" - "errors" - "net/netip" -) - -// WINDIVERT_FILTER VM instruction layout (24 bytes, #pragma pack(1)): -// -// word 0 (LE): field:11 | test:5 | success:16 -// word 1 (LE): failure:16 | neg:1 | reserved:15 -// words 2..5: arg[4] (native-endian uint32 each) -// -// The driver walks this as a decision tree: evaluate the test at inst i; -// on success jump to success; on failure jump to failure. Continuations -// 0x7FFE and 0x7FFF are ACCEPT and REJECT terminals. -const ( - filterInstBytes = 24 - filterMaxInsts = 256 - - fieldZero = 0 - fieldOutbound = 2 - fieldIP = 5 - fieldIPv6 = 6 - fieldTCP = 8 - fieldIPSrcAddr = 21 - fieldIPDstAddr = 22 - fieldIPv6SrcAddr = 28 - fieldIPv6DstAddr = 29 - fieldTCPSrcPort = 38 - fieldTCPDstPort = 39 - - testEQ = 0 - - resultAccept uint16 = 0x7FFE - resultReject uint16 = 0x7FFF -) - -// Filter flags passed to IOCTL_WINDIVERT_STARTUP alongside the compiled -// filter. These tell the driver what *kinds* of packets the filter might -// match, used as a kernel-side fast-reject. -const ( - filterFlagOutbound uint64 = 0x0020 - filterFlagIP uint64 = 0x0040 - filterFlagIPv6 uint64 = 0x0080 -) - -type filterInst struct { - field uint16 // 11 bits used - test uint8 // 5 bits used - success uint16 - failure uint16 - neg bool - arg [4]uint32 -} - -// Filter is a typed specification of packets to capture. It replaces -// WinDivert's filter string language. -// -// Zero value = "reject all" (match nothing), suitable for send-only handles. -type Filter struct { - insts []filterInst - flags uint64 // filter flags for STARTUP ioctl -} - -// reject returns a filter that matches no packet. The empty insts slice -// is encoded as a single rejecting instruction by encode(). -func reject() *Filter { - return &Filter{} -} - -// OutboundTCP returns a filter matching outbound TCP packets on the given -// 5-tuple. Both addresses must share an address family (IPv4 or IPv6). -func OutboundTCP(src, dst netip.AddrPort) (*Filter, error) { - if !src.IsValid() || !dst.IsValid() { - return nil, errors.New("windivert: filter: invalid address port") - } - if src.Addr().Is4() != dst.Addr().Is4() { - return nil, errors.New("windivert: filter: mixed IPv4/IPv6") - } - f := &Filter{ - flags: filterFlagOutbound, - } - // Insts chain as AND: each test's failure = REJECT, success = next inst. - // The final inst's success = ACCEPT. - f.add(fieldOutbound, testEQ, argUint32(1)) - if src.Addr().Is4() { - f.flags |= filterFlagIP - f.add(fieldIP, testEQ, argUint32(1)) - f.add(fieldTCP, testEQ, argUint32(1)) - f.add(fieldIPSrcAddr, testEQ, argIPv4(src.Addr())) - f.add(fieldIPDstAddr, testEQ, argIPv4(dst.Addr())) - } else { - f.flags |= filterFlagIPv6 - f.add(fieldIPv6, testEQ, argUint32(1)) - f.add(fieldTCP, testEQ, argUint32(1)) - f.add(fieldIPv6SrcAddr, testEQ, argIPv6(src.Addr())) - f.add(fieldIPv6DstAddr, testEQ, argIPv6(dst.Addr())) - } - f.add(fieldTCPSrcPort, testEQ, argUint32(uint32(src.Port()))) - f.add(fieldTCPDstPort, testEQ, argUint32(uint32(dst.Port()))) - return f, nil -} - -func (f *Filter) add(field uint16, test uint8, arg [4]uint32) { - f.insts = append(f.insts, filterInst{field: field, test: test, arg: arg}) -} - -func argUint32(v uint32) [4]uint32 { return [4]uint32{v, 0, 0, 0} } - -// argIPv4 encodes an IPv4 address for IP_SRCADDR/IP_DSTADDR. The driver -// compares against an IPv4-mapped-IPv6 form: {host_order_u32, 0x0000FFFF, -// 0, 0} (see sys/windivert.c windivert_get_ipv4_addr and the IPv4_SRCADDR -// val-word construction). Omitting the 0x0000FFFF marker causes the EQ -// test to fail for every packet. -func argIPv4(addr netip.Addr) [4]uint32 { - b := addr.As4() - return [4]uint32{binary.BigEndian.Uint32(b[:]), 0x0000FFFF, 0, 0} -} - -// argIPv6 encodes an IPv6 address for IPV6_SRCADDR/IPV6_DSTADDR. The -// driver stores the address as four host-order uint32s in REVERSED word -// order: val[0]=low (bytes 12..15), val[3]=high (bytes 0..3). See -// sys/windivert.c windivert_outbound_network_v6_classify val-word -// construction. -func argIPv6(addr netip.Addr) [4]uint32 { - b := addr.As16() - return [4]uint32{ - binary.BigEndian.Uint32(b[12:16]), - binary.BigEndian.Uint32(b[8:12]), - binary.BigEndian.Uint32(b[4:8]), - binary.BigEndian.Uint32(b[0:4]), - } -} - -// encode serializes the Filter to the on-wire WINDIVERT_FILTER[] format -// plus the filter_flags for STARTUP ioctl. -func (f *Filter) encode() ([]byte, uint64, error) { - if len(f.insts) == 0 { - // "Reject all" — one instruction, ZERO == 0 is always true, but we - // invert by setting both success and failure to REJECT. - return encodeInst(filterInst{ - field: fieldZero, - test: testEQ, - success: resultReject, - failure: resultReject, - }), 0, nil - } - if len(f.insts) > filterMaxInsts-1 { - return nil, 0, errors.New("windivert: filter too long") - } - buf := make([]byte, 0, filterInstBytes*len(f.insts)) - for i, inst := range f.insts { - if i == len(f.insts)-1 { - inst.success = resultAccept - } else { - inst.success = uint16(i + 1) - } - inst.failure = resultReject - buf = append(buf, encodeInst(inst)...) - } - return buf, f.flags, nil -} - -func encodeInst(inst filterInst) []byte { - out := make([]byte, filterInstBytes) - word0 := uint32(inst.field&0x7FF) | uint32(inst.test&0x1F)<<11 | - uint32(inst.success)<<16 - word1 := uint32(inst.failure) - if inst.neg { - word1 |= 1 << 16 - } - binary.LittleEndian.PutUint32(out[0:4], word0) - binary.LittleEndian.PutUint32(out[4:8], word1) - binary.LittleEndian.PutUint32(out[8:12], inst.arg[0]) - binary.LittleEndian.PutUint32(out[12:16], inst.arg[1]) - binary.LittleEndian.PutUint32(out[16:20], inst.arg[2]) - binary.LittleEndian.PutUint32(out[20:24], inst.arg[3]) - return out -} diff --git a/transport/internet/tls/tlsspoof/windivert/handle_windows.go b/transport/internet/tls/tlsspoof/windivert/handle_windows.go deleted file mode 100644 index c48e6214c11b..000000000000 --- a/transport/internet/tls/tlsspoof/windivert/handle_windows.go +++ /dev/null @@ -1,323 +0,0 @@ -//go:build windows - -package windivert - -import ( - "encoding/binary" - "errors" - "fmt" - "runtime" - "sync" - "unsafe" - - "golang.org/x/sys/windows" -) - -// Handle owns a WinDivert kernel device handle plus a private event for -// overlapped I/O. Methods on *Handle are not safe for concurrent use -// across goroutines (there is a single shared event per Handle). -// -// addr is a per-Handle Address buffer the IOCTL struct embeds a pointer -// to. It lives on the heap (as a field of a heap-allocated Handle) so -// the pointer value stored as bytes in the ioctl buffer remains valid -// across stack growth between buildIoctl* and the DeviceIoControl -// syscall — stack-local Address values are not safe for this pattern -// because Go's escape analysis does not see the pointer through the -// unsafe.Pointer → uintptr → bytes conversion. -type Handle struct { - device windows.Handle - event windows.Handle - closing sync.Once - closeErr error - addr Address -} - -// Filter may be nil for "reject all", suitable for send-only handles. -// Requires Administrator on first call per process (installs the kernel -// driver via SCM); subsequent calls reuse the running driver. -func Open(filter *Filter, layer Layer, priority int16, flags Flag) (*Handle, error) { - err := validateOpenArgs(layer, priority, flags) - if err != nil { - return nil, err - } - if filter == nil { - filter = reject() - } - filterBin, filterFlags, err := filter.encode() - if err != nil { - return nil, err - } - device, err := openDevice() - if err != nil { - if !errors.Is(err, windows.ERROR_FILE_NOT_FOUND) && - !errors.Is(err, windows.ERROR_PATH_NOT_FOUND) { - if errors.Is(err, windows.ERROR_ACCESS_DENIED) { - return nil, fmt.Errorf("windivert: open device (administrator required): %w", err) - } - return nil, fmt.Errorf("windivert: open device: %w", err) - } - // Device node missing: kernel driver not loaded. Install + retry. - // Matches WinDivertOpen's lazy-install path; avoids racing StartService - // against a still-loaded driver whose SCM record is marked for deletion. - err = ensureDriver() - if err != nil { - return nil, err - } - device, err = openDevice() - if err != nil { - if errors.Is(err, windows.ERROR_ACCESS_DENIED) { - return nil, fmt.Errorf("windivert: open device (administrator required): %w", err) - } - return nil, fmt.Errorf("windivert: open device: %w", err) - } - } - event, err := windows.CreateEvent(nil, 1, 0, nil) // manual reset, unsignaled - if err != nil { - windows.CloseHandle(device) - return nil, fmt.Errorf("windivert: create event: %w", err) - } - h := &Handle{device: device, event: event} - - err = h.initialize(layer, priority, flags) - if err != nil { - h.Close() - return nil, err - } - err = h.startup(filterBin, filterFlags) - if err != nil { - h.Close() - return nil, err - } - return h, nil -} - -func openDevice() (windows.Handle, error) { - return windows.CreateFile( - driverDevName, - windows.GENERIC_READ|windows.GENERIC_WRITE, - 0, nil, - windows.OPEN_EXISTING, - windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_OVERLAPPED, - 0, - ) -} - -func validateOpenArgs(layer Layer, priority int16, flags Flag) error { - if layer != LayerNetwork { - return fmt.Errorf("windivert: invalid layer %d", uint32(layer)) - } - if priority < PriorityLowest || priority > PriorityHighest { - return errors.New("windivert: priority out of range") - } - const supportedFlags = FlagSniff | FlagSendOnly - if flags&^supportedFlags != 0 { - return errors.New("windivert: unknown flag bits") - } - if flags&FlagSniff != 0 && flags&FlagSendOnly != 0 { - return errors.New("windivert: FlagSniff and FlagSendOnly are mutually exclusive") - } - return nil -} - -func (h *Handle) initialize(layer Layer, priority int16, flags Flag) error { - in := buildIoctlInitialize(layer, priority, flags) - // WINDIVERT_VERSION is a 64-byte packed struct; only the first 20 - // bytes (magic, major, minor, bits) carry data, the rest is reserved. - var outBuf [versionStructSize]byte - binary.LittleEndian.PutUint64(outBuf[0:8], magicDLL) - binary.LittleEndian.PutUint32(outBuf[8:12], versionMajor) - binary.LittleEndian.PutUint32(outBuf[12:16], versionMinor) - binary.LittleEndian.PutUint32(outBuf[16:20], uint32(unsafe.Sizeof(uintptr(0))*8)) - _, err := doIoctl(h.device, ioctlInitialize, in[:], outBuf[:], h.event) - if err != nil { - return fmt.Errorf("windivert: initialize ioctl: %w", err) - } - gotMagic := binary.LittleEndian.Uint64(outBuf[0:8]) - if gotMagic != magicSYS { - return fmt.Errorf("windivert: driver magic mismatch (got %d)", gotMagic) - } - gotMajor := binary.LittleEndian.Uint32(outBuf[8:12]) - if gotMajor < versionMajor { - gotMinor := binary.LittleEndian.Uint32(outBuf[12:16]) - return fmt.Errorf("windivert: driver version too old: %d.%d", gotMajor, gotMinor) - } - return nil -} - -func (h *Handle) startup(filterBin []byte, filterFlags uint64) error { - in := buildIoctlStartup(filterFlags) - _, err := doIoctl(h.device, ioctlStartup, in[:], filterBin, h.event) - if err != nil { - return fmt.Errorf("windivert: startup ioctl: %w", err) - } - return nil -} - -// If the handle is closed mid-Recv the error wraps ERROR_OPERATION_ABORTED. -func (h *Handle) Recv(buf []byte) (int, Address, error) { - if len(buf) == 0 { - return 0, Address{}, errors.New("windivert: recv: zero-length buffer") - } - h.addr = Address{} - in := buildIoctlRecv(&h.addr) - n, err := doIoctl(h.device, ioctlRecv, in[:], buf, h.event) - runtime.KeepAlive(h) - if err != nil { - return 0, Address{}, err - } - return int(n), h.addr, nil -} - -// The address's Outbound flag controls whether the packet is sent toward -// the wire (outbound=true) or delivered up the stack (outbound=false). -// IfIdx and SubIfIdx can stay zero — the driver uses the routing table -// when IfIdx=0. -func (h *Handle) Send(packet []byte, addr *Address) (int, error) { - if len(packet) == 0 { - return 0, errors.New("windivert: send: empty packet") - } - if addr == nil { - return 0, errors.New("windivert: send: nil address") - } - h.addr = *addr - in := buildIoctlSend(&h.addr) - n, err := doIoctl(h.device, ioctlSend, in[:], packet, h.event) - runtime.KeepAlive(h) - if err != nil { - return 0, err - } - return int(n), nil -} - -// Idempotent. Aborts any in-flight I/O on the handle. -func (h *Handle) Close() error { - h.closing.Do(func() { - var errs []error - if h.device != 0 { - err := windows.CloseHandle(h.device) - if err != nil { - errs = append(errs, err) - } - h.device = 0 - } - if h.event != 0 { - err := windows.CloseHandle(h.event) - if err != nil { - errs = append(errs, err) - } - h.event = 0 - } - h.closeErr = errors.Join(errs...) - }) - return h.closeErr -} - -// IOCTL codes from windivert_device.h. CTL_CODE macro layout: -// -// (DeviceType << 16) | (Access << 14) | (Function << 2) | Method -const ( - fileDeviceNetwork uint32 = 0x12 - accessReadWrite uint32 = 3 // FILE_READ_DATA | FILE_WRITE_DATA - accessRead uint32 = 1 - - methodInDirect uint32 = 1 - methodOutDirect uint32 = 2 -) - -func ctlCode(deviceType, access, function, method uint32) uint32 { - return (deviceType << 16) | (access << 14) | (function << 2) | method -} - -var ( - ioctlInitialize = ctlCode(fileDeviceNetwork, accessReadWrite, 0x921, methodOutDirect) - ioctlStartup = ctlCode(fileDeviceNetwork, accessReadWrite, 0x922, methodInDirect) - ioctlRecv = ctlCode(fileDeviceNetwork, accessRead, 0x923, methodOutDirect) - ioctlSend = ctlCode(fileDeviceNetwork, accessReadWrite, 0x924, methodInDirect) -) - -// Magic numbers exchanged during INITIALIZE. DLL sends magicDLL in the -// version struct; driver returns magicSYS on success. -const ( - magicDLL uint64 = 0x4C4C447669645724 // "$WdivDLL" in LE bytes - magicSYS uint64 = 0x5359537669645723 // "#WdivSYS" in LE bytes -) - -const ( - versionMajor uint32 = 2 - versionMinor uint32 = 2 -) - -// Size of the WINDIVERT_IOCTL union on wire (packed). -const ioctlSize = 16 - -// Size of WINDIVERT_VERSION on wire (packed). Only the first 20 bytes -// carry data; the rest is reserved zero padding. -const versionStructSize = 64 - -// doIoctl performs a single synchronous (blocking) overlapped -// DeviceIoControl. The handle is opened with FILE_FLAG_OVERLAPPED so -// DeviceIoControl returns ERROR_IO_PENDING; we then wait for completion -// via GetOverlappedResult. Event is passed in so callers can reuse it -// across calls on the same handle (avoids per-call CreateEvent). -func doIoctl(handle windows.Handle, code uint32, in []byte, out []byte, event windows.Handle) (uint32, error) { - var overlapped windows.Overlapped - overlapped.HEvent = event - _ = windows.ResetEvent(event) - - var inPtr *byte - var inLen uint32 - if len(in) > 0 { - inPtr = &in[0] - inLen = uint32(len(in)) - } - var outPtr *byte - var outLen uint32 - if len(out) > 0 { - outPtr = &out[0] - outLen = uint32(len(out)) - } - var returned uint32 - err := windows.DeviceIoControl(handle, code, inPtr, inLen, outPtr, outLen, &returned, &overlapped) - if err != nil && !errors.Is(err, windows.ERROR_IO_PENDING) { - return 0, err - } - err = windows.GetOverlappedResult(handle, &overlapped, &returned, true) - if err != nil { - return 0, err - } - return returned, nil -} - -func buildIoctlInitialize(layer Layer, priority int16, flags Flag) [ioctlSize]byte { - var buf [ioctlSize]byte - binary.LittleEndian.PutUint32(buf[0:4], uint32(layer)) - // The driver expects priority + WINDIVERT_PRIORITY_HIGHEST (30000) so - // the low range maps to non-negative integers. - binary.LittleEndian.PutUint32(buf[4:8], uint32(int32(priority)+int32(PriorityHighest))) - binary.LittleEndian.PutUint64(buf[8:16], uint64(flags)) - return buf -} - -func buildIoctlStartup(filterFlags uint64) [ioctlSize]byte { - var buf [ioctlSize]byte - binary.LittleEndian.PutUint64(buf[0:8], filterFlags) - return buf -} - -// buildIoctlRecv packs a user-space pointer to a WINDIVERT_ADDRESS into -// the ioctl struct. The driver dereferences it to write the address for -// the received packet. Caller must keep the Address alive via -// runtime.KeepAlive. -func buildIoctlRecv(addr *Address) [ioctlSize]byte { - var buf [ioctlSize]byte - binary.LittleEndian.PutUint64(buf[0:8], uint64(uintptr(unsafe.Pointer(addr)))) - binary.LittleEndian.PutUint64(buf[8:16], 0) - return buf -} - -func buildIoctlSend(addr *Address) [ioctlSize]byte { - var buf [ioctlSize]byte - binary.LittleEndian.PutUint64(buf[0:8], uint64(uintptr(unsafe.Pointer(addr)))) - binary.LittleEndian.PutUint64(buf[8:16], uint64(unsafe.Sizeof(Address{}))) - return buf -} diff --git a/transport/internet/tls/tlsspoof/windivert/windivert.go b/transport/internet/tls/tlsspoof/windivert/windivert.go deleted file mode 100644 index 9d309886cbe3..000000000000 --- a/transport/internet/tls/tlsspoof/windivert/windivert.go +++ /dev/null @@ -1,78 +0,0 @@ -// Package windivert provides a pure-Go binding to the WinDivert kernel -// driver on Windows (amd64 and 386). User-mode WinDivert calls are -// reimplemented in Go; only the signed kernel driver is embedded as an -// asset, since SCM-installed drivers must live on disk and their -// Authenticode signature forbids modification. -// -// Administrator is required for the first Open in a process so SCM can -// load the driver. Upstream: https://github.com/basil00/WinDivert v2.2.2, -// redistributed under its LGPL v3 option; see assets/LICENSE.txt. -package windivert - -import "unsafe" - -const AssetVersion = "2.2.2" - -// MTUMax is WINDIVERT_MTU_MAX from windivert.h (40 + 0xFFFF). Suitable as -// a single-packet receive buffer size. -const MTUMax = 40 + 0xFFFF - -type Layer uint32 - -const LayerNetwork Layer = 0 - -type Flag uint64 - -const ( - // FlagSniff opens a passive observer: the driver copies matching packets - // to userspace without removing them from the network stack. Send is not - // required (and not allowed) on a sniffing handle. - FlagSniff Flag = 0x0001 - // FlagSendOnly opens a write-only injection handle; Recv is not allowed. - FlagSendOnly Flag = 0x0008 -) - -const ( - PriorityHighest int16 = 30000 - PriorityLowest int16 = -30000 -) - -// Address mirrors WINDIVERT_ADDRESS from windivert.h (80 bytes, -// little-endian on both amd64 and 386): -// -// 0: INT64 Timestamp -// 8: UINT32 bitfield: Layer:8 | Event:8 | flags | Reserved1:8 -// 12: UINT32 Reserved2 -// 16: 64 bytes union (WINDIVERT_DATA_NETWORK / FLOW / SOCKET / REFLECT) -type Address struct { - Timestamp int64 - bits uint32 - Reserved2 uint32 - union [64]byte -} - -var _ [80]byte = [unsafe.Sizeof(Address{})]byte{} - -// Bit positions inside the Address's packed flags word. -const ( - addrBitIPv6 = 20 - addrBitIPChecksum = 21 - addrBitTCPChecksum = 22 -) - -func getFlagBit(bits uint32, pos uint) bool { return bits&(1< Date: Sun, 10 May 2026 10:30:00 +0600 Subject: [PATCH 10/42] Transport: Remove TLS spoof integration from dialers --- transport/internet/grpc/dial.go | 6 ------ transport/internet/httpupgrade/dialer.go | 6 ------ transport/internet/kcp/dialer.go | 6 ------ transport/internet/splithttp/dialer.go | 6 ------ transport/internet/tcp/dialer.go | 10 ---------- transport/internet/websocket/dialer.go | 8 -------- 6 files changed, 42 deletions(-) diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index b17caa9730fc..c8b8423c6579 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -140,12 +140,6 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in if config.ServerName == "" && address.Family().IsDomain() { config.ServerName = address.Domain() } - if spoofConn, err := tls.WrapWithSpoof(c, tlsConfig.Spoof, tlsConfig.SpoofMethod, tlsConfig.SpoofCount, config.ServerName); err != nil { - c.Close() - return nil, err - } else { - c = spoofConn - } if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil { return tls.UClient(c, config, fingerprint), nil } else { // Fallback to normal gRPC TLS diff --git a/transport/internet/httpupgrade/dialer.go b/transport/internet/httpupgrade/dialer.go index bb9df1c912fb..571797f6172d 100644 --- a/transport/internet/httpupgrade/dialer.go +++ b/transport/internet/httpupgrade/dialer.go @@ -66,12 +66,6 @@ func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings * tConfig := tls.ConfigFromStreamSettings(streamSettings) if tConfig != nil { tlsConfig := tConfig.GetTLSConfig(tls.WithDestination(dest), tls.WithNextProto("http/1.1")) - if spoofConn, err := tls.WrapWithSpoof(pconn, tConfig.Spoof, tConfig.SpoofMethod, tConfig.SpoofCount, tlsConfig.ServerName); err != nil { - pconn.Close() - return nil, err - } else { - pconn = spoofConn - } if fingerprint := tls.GetFingerprint(tConfig.Fingerprint); fingerprint != nil { conn = tls.UClient(pconn, tlsConfig, fingerprint) if err := conn.(*tls.UConn).WebsocketHandshakeContext(ctx); err != nil { diff --git a/transport/internet/kcp/dialer.go b/transport/internet/kcp/dialer.go index e3ff0bdc9a19..a0e9c8aae25a 100644 --- a/transport/internet/kcp/dialer.go +++ b/transport/internet/kcp/dialer.go @@ -98,12 +98,6 @@ func DialKCP(ctx context.Context, dest net.Destination, streamSettings *internet if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { tlsConfig := config.GetTLSConfig(tls.WithDestination(dest)) - if spoofConn, err := tls.WrapWithSpoof(iConn, config.Spoof, config.SpoofMethod, config.SpoofCount, tlsConfig.ServerName); err != nil { - iConn.Close() - return nil, err - } else { - iConn = spoofConn.(stat.Connection) - } iConn = tls.Client(iConn, tlsConfig) } diff --git a/transport/internet/splithttp/dialer.go b/transport/internet/splithttp/dialer.go index d35a6f7c3db8..f89c71ed9a07 100644 --- a/transport/internet/splithttp/dialer.go +++ b/transport/internet/splithttp/dialer.go @@ -138,12 +138,6 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea } if gotlsConfig != nil { - if spoofConn, err := tls.WrapWithSpoof(conn, tlsConfig.Spoof, tlsConfig.SpoofMethod, tlsConfig.SpoofCount, gotlsConfig.ServerName); err != nil { - conn.Close() - return nil, err - } else { - conn = spoofConn - } if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil { conn = tls.UClient(conn, gotlsConfig, fingerprint) if err := conn.(*tls.UConn).HandshakeContext(ctxInner); err != nil { diff --git a/transport/internet/tcp/dialer.go b/transport/internet/tcp/dialer.go index e226a5657cb3..92fa7557f13a 100644 --- a/transport/internet/tcp/dialer.go +++ b/transport/internet/tcp/dialer.go @@ -74,11 +74,6 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me } } if fingerprint := tls.GetFingerprint(config.Fingerprint); fingerprint != nil { - if spoofConn, err := tls.WrapWithSpoof(conn, config.Spoof, config.SpoofMethod, config.SpoofCount, tlsConfig.ServerName); err != nil { - return nil, err - } else { - conn = spoofConn - } conn = tls.UClient(conn, tlsConfig, fingerprint) if len(tlsConfig.NextProtos) == 1 && tlsConfig.NextProtos[0] == "http/1.1" { // allow manually specify err = conn.(*tls.UConn).WebsocketHandshakeContext(ctx) @@ -86,11 +81,6 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me err = conn.(*tls.UConn).HandshakeContext(ctx) } } else { - if spoofConn, err := tls.WrapWithSpoof(conn, config.Spoof, config.SpoofMethod, config.SpoofCount, tlsConfig.ServerName); err != nil { - return nil, err - } else { - conn = spoofConn - } conn = tls.Client(conn, tlsConfig) err = conn.(*tls.Conn).HandshakeContext(ctx) } diff --git a/transport/internet/websocket/dialer.go b/transport/internet/websocket/dialer.go index f6eb73e1edae..8e295da062e8 100644 --- a/transport/internet/websocket/dialer.go +++ b/transport/internet/websocket/dialer.go @@ -94,14 +94,6 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in pconn = newConn } - // Wrap with TLS spoofing if configured - if spoofConn, err := tls.WrapWithSpoof(pconn, tConfig.Spoof, tConfig.SpoofMethod, tConfig.SpoofCount, tlsConfig.ServerName); err != nil { - pconn.Close() - return nil, err - } else { - pconn = spoofConn - } - // TLS and apply the handshake cn := tls.UClient(pconn, tlsConfig, fingerprint).(*tls.UConn) if err := cn.WebsocketHandshakeContext(ctx); err != nil { From 84b0e278fde09d25d477651bfc8c4768b16603ab Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Sun, 10 May 2026 11:00:00 +0600 Subject: [PATCH 11/42] Config: Add rawpacket tcpmask support --- infra/conf/transport_internet.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 7bd481652b43..62f3c8036a8d 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -23,6 +23,7 @@ import ( "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/finalmask/fragment" "github.com/xtls/xray-core/transport/internet/finalmask/header/custom" + "github.com/xtls/xray-core/transport/internet/finalmask/rawpacket" "github.com/xtls/xray-core/transport/internet/finalmask/header/dns" "github.com/xtls/xray-core/transport/internet/finalmask/header/dtls" "github.com/xtls/xray-core/transport/internet/finalmask/header/srtp" @@ -1237,6 +1238,7 @@ var ( tcpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{ "header-custom": func() interface{} { return new(HeaderCustomTCP) }, "fragment": func() interface{} { return new(FragmentMask) }, + "rawpacket": func() interface{} { return new(RawpacketMask) }, "sudoku": func() interface{} { return new(Sudoku) }, }, "type", "settings") @@ -1447,6 +1449,23 @@ func (c *FragmentMask) Build() (proto.Message, error) { return config, nil } +type RawpacketMask struct { + Payload string `json:"payload"` + Method string `json:"method"` + TTL int32 `json:"ttl"` + Count int32 `json:"count"` +} + +func (c *RawpacketMask) Build() (proto.Message, error) { + config := &rawpacket.Config{ + Payload: c.Payload, + Method: c.Method, + Ttl: uint32(c.TTL), + Count: c.Count, + } + return config, nil +} + type NoiseItem struct { Rand Int32Range `json:"rand"` RandRange *Int32Range `json:"randRange"` From f2557ab53cec82a185a54c1936137c38c5b01d23 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Sun, 10 May 2026 11:49:44 +0600 Subject: [PATCH 12/42] Fix protoc version header in generated proto files --- transport/internet/finalmask/rawpacket/config.pb.go | 2 +- transport/internet/tls/config.pb.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/transport/internet/finalmask/rawpacket/config.pb.go b/transport/internet/finalmask/rawpacket/config.pb.go index f1e3982bb2d4..26f8b63d26ca 100644 --- a/transport/internet/finalmask/rawpacket/config.pb.go +++ b/transport/internet/finalmask/rawpacket/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v7.34.1 +// protoc v6.33.5 // source: transport/internet/finalmask/rawpacket/config.proto package rawpacket diff --git a/transport/internet/tls/config.pb.go b/transport/internet/tls/config.pb.go index 5f7688a5c512..84aa9aeb4236 100644 --- a/transport/internet/tls/config.pb.go +++ b/transport/internet/tls/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v7.34.1 +// protoc v6.33.5 // source: transport/internet/tls/config.proto package tls From 14fd93615fd4fd1aad9e5a6fe57015bc2b310884 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Sun, 10 May 2026 06:52:41 +0000 Subject: [PATCH 13/42] Fix protoc version header in generated proto files --- transport/internet/finalmask/rawpacket/config.pb.go | 2 +- transport/internet/kcp/dialer.go | 3 +-- transport/internet/tls/config.pb.go | 11 +++++------ 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/transport/internet/finalmask/rawpacket/config.pb.go b/transport/internet/finalmask/rawpacket/config.pb.go index f1e3982bb2d4..26f8b63d26ca 100644 --- a/transport/internet/finalmask/rawpacket/config.pb.go +++ b/transport/internet/finalmask/rawpacket/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v7.34.1 +// protoc v6.33.5 // source: transport/internet/finalmask/rawpacket/config.proto package rawpacket diff --git a/transport/internet/kcp/dialer.go b/transport/internet/kcp/dialer.go index a0e9c8aae25a..175998ec7dd3 100644 --- a/transport/internet/kcp/dialer.go +++ b/transport/internet/kcp/dialer.go @@ -97,8 +97,7 @@ func DialKCP(ctx context.Context, dest net.Destination, streamSettings *internet var iConn stat.Connection = session if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { - tlsConfig := config.GetTLSConfig(tls.WithDestination(dest)) - iConn = tls.Client(iConn, tlsConfig) + iConn = tls.Client(iConn, config.GetTLSConfig(tls.WithDestination(dest))) } return iConn, nil diff --git a/transport/internet/tls/config.pb.go b/transport/internet/tls/config.pb.go index 5f7688a5c512..37628755eb4f 100644 --- a/transport/internet/tls/config.pb.go +++ b/transport/internet/tls/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v7.34.1 +// protoc v6.33.5 // source: transport/internet/tls/config.proto package tls @@ -201,11 +201,10 @@ type Config struct { RejectUnknownSni bool `protobuf:"varint,12,opt,name=reject_unknown_sni,json=rejectUnknownSni,proto3" json:"reject_unknown_sni,omitempty"` MasterKeyLog string `protobuf:"bytes,15,opt,name=master_key_log,json=masterKeyLog,proto3" json:"master_key_log,omitempty"` // Lists of string as CurvePreferences values. - CurvePreferences []string `protobuf:"bytes,16,rep,name=curve_preferences,json=curvePreferences,proto3" json:"curve_preferences,omitempty"` - VerifyPeerCertByName []string `protobuf:"bytes,17,rep,name=verify_peer_cert_by_name,json=verifyPeerCertByName,proto3" json:"verify_peer_cert_by_name,omitempty"` - EchServerKeys []byte `protobuf:"bytes,18,opt,name=ech_server_keys,json=echServerKeys,proto3" json:"ech_server_keys,omitempty"` - EchConfigList string `protobuf:"bytes,19,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"` - // Deprecated + CurvePreferences []string `protobuf:"bytes,16,rep,name=curve_preferences,json=curvePreferences,proto3" json:"curve_preferences,omitempty"` + VerifyPeerCertByName []string `protobuf:"bytes,17,rep,name=verify_peer_cert_by_name,json=verifyPeerCertByName,proto3" json:"verify_peer_cert_by_name,omitempty"` + EchServerKeys []byte `protobuf:"bytes,18,opt,name=ech_server_keys,json=echServerKeys,proto3" json:"ech_server_keys,omitempty"` + EchConfigList string `protobuf:"bytes,19,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"` EchForceQuery string `protobuf:"bytes,20,opt,name=ech_force_query,json=echForceQuery,proto3" json:"ech_force_query,omitempty"` EchSocketSettings *internet.SocketConfig `protobuf:"bytes,21,opt,name=ech_socket_settings,json=echSocketSettings,proto3" json:"ech_socket_settings,omitempty"` PinnedPeerCertSha256 [][]byte `protobuf:"bytes,22,rep,name=pinned_peer_cert_sha256,json=pinnedPeerCertSha256,proto3" json:"pinned_peer_cert_sha256,omitempty"` From 1744befa572aeef1a64b900de15d42fdde27b117 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Fri, 29 May 2026 04:38:53 +0600 Subject: [PATCH 14/42] Rawpacket: Fix injection Write/Close order;; add sni field and fake-hello CLI --- infra/conf/transport_internet.go | 2 + main/commands/all/tls/fakehello.go | 48 +++++ main/commands/all/tls/tls.go | 1 + .../finalmask/rawpacket/client_hello.go | 51 +++++ .../internet/finalmask/rawpacket/config.pb.go | 17 +- .../internet/finalmask/rawpacket/config.proto | 4 + .../internet/finalmask/rawpacket/conn.go | 41 ++-- .../internet/finalmask/rawpacket/conn_test.go | 181 ++++++++++++++---- 8 files changed, 292 insertions(+), 53 deletions(-) create mode 100644 main/commands/all/tls/fakehello.go create mode 100644 transport/internet/finalmask/rawpacket/client_hello.go diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 62f3c8036a8d..bcd3b91d411e 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -1451,6 +1451,7 @@ func (c *FragmentMask) Build() (proto.Message, error) { type RawpacketMask struct { Payload string `json:"payload"` + Sni string `json:"sni"` Method string `json:"method"` TTL int32 `json:"ttl"` Count int32 `json:"count"` @@ -1459,6 +1460,7 @@ type RawpacketMask struct { func (c *RawpacketMask) Build() (proto.Message, error) { config := &rawpacket.Config{ Payload: c.Payload, + Sni: c.Sni, Method: c.Method, Ttl: uint32(c.TTL), Count: c.Count, diff --git a/main/commands/all/tls/fakehello.go b/main/commands/all/tls/fakehello.go new file mode 100644 index 000000000000..d7d84bca6041 --- /dev/null +++ b/main/commands/all/tls/fakehello.go @@ -0,0 +1,48 @@ +package tls + +import ( + "encoding/base64" + "fmt" + "os" + + "github.com/xtls/xray-core/main/commands/base" + "github.com/xtls/xray-core/transport/internet/finalmask/rawpacket" +) + +var cmdFakeHello = &base.Command{ + UsageLine: "{{.Exec}} tls fake-hello [-hex] ", + Short: "Generate a fake TLS ClientHello payload for rawpacket", + Long: ` +Generate a fake TLS ClientHello for use in finalmask rawpacket settings. + +Arguments: + + -base64 + Output base64-encoded payload (default). + -hex + Output hex-encoded payload instead of base64. +`, +} + +func init() { + cmdFakeHello.Run = executeFakeHello +} + +var fakeHelloHex = cmdFakeHello.Flag.Bool("hex", false, "") + +func executeFakeHello(cmd *base.Command, args []string) { + if cmdFakeHello.Flag.NArg() < 1 { + base.Fatalf("sni not specified") + } + sni := cmdFakeHello.Flag.Arg(0) + payload, err := rawpacket.BuildFakeClientHello(sni) + if err != nil { + base.Fatalf("failed to build ClientHello: %s", err) + } + switch { + case *fakeHelloHex: + fmt.Fprintf(os.Stdout, "%x\n", payload) + default: + fmt.Fprintln(os.Stdout, base64.StdEncoding.EncodeToString(payload)) + } +} diff --git a/main/commands/all/tls/tls.go b/main/commands/all/tls/tls.go index 17a9465a7851..27bc4e8c373c 100644 --- a/main/commands/all/tls/tls.go +++ b/main/commands/all/tls/tls.go @@ -15,5 +15,6 @@ var CmdTLS = &base.Command{ cmdPing, cmdHash, cmdECH, + cmdFakeHello, }, } diff --git a/transport/internet/finalmask/rawpacket/client_hello.go b/transport/internet/finalmask/rawpacket/client_hello.go new file mode 100644 index 000000000000..04eec64584da --- /dev/null +++ b/transport/internet/finalmask/rawpacket/client_hello.go @@ -0,0 +1,51 @@ +package rawpacket + +import ( + "bytes" + "context" + "crypto/tls" + "errors" + "io" + "net" + "time" +) + +// BuildFakeClientHello drives crypto/tls against a write-only in-memory conn +// to capture a generated ClientHello. CurvePreferences pins classical groups +// to suppress Go's default X25519MLKEM768 hybrid key share; without this the +// post-quantum public key alone (~1184 bytes) pushes the record past one MSS, +// and middleboxes do not reassemble fragmented ClientHellos. The handshake +// error is discarded because the stub conn's Read returns immediately. +func BuildFakeClientHello(sni string) ([]byte, error) { + if sni == "" { + return nil, errors.New("empty sni") + } + var buf bytes.Buffer + tlsConn := tls.Client(&writeOnlyConn{w: &buf}, &tls.Config{ + ServerName: sni, + // Order matches what browsers advertised before post-quantum. + CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP384}, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS13, + NextProtos: []string{"h2", "http/1.1"}, + InsecureSkipVerify: true, + }) + _ = tlsConn.HandshakeContext(context.Background()) + if buf.Len() == 0 { + return nil, errors.New("tls ClientHello not produced") + } + return buf.Bytes(), nil +} + +type writeOnlyConn struct { + w io.Writer +} + +func (c *writeOnlyConn) Read([]byte) (int, error) { return 0, io.EOF } +func (c *writeOnlyConn) Write(p []byte) (int, error) { return c.w.Write(p) } +func (c *writeOnlyConn) Close() error { return nil } +func (c *writeOnlyConn) LocalAddr() net.Addr { return nil } +func (c *writeOnlyConn) RemoteAddr() net.Addr { return nil } +func (c *writeOnlyConn) SetDeadline(time.Time) error { return nil } +func (c *writeOnlyConn) SetReadDeadline(time.Time) error { return nil } +func (c *writeOnlyConn) SetWriteDeadline(time.Time) error { return nil } diff --git a/transport/internet/finalmask/rawpacket/config.pb.go b/transport/internet/finalmask/rawpacket/config.pb.go index 26f8b63d26ca..9845957d9d85 100644 --- a/transport/internet/finalmask/rawpacket/config.pb.go +++ b/transport/internet/finalmask/rawpacket/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v6.33.5 +// protoc v7.34.1 // source: transport/internet/finalmask/rawpacket/config.proto package rawpacket @@ -24,7 +24,10 @@ const ( type Config struct { state protoimpl.MessageState `protogen:"open.v1"` // Base64-encoded fake payload bytes to inject before the real traffic. + // When empty, sni is used to auto-generate a TLS ClientHello. Payload string `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + // Fake SNI hostname for auto-generated ClientHello when payload is empty. + Sni string `protobuf:"bytes,5,opt,name=sni,proto3" json:"sni,omitempty"` // Corruption method to make the fake packet dropped by the server. // Available: wrong-sequence, wrong-checksum, wrong-ack, wrong-md5, wrong-timestamp. Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"` @@ -74,6 +77,13 @@ func (x *Config) GetPayload() string { return "" } +func (x *Config) GetSni() string { + if x != nil { + return x.Sni + } + return "" +} + func (x *Config) GetMethod() string { if x != nil { return x.Method @@ -99,9 +109,10 @@ var File_transport_internet_finalmask_rawpacket_config_proto protoreflect.FileDe const file_transport_internet_finalmask_rawpacket_config_proto_rawDesc = "" + "\n" + - "3transport/internet/finalmask/rawpacket/config.proto\x12+xray.transport.internet.finalmask.rawpacket\"b\n" + + "3transport/internet/finalmask/rawpacket/config.proto\x12+xray.transport.internet.finalmask.rawpacket\"t\n" + "\x06Config\x12\x18\n" + - "\apayload\x18\x01 \x01(\tR\apayload\x12\x16\n" + + "\apayload\x18\x01 \x01(\tR\apayload\x12\x10\n" + + "\x03sni\x18\x05 \x01(\tR\x03sni\x12\x16\n" + "\x06method\x18\x02 \x01(\tR\x06method\x12\x10\n" + "\x03ttl\x18\x03 \x01(\rR\x03ttl\x12\x14\n" + "\x05count\x18\x04 \x01(\x05R\x05countB\xa3\x01\n" + diff --git a/transport/internet/finalmask/rawpacket/config.proto b/transport/internet/finalmask/rawpacket/config.proto index f6b3e8dcb10e..8a25852468b9 100644 --- a/transport/internet/finalmask/rawpacket/config.proto +++ b/transport/internet/finalmask/rawpacket/config.proto @@ -8,8 +8,12 @@ option java_multiple_files = true; message Config { // Base64-encoded fake payload bytes to inject before the real traffic. + // When empty, sni is used to auto-generate a TLS ClientHello. string payload = 1; + // Fake SNI hostname for auto-generated ClientHello when payload is empty. + string sni = 5; + // Corruption method to make the fake packet dropped by the server. // Available: wrong-sequence, wrong-checksum, wrong-ack, wrong-md5, wrong-timestamp. string method = 2; diff --git a/transport/internet/finalmask/rawpacket/conn.go b/transport/internet/finalmask/rawpacket/conn.go index 188b145ea2f7..cc618cff462a 100644 --- a/transport/internet/finalmask/rawpacket/conn.go +++ b/transport/internet/finalmask/rawpacket/conn.go @@ -75,18 +75,27 @@ type Conn struct { } func NewConnClient(cfg *Config, conn net.Conn) (net.Conn, error) { - if cfg.Payload == "" { + if cfg.Payload == "" && cfg.Sni == "" { return conn, nil } if !PlatformSupported { return nil, errors.New("rawpacket is not supported on this platform") } - payload, err := base64.StdEncoding.DecodeString(cfg.Payload) - if err != nil { - return nil, fmt.Errorf("rawpacket: invalid base64 payload: %w", err) - } - if len(payload) == 0 { - return nil, errors.New("rawpacket: payload is empty") + var payload []byte + var err error + if cfg.Payload != "" { + payload, err = base64.StdEncoding.DecodeString(cfg.Payload) + if err != nil { + return nil, fmt.Errorf("rawpacket: invalid base64 payload: %w", err) + } + if len(payload) == 0 { + return nil, errors.New("rawpacket: payload is empty") + } + } else { + payload, err = BuildFakeClientHello(cfg.Sni) + if err != nil { + return nil, fmt.Errorf("rawpacket: build fake ClientHello: %w", err) + } } method, err := ParseMethod(cfg.Method) if err != nil { @@ -120,18 +129,24 @@ func (c *Conn) Write(b []byte) (n int, err error) { if c.injectionCount >= c.maxInjections { return c.Conn.Write(b) } + closeSpoofer := false + defer func() { + if closeSpoofer { + if closeErr := c.spoofer.Close(); closeErr != nil && err == nil { + err = fmt.Errorf("rawpacket: close spoofer: %w", closeErr) + } + } + }() err = c.spoofer.Inject(c.fakePayload) if err != nil { return 0, fmt.Errorf("rawpacket: inject: %w", err) } c.injectionCount++ if c.injectionCount >= c.maxInjections { - closeErr := c.spoofer.Close() - if closeErr != nil { - return 0, fmt.Errorf("rawpacket: close spoofer: %w", closeErr) - } + closeSpoofer = true } - return c.Conn.Write(b) + n, err = c.Conn.Write(b) + return n, err } func (c *Conn) Close() error { @@ -164,6 +179,8 @@ func wrapPermissionError(err error) error { return fmt.Errorf("%w\n Hint: rawpacket requires root on macOS. Run with: sudo ./xray", err) case "freebsd": return fmt.Errorf("%w\n Hint: rawpacket requires root on FreeBSD. Run with: sudo ./xray", err) + case "windows": + return fmt.Errorf("%w\n Hint: rawpacket requires Administrator on Windows (WinDivert driver)", err) default: return err } diff --git a/transport/internet/finalmask/rawpacket/conn_test.go b/transport/internet/finalmask/rawpacket/conn_test.go index ab77ae584e26..242d00a41a56 100644 --- a/transport/internet/finalmask/rawpacket/conn_test.go +++ b/transport/internet/finalmask/rawpacket/conn_test.go @@ -1,46 +1,151 @@ package rawpacket import ( + "errors" + "io" + "net" + "sync" "testing" + "time" ) -func TestParseMethod(t *testing.T) { - tests := []struct { - input string - expected Method - hasErr bool - }{ - {"", MethodWrongSequence, false}, - {"wrong-sequence", MethodWrongSequence, false}, - {"wrong-checksum", MethodWrongChecksum, false}, - {"wrong-ack", MethodWrongAcknowledgment, false}, - {"wrong-md5", MethodWrongMD5Sig, false}, - {"wrong-timestamp", MethodWrongTimestamp, false}, - {"invalid", 0, true}, - } - for _, tt := range tests { - m, err := ParseMethod(tt.input) - if tt.hasErr { - if err == nil { - t.Errorf("ParseMethod(%q): expected error, got nil", tt.input) - } - continue - } - if err != nil { - t.Errorf("ParseMethod(%q): unexpected error: %v", tt.input, err) - continue - } - if m != tt.expected { - t.Errorf("ParseMethod(%q) = %v, want %v", tt.input, m, tt.expected) - } - } -} - -func TestMethodString(t *testing.T) { - if MethodWrongSequence.String() != "wrong-sequence" { - t.Fatalf("unexpected method string: %s", MethodWrongSequence.String()) - } - if MethodWrongChecksum.String() != "wrong-checksum" { - t.Fatalf("unexpected method string: %s", MethodWrongChecksum.String()) +type mockSpoofer struct { + mu sync.Mutex + calls []string + injectErr error + closeErr error +} + +func (m *mockSpoofer) Inject([]byte) error { + m.mu.Lock() + defer m.mu.Unlock() + m.calls = append(m.calls, "inject") + return m.injectErr +} + +func (m *mockSpoofer) Close() error { + m.mu.Lock() + defer m.mu.Unlock() + m.calls = append(m.calls, "close") + return m.closeErr +} + +func (m *mockSpoofer) callOrder() []string { + m.mu.Lock() + defer m.mu.Unlock() + out := make([]string, len(m.calls)) + copy(out, m.calls) + return out +} + +type recordingConn struct { + mu sync.Mutex + writes [][]byte +} + +func (c *recordingConn) Read([]byte) (int, error) { return 0, io.EOF } +func (c *recordingConn) Write(b []byte) (int, error) { + c.mu.Lock() + defer c.mu.Unlock() + dup := make([]byte, len(b)) + copy(dup, b) + c.writes = append(c.writes, dup) + return len(b), nil +} +func (c *recordingConn) Close() error { return nil } +func (c *recordingConn) LocalAddr() net.Addr { return nil } +func (c *recordingConn) RemoteAddr() net.Addr { return nil } +func (c *recordingConn) SetDeadline(time.Time) error { return nil } +func (c *recordingConn) SetReadDeadline(time.Time) error { return nil } +func (c *recordingConn) SetWriteDeadline(time.Time) error { return nil } + +func (c *recordingConn) wrotePayloads() [][]byte { + c.mu.Lock() + defer c.mu.Unlock() + out := make([][]byte, len(c.writes)) + for i, w := range c.writes { + dup := make([]byte, len(w)) + copy(dup, w) + out[i] = dup + } + return out +} + +func TestWriteCallOrder(t *testing.T) { + spoofer := &mockSpoofer{} + client, server := net.Pipe() + defer client.Close() + defer server.Close() + + go func() { + _, _ = io.ReadAll(server) + }() + + conn := &Conn{ + Conn: client, + spoofer: spoofer, + fakePayload: []byte("fake"), + maxInjections: 1, + } + + if _, err := conn.Write([]byte("real")); err != nil { + t.Fatalf("Write: %v", err) + } + + order := spoofer.callOrder() + if len(order) != 2 || order[0] != "inject" || order[1] != "close" { + t.Fatalf("call order = %v, want [inject close]", order) + } +} + +func TestWriteCloseAfterUnderlyingWrite(t *testing.T) { + spoofer := &mockSpoofer{} + rec := &recordingConn{} + conn := &Conn{ + Conn: rec, + spoofer: spoofer, + fakePayload: []byte("fake"), + maxInjections: 1, + } + + if _, err := conn.Write([]byte("real")); err != nil { + t.Fatalf("Write: %v", err) + } + if len(rec.wrotePayloads()) != 1 { + t.Fatalf("expected one underlying write, got %d", len(rec.wrotePayloads())) + } + if order := spoofer.callOrder(); len(order) != 2 || order[1] != "close" { + t.Fatalf("close not last: %v", order) } } + +func TestBuildFakeClientHello(t *testing.T) { + hello, err := BuildFakeClientHello("hcaptcha.com") + if err != nil { + t.Fatalf("buildFakeClientHello: %v", err) + } + if len(hello) == 0 { + t.Fatal("empty ClientHello") + } + if hello[0] != 0x16 { + t.Fatalf("expected TLS handshake record (0x16), got 0x%x", hello[0]) + } +} + +func TestBuildFakeClientHelloEmptySNI(t *testing.T) { + _, err := BuildFakeClientHello("") + if err == nil { + t.Fatal("expected error for empty sni") + } +} + +type discardConn struct{} + +func (discardConn) Read([]byte) (int, error) { return 0, io.EOF } +func (discardConn) Write([]byte) (int, error) { return 0, errors.New("unexpected write") } +func (discardConn) Close() error { return nil } +func (discardConn) LocalAddr() net.Addr { return nil } +func (discardConn) RemoteAddr() net.Addr { return nil } +func (discardConn) SetDeadline(time.Time) error { return nil } +func (discardConn) SetReadDeadline(time.Time) error { return nil } +func (discardConn) SetWriteDeadline(time.Time) error { return nil } From 0198cf2745fb73be3afa3631f79a680496ca5679 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Fri, 29 May 2026 04:41:00 +0600 Subject: [PATCH 15/42] Update config.pb.go --- transport/internet/finalmask/rawpacket/config.pb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transport/internet/finalmask/rawpacket/config.pb.go b/transport/internet/finalmask/rawpacket/config.pb.go index 9845957d9d85..2ba1457af33c 100644 --- a/transport/internet/finalmask/rawpacket/config.pb.go +++ b/transport/internet/finalmask/rawpacket/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v7.34.1 +// protoc v6.33.5 // source: transport/internet/finalmask/rawpacket/config.proto package rawpacket From 1d2602853b96af66f354eb2f62dc3e89d638aba8 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Sat, 25 Apr 2026 14:00:00 +0600 Subject: [PATCH 16/42] TLSSpoof: Add core package for fake ClientHello injection --- .../internet/tls/tlsspoof/client_hello.go | 75 ++++++++ transport/internet/tls/tlsspoof/endpoints.go | 27 +++ transport/internet/tls/tlsspoof/packet.go | 163 ++++++++++++++++ transport/internet/tls/tlsspoof/spoof.go | 182 ++++++++++++++++++ transport/internet/tls/tlsspoof/spoof_test.go | 111 +++++++++++ transport/internet/tls/tlsspoof/tcpip.go | 155 +++++++++++++++ 6 files changed, 713 insertions(+) create mode 100644 transport/internet/tls/tlsspoof/client_hello.go create mode 100644 transport/internet/tls/tlsspoof/endpoints.go create mode 100644 transport/internet/tls/tlsspoof/packet.go create mode 100644 transport/internet/tls/tlsspoof/spoof.go create mode 100644 transport/internet/tls/tlsspoof/spoof_test.go create mode 100644 transport/internet/tls/tlsspoof/tcpip.go diff --git a/transport/internet/tls/tlsspoof/client_hello.go b/transport/internet/tls/tlsspoof/client_hello.go new file mode 100644 index 000000000000..b078697c97cc --- /dev/null +++ b/transport/internet/tls/tlsspoof/client_hello.go @@ -0,0 +1,75 @@ +package tlsspoof + +import ( + "bytes" + "context" + "crypto/tls" + + "errors" + "net" + "time" +) + +type writeOnlyConn struct { + net.Conn + w *bytes.Buffer +} + +func (c *writeOnlyConn) Write(b []byte) (int, error) { + return c.w.Write(b) +} + +func (c *writeOnlyConn) Read(b []byte) (int, error) { + return 0, errors.New("read from write-only conn") +} + +func (c *writeOnlyConn) Close() error { + return nil +} + +func (c *writeOnlyConn) LocalAddr() net.Addr { + return nil +} + +func (c *writeOnlyConn) RemoteAddr() net.Addr { + return nil +} + +func (c *writeOnlyConn) SetDeadline(t time.Time) error { + return nil +} + +func (c *writeOnlyConn) SetReadDeadline(t time.Time) error { + return nil +} + +func (c *writeOnlyConn) SetWriteDeadline(t time.Time) error { + return nil +} + +// buildFakeClientHello drives crypto/tls against a write-only in-memory conn +// to capture a generated ClientHello. CurvePreferences pins classical groups +// to suppress Go's default X25519MLKEM768 hybrid key share; without this the +// post-quantum public key alone (~1184 bytes) pushes the record past one MSS, +// and middleboxes do not reassemble fragmented ClientHellos. The handshake +// error is discarded because the stub conn's Read returns immediately. +func buildFakeClientHello(sni string) ([]byte, error) { + if sni == "" { + return nil, errors.New("empty sni") + } + var buf bytes.Buffer + tlsConn := tls.Client(&writeOnlyConn{w: &buf}, &tls.Config{ + ServerName: sni, + // Order matches what browsers advertised before post-quantum. + CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP384}, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS13, + NextProtos: []string{"h2", "http/1.1"}, + InsecureSkipVerify: true, + }) + _ = tlsConn.HandshakeContext(context.Background()) + if buf.Len() == 0 { + return nil, errors.New("tls ClientHello not produced") + } + return buf.Bytes(), nil +} diff --git a/transport/internet/tls/tlsspoof/endpoints.go b/transport/internet/tls/tlsspoof/endpoints.go new file mode 100644 index 000000000000..ac0c30484226 --- /dev/null +++ b/transport/internet/tls/tlsspoof/endpoints.go @@ -0,0 +1,27 @@ +package tlsspoof + +import ( + "net" + "net/netip" + + "errors" +) + +// The returned addresses are v4-unmapped and share the same family. +func tcpEndpoints(conn net.Conn) (*net.TCPConn, netip.AddrPort, netip.AddrPort, error) { + tcpConn, isTCP := conn.(*net.TCPConn) + if !isTCP { + return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("tls_spoof: underlying conn is not *net.TCPConn") + } + local := tcpConn.LocalAddr().(*net.TCPAddr).AddrPort() + remote := tcpConn.RemoteAddr().(*net.TCPAddr).AddrPort() + if !local.IsValid() || !remote.IsValid() { + return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("tls_spoof: invalid conn address") + } + local = netip.AddrPortFrom(local.Addr().Unmap(), local.Port()) + remote = netip.AddrPortFrom(remote.Addr().Unmap(), remote.Port()) + if local.Addr().Is4() != remote.Addr().Is4() { + return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("tls_spoof: local/remote address family mismatch") + } + return tcpConn, local, remote, nil +} diff --git a/transport/internet/tls/tlsspoof/packet.go b/transport/internet/tls/tlsspoof/packet.go new file mode 100644 index 000000000000..5c23c0631ab8 --- /dev/null +++ b/transport/internet/tls/tlsspoof/packet.go @@ -0,0 +1,163 @@ +package tlsspoof + +import ( + "encoding/binary" + "net/netip" + + "fmt" +) + +const ( + defaultTTL uint8 = 64 + defaultWindowSize uint16 = 0xFFFF + tcpHeaderLen = TCPMinimumSize + + tcpOptionMD5Signature = 19 + tcpOptionMD5SignatureLength = 18 + tcpTimestampBackdate = 3600000 +) + +type spoofPacketInfo struct { + seqNum uint32 + ackNum uint32 + corrupt bool + options []byte +} + +func buildTCPSegment( + src netip.AddrPort, + dst netip.AddrPort, + packetInfo spoofPacketInfo, + payload []byte, +) []byte { + if src.Addr().Is4() != dst.Addr().Is4() { + panic("tlsspoof: mixed IPv4/IPv6 address family") + } + var ( + frame []byte + ipHeaderLen int + ) + ipPayloadLen := tcpHeaderLen + len(packetInfo.options) + len(payload) + if src.Addr().Is4() { + ipHeaderLen = IPv4MinimumSize + frame = make([]byte, ipHeaderLen+ipPayloadLen) + ip := IPv4(frame[:ipHeaderLen]) + ip.Encode(uint16(len(frame)), 0, defaultTTL, TCPProtocolNumber, src.Addr(), dst.Addr()) + } else { + ipHeaderLen = IPv6MinimumSize + frame = make([]byte, ipHeaderLen+ipPayloadLen) + ip := IPv6(frame[:ipHeaderLen]) + ip.Encode(uint16(ipPayloadLen), TCPProtocolNumber, defaultTTL, src.Addr(), dst.Addr()) + } + encodeTCP(frame, ipHeaderLen, src, dst, packetInfo, payload) + return frame +} + +func encodeTCP(frame []byte, ipHeaderLen int, src, dst netip.AddrPort, packetInfo spoofPacketInfo, payload []byte) { + tcp := TCP(frame[ipHeaderLen:]) + copy(frame[ipHeaderLen+tcpHeaderLen:], packetInfo.options) + optionsLen := len(packetInfo.options) + copy(frame[ipHeaderLen+tcpHeaderLen+optionsLen:], payload) + tcp.Encode(src.Port(), dst.Port(), packetInfo.seqNum, packetInfo.ackNum, uint8(tcpHeaderLen+optionsLen), TCPFlagAck|TCPFlagPsh, defaultWindowSize) + applyTCPChecksum(tcp, src.Addr(), dst.Addr(), payload, packetInfo.corrupt) +} + +func buildSpoofFrame(method Method, src, dst netip.AddrPort, sendNext, receiveNext, timestamp uint32, tcpOptions, payload []byte) ([]byte, error) { + packetInfo, err := resolveSpoofPacketInfo(method, sendNext, receiveNext, timestamp, tcpOptions, payload) + if err != nil { + return nil, err + } + return buildTCPSegment(src, dst, packetInfo, payload), nil +} + +// buildSpoofTCPSegment returns a TCP segment without an IP header, for +// platforms where the kernel synthesises the IP header (darwin IPv6). +func buildSpoofTCPSegment(method Method, src, dst netip.AddrPort, sendNext, receiveNext, timestamp uint32, payload []byte) ([]byte, error) { + packetInfo, err := resolveSpoofPacketInfo(method, sendNext, receiveNext, timestamp, nil, payload) + if err != nil { + return nil, err + } + segment := make([]byte, tcpHeaderLen+len(packetInfo.options)+len(payload)) + encodeTCP(segment, 0, src, dst, packetInfo, payload) + return segment, nil +} + +func resolveSpoofPacketInfo(method Method, sendNext, receiveNext, timestamp uint32, tcpOptions, payload []byte) (spoofPacketInfo, error) { + packetInfo := spoofPacketInfo{seqNum: sendNext, ackNum: receiveNext} + switch method { + case MethodWrongSequence: + packetInfo.seqNum = sendNext - uint32(len(payload)) + case MethodWrongChecksum: + packetInfo.corrupt = true + case MethodWrongAcknowledgment: + packetInfo.ackNum = receiveNext - uint32(defaultWindowSize/2) + case MethodWrongMD5Sig: + packetInfo.options = buildMD5SignatureOptions() + case MethodWrongTimestamp: + packetInfo.options = buildWrongTimestampOptions(timestamp, tcpOptions) + default: + return packetInfo, fmt.Errorf("tls_spoof: unknown method %v", method) + } + return packetInfo, nil +} + +func buildMD5SignatureOptions() []byte { + options := make([]byte, tcpOptionMD5SignatureLength+2) + options[0] = tcpOptionMD5Signature + options[1] = tcpOptionMD5SignatureLength + return options +} + +func buildWrongTimestampOptions(timestamp uint32, tcpOptions []byte) []byte { + spoofedTimestamp := timestamp + if spoofedTimestamp > tcpTimestampBackdate { + spoofedTimestamp -= tcpTimestampBackdate + } else { + spoofedTimestamp = 0 + } + if rewriteTCPOptionTimestamp(tcpOptions, spoofedTimestamp) { + return tcpOptions + } + options := make([]byte, TCPOptionTSLength+2) + EncodeTSOption(spoofedTimestamp, 0, options) + return options +} + +// rewriteTCPOptionTimestamp finds the TS option in tcpOptions and writes +// timestamp into its TSVal field in place. The caller must own tcpOptions +// (parseTCPPacket already returns a private copy on Windows). +func rewriteTCPOptionTimestamp(tcpOptions []byte, timestamp uint32) bool { + for i := 0; i < len(tcpOptions); { + switch tcpOptions[i] { + case TCPOptionEOL: + return false + case TCPOptionNOP: + i++ + continue + } + if i+1 >= len(tcpOptions) { + return false + } + optionLen := int(tcpOptions[i+1]) + if optionLen < 2 || i+optionLen > len(tcpOptions) { + return false + } + if tcpOptions[i] == TCPOptionTS && optionLen == TCPOptionTSLength { + binary.BigEndian.PutUint32(tcpOptions[i+2:], timestamp) + return true + } + i += optionLen + } + return false +} + +func applyTCPChecksum(tcp TCP, srcAddr, dstAddr netip.Addr, payload []byte, corrupt bool) { + tcpLen := int(tcp.DataOffset()) + len(payload) + pseudo := PseudoHeaderChecksum(TCPProtocolNumber, srcAddr.AsSlice(), dstAddr.AsSlice(), uint16(tcpLen)) + payloadChecksum := Checksum(payload, 0) + tcpChecksum := ^tcp.CalculateChecksum(CombineChecksum(pseudo, payloadChecksum)) + if corrupt { + tcpChecksum ^= 0xFFFF + } + tcp.SetChecksum(tcpChecksum) +} diff --git a/transport/internet/tls/tlsspoof/spoof.go b/transport/internet/tls/tlsspoof/spoof.go new file mode 100644 index 000000000000..6a9eae93a45b --- /dev/null +++ b/transport/internet/tls/tlsspoof/spoof.go @@ -0,0 +1,182 @@ +package tlsspoof + +import ( + "errors" + "fmt" + "net" + "runtime" + "syscall" +) + +type Method int + +const ( + MethodWrongSequence Method = iota + MethodWrongChecksum + MethodWrongAcknowledgment + MethodWrongMD5Sig + MethodWrongTimestamp +) + +const ( + MethodNameWrongSequence = "wrong-sequence" + MethodNameWrongChecksum = "wrong-checksum" + MethodNameWrongAcknowledgment = "wrong-ack" + MethodNameWrongMD5Sig = "wrong-md5" + MethodNameWrongTimestamp = "wrong-timestamp" +) + +func ParseOptions(spoof, method string) (string, Method, error) { + if spoof == "" { + if method != "" { + return "", 0, errors.New("spoof_method requires spoof") + } + return "", 0, nil + } + if net.ParseIP(spoof) != nil { + return "", 0, errors.New("tls_spoof: IP-literal server names are not allowed") + } + if !PlatformSupported { + return "", 0, errors.New("tls_spoof is not supported on this platform") + } + parsedMethod, err := ParseMethod(method) + if err != nil { + return "", 0, err + } + return spoof, parsedMethod, nil +} + +func ParseMethod(s string) (Method, error) { + switch s { + case "", MethodNameWrongSequence: + return MethodWrongSequence, nil + case MethodNameWrongChecksum: + return MethodWrongChecksum, nil + case MethodNameWrongAcknowledgment: + return MethodWrongAcknowledgment, nil + case MethodNameWrongMD5Sig: + return MethodWrongMD5Sig, nil + case MethodNameWrongTimestamp: + return MethodWrongTimestamp, nil + default: + return 0, fmt.Errorf("tls_spoof: unknown method: %s", s) + } +} + +func (m Method) String() string { + switch m { + case MethodWrongSequence: + return MethodNameWrongSequence + case MethodWrongChecksum: + return MethodNameWrongChecksum + case MethodWrongAcknowledgment: + return MethodNameWrongAcknowledgment + case MethodWrongMD5Sig: + return MethodNameWrongMD5Sig + case MethodWrongTimestamp: + return MethodNameWrongTimestamp + default: + return "unknown" + } +} + +type rawSpoofer interface { + Inject(payload []byte) error + Close() error +} + +type Conn struct { + net.Conn + spoofer rawSpoofer + fakeHello []byte + injectionCount int + maxInjections int // how many times to inject; default 1 +} + +// NewConn wraps a connection with TLS spoofing. maxInjections controls how +// many Write() calls will trigger a fake ClientHello injection (0 or 1 = single-shot). +func NewConn(conn net.Conn, method Method, fakeSNI string, maxInjections int) (*Conn, error) { + spoofer, err := newRawSpoofer(conn, method) + if err != nil { + return nil, wrapPermissionError(err) + } + result, err := newConn(conn, spoofer, fakeSNI, maxInjections) + if err != nil { + spoofer.Close() + return nil, err + } + return result, nil +} + +func newConn(conn net.Conn, spoofer rawSpoofer, fakeSNI string, maxInjections int) (*Conn, error) { + fakeHello, err := buildFakeClientHello(fakeSNI) + if err != nil { + return nil, func(err error, m string) error { return err }(err, "tls_spoof: build fake ClientHello") + } + if maxInjections <= 0 { + maxInjections = 1 + } + return &Conn{ + Conn: conn, + spoofer: spoofer, + fakeHello: fakeHello, + maxInjections: maxInjections, + }, nil +} + +func (c *Conn) Write(b []byte) (n int, err error) { + if c.injectionCount >= c.maxInjections { + return c.Conn.Write(b) + } + err = c.spoofer.Inject(c.fakeHello) + if err != nil { + return 0, func(err error, m string) error { return err }(err, "tls_spoof: inject") + } + c.injectionCount++ + if c.injectionCount >= c.maxInjections { + closeErr := c.spoofer.Close() + if closeErr != nil { + return 0, func(err error, m string) error { return err }(closeErr, "tls_spoof: close spoofer") + } + } + return c.Conn.Write(b) +} + +func (c *Conn) Close() error { + return func(e1, e2 error) error { + if e1 != nil { + return e1 + } + return e2 + }(c.Conn.Close(), c.spoofer.Close()) +} + +func (c *Conn) ReaderReplaceable() bool { + return true +} + +func (c *Conn) WriterReplaceable() bool { + return c.injectionCount >= c.maxInjections +} + +func (c *Conn) Upstream() any { + return c.Conn +} + +// wrapPermissionError adds platform-specific hints when the spoofer fails +// due to insufficient privileges. +func wrapPermissionError(err error) error { + if !errors.Is(err, syscall.EPERM) && !errors.Is(err, syscall.EACCES) { + return err + } + switch runtime.GOOS { + case "linux": + return fmt.Errorf("%w\n Hint: run as root, or grant capabilities:\n sudo setcap cap_net_raw,cap_net_admin+ep /path/to/xray", err) + case "darwin": + return fmt.Errorf("%w\n Hint: TLS spoofing requires root on macOS. Run with: sudo ./xray", err) + case "freebsd": + return fmt.Errorf("%w\n Hint: TLS spoofing requires root on FreeBSD. Run with: sudo ./xray", err) + default: + return err + } +} diff --git a/transport/internet/tls/tlsspoof/spoof_test.go b/transport/internet/tls/tlsspoof/spoof_test.go new file mode 100644 index 000000000000..c51e4fddceb0 --- /dev/null +++ b/transport/internet/tls/tlsspoof/spoof_test.go @@ -0,0 +1,111 @@ +package tlsspoof + +import ( + "testing" +) + +func TestBuildFakeClientHello(t *testing.T) { + hello, err := buildFakeClientHello("www.example.com") + if err != nil { + t.Fatal("buildFakeClientHello returned error:", err) + } + if len(hello) == 0 { + t.Fatal("buildFakeClientHello returned empty payload") + } + // TLS record header: content type 0x16 (handshake) + if hello[0] != 0x16 { + t.Fatalf("expected TLS handshake record type 0x16, got 0x%02x", hello[0]) + } + // TLS version: 0x0301 (TLS 1.0 record layer) + if hello[1] != 0x03 || hello[2] != 0x01 { + t.Fatalf("unexpected TLS record version: 0x%02x%02x", hello[1], hello[2]) + } + t.Logf("ClientHello payload length: %d bytes", len(hello)) +} + +func TestBuildFakeClientHelloEmptySNI(t *testing.T) { + _, err := buildFakeClientHello("") + if err == nil { + t.Fatal("expected error for empty SNI") + } +} + +func TestParseMethod(t *testing.T) { + tests := []struct { + input string + expected Method + hasErr bool + }{ + {"", MethodWrongSequence, false}, + {"wrong-sequence", MethodWrongSequence, false}, + {"wrong-checksum", MethodWrongChecksum, false}, + {"wrong-ack", MethodWrongAcknowledgment, false}, + {"wrong-md5", MethodWrongMD5Sig, false}, + {"wrong-timestamp", MethodWrongTimestamp, false}, + {"invalid", 0, true}, + } + for _, tt := range tests { + m, err := ParseMethod(tt.input) + if tt.hasErr { + if err == nil { + t.Errorf("ParseMethod(%q): expected error, got nil", tt.input) + } + continue + } + if err != nil { + t.Errorf("ParseMethod(%q): unexpected error: %v", tt.input, err) + continue + } + if m != tt.expected { + t.Errorf("ParseMethod(%q) = %v, want %v", tt.input, m, tt.expected) + } + } +} + +func TestParseOptions(t *testing.T) { + // Empty spoof should be a no-op + sni, _, err := ParseOptions("", "") + if err != nil { + t.Fatal("ParseOptions(\"\", \"\"): unexpected error:", err) + } + if sni != "" { + t.Fatalf("expected empty SNI, got %q", sni) + } + + // spoof_method without spoof should error + _, _, err = ParseOptions("", "wrong-checksum") + if err == nil { + t.Fatal("expected error when spoof_method set without spoof") + } + + // Valid combo + sni, method, err := ParseOptions("fake.example.com", "wrong-checksum") + if err != nil { + t.Fatal("ParseOptions: unexpected error:", err) + } + if sni != "fake.example.com" { + t.Fatalf("expected SNI 'fake.example.com', got %q", sni) + } + if method != MethodWrongChecksum { + t.Fatalf("expected MethodWrongChecksum, got %v", method) + } + + // IP-literal should be rejected + _, _, err = ParseOptions("1.2.3.4", "wrong-checksum") + if err == nil { + t.Fatal("expected error for IP-literal spoof") + } + _, _, err = ParseOptions("::1", "wrong-checksum") + if err == nil { + t.Fatal("expected error for IP-literal spoof") + } +} + +func TestMethodString(t *testing.T) { + if MethodWrongSequence.String() != "wrong-sequence" { + t.Fatalf("unexpected method string: %s", MethodWrongSequence.String()) + } + if MethodWrongChecksum.String() != "wrong-checksum" { + t.Fatalf("unexpected method string: %s", MethodWrongChecksum.String()) + } +} diff --git a/transport/internet/tls/tlsspoof/tcpip.go b/transport/internet/tls/tlsspoof/tcpip.go new file mode 100644 index 000000000000..62657ccefd68 --- /dev/null +++ b/transport/internet/tls/tlsspoof/tcpip.go @@ -0,0 +1,155 @@ +package tlsspoof + +import ( + "encoding/binary" + "net/netip" +) + +const ( + IPv4MinimumSize = 20 + IPv6MinimumSize = 40 + TCPMinimumSize = 20 + TCPProtocolNumber = 6 + + TCPOptionEOL = 0 + TCPOptionNOP = 1 + TCPOptionTS = 8 + TCPOptionTSLength = 10 + + TCPFlagFin = 0x01 + TCPFlagSyn = 0x02 + TCPFlagRst = 0x04 + TCPFlagPsh = 0x08 + TCPFlagAck = 0x10 +) + +func Checksum(data []byte, initial uint16) uint16 { + var csum uint32 = uint32(initial) + for i := 0; i < len(data)-1; i += 2 { + csum += uint32(binary.BigEndian.Uint16(data[i:])) + } + if len(data)%2 == 1 { + csum += uint32(data[len(data)-1]) << 8 + } + for csum > 0xffff { + csum = (csum >> 16) + (csum & 0xffff) + } + return uint16(csum) +} + +func PseudoHeaderChecksum(protocol uint8, srcAddr, dstAddr []byte, totalLen uint16) uint16 { + var csum uint32 + for i := 0; i < len(srcAddr); i += 2 { + csum += uint32(binary.BigEndian.Uint16(srcAddr[i:])) + } + for i := 0; i < len(dstAddr); i += 2 { + csum += uint32(binary.BigEndian.Uint16(dstAddr[i:])) + } + csum += uint32(protocol) + csum += uint32(totalLen) + for csum > 0xffff { + csum = (csum >> 16) + (csum & 0xffff) + } + return uint16(csum) +} + +func CombineChecksum(c1, c2 uint16) uint16 { + csum := uint32(c1) + uint32(c2) + for csum > 0xffff { + csum = (csum >> 16) + (csum & 0xffff) + } + return uint16(csum) +} + +func EncodeTSOption(val uint32, ecr uint32, b []byte) { + b[0] = TCPOptionTS + b[1] = TCPOptionTSLength + binary.BigEndian.PutUint32(b[2:], val) + binary.BigEndian.PutUint32(b[6:], ecr) +} + +func ParseTCPOptions(b []byte) (tsVal uint32, hasTS bool) { + for i := 0; i < len(b); { + if b[i] == TCPOptionEOL { + break + } + if b[i] == TCPOptionNOP { + i++ + continue + } + if i+1 >= len(b) { + break + } + optLen := int(b[i+1]) + if optLen < 2 || i+optLen > len(b) { + break + } + if b[i] == TCPOptionTS && optLen == TCPOptionTSLength { + return binary.BigEndian.Uint32(b[i+2:]), true + } + i += optLen + } + return 0, false +} + +// IPv4 header representation +type IPv4 []byte + +func (b IPv4) TotalLength() uint16 { return binary.BigEndian.Uint16(b[2:]) } +func (b IPv4) Flags() uint8 { return uint8(binary.BigEndian.Uint16(b[6:]) >> 13) } +func (b IPv4) FragmentOffset() uint16 { return binary.BigEndian.Uint16(b[6:]) & 0x1fff } +func (b IPv4) Protocol() uint8 { return b[9] } +func (b IPv4) HeaderLength() uint8 { return (b[0] & 0x0f) * 4 } + +func (b IPv4) Encode(totalLength uint16, id uint16, ttl uint8, protocol uint8, src, dst netip.Addr) { + b[0] = (4 << 4) | 5 // IPv4, Header Length = 20 + b[1] = 0 // TOS + binary.BigEndian.PutUint16(b[2:], totalLength) + binary.BigEndian.PutUint16(b[4:], id) + binary.BigEndian.PutUint16(b[6:], 0) // Flags and Fragment Offset + b[8] = ttl + b[9] = protocol + b[10] = 0 // Checksum (0 for calculation) + copy(b[12:16], src.AsSlice()) + copy(b[16:20], dst.AsSlice()) + csum := Checksum(b[:20], 0) + binary.BigEndian.PutUint16(b[10:], ^csum) +} + +type IPv6 []byte + +func (b IPv6) PayloadLength() uint16 { return binary.BigEndian.Uint16(b[4:]) } +func (b IPv6) TransportProtocol() uint8 { return b[6] } + +func (b IPv6) Encode(payloadLength uint16, transportProtocol uint8, hopLimit uint8, src, dst netip.Addr) { + binary.BigEndian.PutUint32(b[0:], 6<<28) // Version 6, Traffic Class 0, Flow Label 0 + binary.BigEndian.PutUint16(b[4:], payloadLength) + b[6] = transportProtocol + b[7] = hopLimit + copy(b[8:24], src.AsSlice()) + copy(b[24:40], dst.AsSlice()) +} + +type TCP []byte + +func (b TCP) DataOffset() uint8 { return (b[12] >> 4) * 4 } +func (b TCP) SequenceNumber() uint32 { return binary.BigEndian.Uint32(b[4:]) } +func (b TCP) AckNumber() uint32 { return binary.BigEndian.Uint32(b[8:]) } +func (b TCP) Options() []byte { return b[TCPMinimumSize:b.DataOffset()] } +func (b TCP) SetChecksum(csum uint16) { binary.BigEndian.PutUint16(b[16:], csum) } + +func (b TCP) Encode(srcPort, dstPort uint16, seqNum, ackNum uint32, dataOffset uint8, flags uint8, windowSize uint16) { + binary.BigEndian.PutUint16(b[0:], srcPort) + binary.BigEndian.PutUint16(b[2:], dstPort) + binary.BigEndian.PutUint32(b[4:], seqNum) + binary.BigEndian.PutUint32(b[8:], ackNum) + b[12] = (dataOffset / 4) << 4 + b[13] = flags + binary.BigEndian.PutUint16(b[14:], windowSize) + b[16] = 0 // Checksum + binary.BigEndian.PutUint16(b[18:], 0) // Urgent pointer +} + +func (b TCP) CalculateChecksum(initial uint16) uint16 { + return Checksum(b, initial) +} From d1ec6d802805362f4709b0269fa1705749a436cf Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:30:00 +0600 Subject: [PATCH 17/42] TLSSpoof: Add raw socket spoofers for Linux, Darwin and FreeBSD --- transport/internet/tls/tlsspoof/raw_darwin.go | 198 ++++++++++++++++++ .../internet/tls/tlsspoof/raw_freebsd.go | 172 +++++++++++++++ transport/internet/tls/tlsspoof/raw_linux.go | 166 +++++++++++++++ transport/internet/tls/tlsspoof/raw_stub.go | 15 ++ transport/internet/tls/tlsspoof/raw_unix.go | 25 +++ .../tls/tlsspoof/spoof_freebsd_test.go | 82 ++++++++ 6 files changed, 658 insertions(+) create mode 100644 transport/internet/tls/tlsspoof/raw_darwin.go create mode 100644 transport/internet/tls/tlsspoof/raw_freebsd.go create mode 100644 transport/internet/tls/tlsspoof/raw_linux.go create mode 100644 transport/internet/tls/tlsspoof/raw_stub.go create mode 100644 transport/internet/tls/tlsspoof/raw_unix.go create mode 100644 transport/internet/tls/tlsspoof/spoof_freebsd_test.go diff --git a/transport/internet/tls/tlsspoof/raw_darwin.go b/transport/internet/tls/tlsspoof/raw_darwin.go new file mode 100644 index 000000000000..3b45d17023be --- /dev/null +++ b/transport/internet/tls/tlsspoof/raw_darwin.go @@ -0,0 +1,198 @@ +package tlsspoof + +import ( + "encoding/binary" + "net" + "net/netip" + "strconv" + "strings" + "sync" + "syscall" + + "errors" + "fmt" + + "golang.org/x/sys/unix" +) + +const PlatformSupported = true + +// Offsets into xinpcb_n within each net.inet.tcp.pcblist_n record, identical +// to the values used by common/process/searcher_darwin_shared.go. +const ( + darwinXinpgenSize = 24 + darwinXsocketOffset = 104 + darwinXinpcbForeignPort = 16 + darwinXinpcbLocalPort = 18 + darwinXinpcbVFlag = 44 + darwinXinpcbForeignAddr = 48 + darwinXinpcbLocalAddr = 64 + darwinXinpcbIPv4Offset = 12 + + darwinTCPExtraSize = 208 + + darwinXtcpcbSndNxtOffset = 56 + darwinXtcpcbRcvNxtOffset = 80 +) + +// darwinStructSize returns the size of xinpcb_n for the running Darwin kernel. +// Darwin 22 (macOS 13 Ventura) grew the struct from 384 to 408 bytes; there is +// no ABI-stable way to read it, so we key off the kernel version. +var darwinStructSize = sync.OnceValues(func() (int, error) { + value, err := syscall.Sysctl("kern.osrelease") + if err != nil { + return 0, func(err error, m string) error { return err }(err, "sysctl kern.osrelease") + } + major, _, ok := strings.Cut(value, ".") + if !ok { + return 0, fmt.Errorf("unexpected kern.osrelease format: %s", value) + } + n, err := strconv.ParseInt(major, 10, 64) + if err != nil { + return 0, func(err error, m string) error { return err }(err, "parse kern.osrelease major version: ") + } + if n >= 22 { + return 408, nil + } + return 384, nil +}) + +type darwinSpoofer struct { + method Method + src netip.AddrPort + dst netip.AddrPort + rawFD int + rawSockAddr unix.Sockaddr + sendNext uint32 + receiveNext uint32 +} + +func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { + if method == MethodWrongTimestamp { + return nil, errors.New("tls_spoof: wrong-timestamp is not supported on macOS") + } + _, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + fd, sockaddr, err := openDarwinRawSocket(src, dst) + if err != nil { + return nil, err + } + sendNext, receiveNext, err := readDarwinTCPSequence(src, dst) + if err != nil { + unix.Close(fd) + return nil, err + } + return &darwinSpoofer{ + method: method, + src: src, + dst: dst, + rawFD: fd, + rawSockAddr: sockaddr, + sendNext: sendNext, + receiveNext: receiveNext, + }, nil +} + +// readDarwinTCPSequence scans net.inet.tcp.pcblist_n for the PCB that matches +// src -> dst and returns (snd_nxt, rcv_nxt). These live in xtcpcb_n at the end +// of each record; see darwin-xnu bsd/netinet/in_pcblist.c:get_pcblist_n. +func readDarwinTCPSequence(src, dst netip.AddrPort) (uint32, uint32, error) { + buffer, err := unix.SysctlRaw("net.inet.tcp.pcblist_n") + if err != nil { + return 0, 0, func(err error, m string) error { return err }(err, "sysctl net.inet.tcp.pcblist_n") + } + structSize, err := darwinStructSize() + if err != nil { + return 0, 0, err + } + itemSize := structSize + darwinTCPExtraSize + for i := darwinXinpgenSize; i+itemSize <= len(buffer); i += itemSize { + inpcb := buffer[i : i+darwinXsocketOffset] + xtcpcb := buffer[i+structSize : i+itemSize] + localPort := binary.BigEndian.Uint16(inpcb[darwinXinpcbLocalPort : darwinXinpcbLocalPort+2]) + remotePort := binary.BigEndian.Uint16(inpcb[darwinXinpcbForeignPort : darwinXinpcbForeignPort+2]) + if localPort != src.Port() || remotePort != dst.Port() { + continue + } + versionFlag := inpcb[darwinXinpcbVFlag] + var localAddr, remoteAddr netip.Addr + switch { + case versionFlag&0x1 != 0: + localAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset : darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset+4])) + remoteAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset : darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset+4])) + case versionFlag&0x2 != 0: + localAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbLocalAddr : darwinXinpcbLocalAddr+16])) + remoteAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbForeignAddr : darwinXinpcbForeignAddr+16])) + default: + continue + } + if localAddr.Unmap() != src.Addr() || remoteAddr.Unmap() != dst.Addr() { + continue + } + sendNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbSndNxtOffset : darwinXtcpcbSndNxtOffset+4]) + receiveNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbRcvNxtOffset : darwinXtcpcbRcvNxtOffset+4]) + return sendNext, receiveNext, nil + } + return 0, 0, fmt.Errorf("tls_spoof: connection %v->%v not found in pcblist_n", src, dst) +} + +func openDarwinRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) { + if dst.Addr().Is4() { + return openIPv4RawSocket(dst) + } + // macOS does not accept IPV6_HDRINCL on AF_INET6 SOCK_RAW IPPROTO_TCP + // sockets, so the kernel builds the IPv6 header itself. Bind to the real + // connection's source address so in6_selectsrc returns it, and rely on + // in6p_cksum defaulting to -1 so the user-supplied TCP checksum is + // preserved (including deliberately corrupted ones). + fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, func(err error, m string) error { return err }(err, "open AF_INET6 SOCK_RAW") + } + err = unix.Bind(fd, &unix.SockaddrInet6{Addr: src.Addr().As16()}) + if err != nil { + unix.Close(fd) + return -1, nil, func(err error, m string) error { return err }(err, "bind AF_INET6 SOCK_RAW") + } + sockaddr := &unix.SockaddrInet6{Port: int(dst.Port()), Addr: dst.Addr().As16()} + return fd, sockaddr, nil +} + +func (s *darwinSpoofer) Inject(payload []byte) error { + if !s.src.Addr().Is4() { + segment, err := buildSpoofTCPSegment(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, payload) + if err != nil { + return err + } + err = unix.Sendto(s.rawFD, segment, 0, s.rawSockAddr) + if err != nil { + return func(err error, m string) error { return err }(err, "sendto raw socket") + } + return nil + } + frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, nil, payload) + if err != nil { + return err + } + // Darwin inherits the historical BSD quirk: with IP_HDRINCL the kernel + // expects ip_len and ip_off in host byte order, not network byte order. + ip := IPv4(frame) + binary.NativeEndian.PutUint16(ip[2:4], ip.TotalLength()) + binary.NativeEndian.PutUint16(ip[6:8], uint16(ip.Flags())<<13|ip.FragmentOffset()) + err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) + if err != nil { + return func(err error, m string) error { return err }(err, "sendto raw socket") + } + return nil +} + +func (s *darwinSpoofer) Close() error { + if s.rawFD < 0 { + return nil + } + err := unix.Close(s.rawFD) + s.rawFD = -1 + return err +} diff --git a/transport/internet/tls/tlsspoof/raw_freebsd.go b/transport/internet/tls/tlsspoof/raw_freebsd.go new file mode 100644 index 000000000000..c38a249bf721 --- /dev/null +++ b/transport/internet/tls/tlsspoof/raw_freebsd.go @@ -0,0 +1,172 @@ +package tlsspoof + +import ( + "encoding/binary" + "errors" + "fmt" + "net" + "net/netip" + "syscall" + "unsafe" + + "golang.org/x/sys/unix" +) + +const PlatformSupported = true + +// FreeBSD tcp_info offsets for snd_nxt and rcv_nxt. +// Derived from FreeBSD sys/netinet/tcp.h struct tcp_info layout. +// +// struct tcp_info { +// u8 state, __ca, __retrans, __probes, __backoff, opts, wscale = 8 bytes (with pad) +// u32 rto, __ato, snd_mss, rcv_mss = 16 bytes (offset 8) +// u32 __unacked, __sacked, __lost, __retrans, __fackets = 20 bytes (offset 24) +// u32 __last_data_sent, __last_ack_sent, last_data_recv, __last_ack_recv = 16 bytes (offset 44) +// u32 __pmtu, __rcv_ssthresh, rtt, rttvar, snd_ssthresh, snd_cwnd, __advmss, __reordering = 32 bytes (offset 60) +// u32 __rcv_rtt, rcv_space = 8 bytes (offset 92) +// u32 snd_wnd, snd_bwnd = 8 bytes (offset 100) +// u32 snd_nxt, rcv_nxt = 8 bytes (offset 108) +// ... remaining fields +// } +const ( + freebsdTCPInfoSndNxtOffset = 108 + freebsdTCPInfoRcvNxtOffset = 112 + freebsdTCPInfoMinSize = 116 // must read at least through rcv_nxt +) + +type freebsdSpoofer struct { + method Method + src netip.AddrPort + dst netip.AddrPort + rawFD int + rawSockAddr unix.Sockaddr + sendNext uint32 + receiveNext uint32 +} + +func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { + if method == MethodWrongTimestamp { + return nil, errors.New("tls_spoof: wrong-timestamp is not supported on FreeBSD") + } + tcpConn, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + fd, sockaddr, err := openFreeBSDRawSocket(src, dst) + if err != nil { + return nil, err + } + sendNext, receiveNext, err := readFreeBSDTCPSequence(tcpConn) + if err != nil { + unix.Close(fd) + return nil, err + } + return &freebsdSpoofer{ + method: method, + src: src, + dst: dst, + rawFD: fd, + rawSockAddr: sockaddr, + sendNext: sendNext, + receiveNext: receiveNext, + }, nil +} + +// readFreeBSDTCPSequence retrieves snd_nxt and rcv_nxt via TCP_INFO getsockopt. +func readFreeBSDTCPSequence(conn *net.TCPConn) (uint32, uint32, error) { + raw, err := conn.SyscallConn() + if err != nil { + return 0, 0, fmt.Errorf("tls_spoof: SyscallConn: %w", err) + } + var sendNext, receiveNext uint32 + var sockErr error + err = raw.Control(func(fd uintptr) { + buf := make([]byte, 256) // generous buffer for tcp_info + bufLen := uint32(len(buf)) + _, _, errno := syscall.Syscall6( + syscall.SYS_GETSOCKOPT, + fd, + uintptr(syscall.IPPROTO_TCP), + uintptr(0x20), // TCP_INFO = 0x20 + uintptr(unsafe.Pointer(&buf[0])), + uintptr(unsafe.Pointer(&bufLen)), + 0, + ) + if errno != 0 { + sockErr = fmt.Errorf("tls_spoof: getsockopt TCP_INFO: %w", errno) + return + } + if bufLen < freebsdTCPInfoMinSize { + sockErr = fmt.Errorf("tls_spoof: TCP_INFO too short: %d < %d", bufLen, freebsdTCPInfoMinSize) + return + } + sendNext = binary.NativeEndian.Uint32(buf[freebsdTCPInfoSndNxtOffset : freebsdTCPInfoSndNxtOffset+4]) + receiveNext = binary.NativeEndian.Uint32(buf[freebsdTCPInfoRcvNxtOffset : freebsdTCPInfoRcvNxtOffset+4]) + }) + if err != nil { + return 0, 0, err + } + if sockErr != nil { + return 0, 0, sockErr + } + return sendNext, receiveNext, nil +} + +func openFreeBSDRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) { + if dst.Addr().Is4() { + return openIPv4RawSocket(dst) + } + // FreeBSD, like macOS, does not support IPV6_HDRINCL on SOCK_RAW/IPPROTO_TCP. + // The kernel constructs the IPv6 header. Bind to the source address + // and let the kernel fill in the IPv6 header automatically. + fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, fmt.Errorf("tls_spoof: open AF_INET6 SOCK_RAW: %w", err) + } + err = unix.Bind(fd, &unix.SockaddrInet6{Addr: src.Addr().As16()}) + if err != nil { + unix.Close(fd) + return -1, nil, fmt.Errorf("tls_spoof: bind AF_INET6 SOCK_RAW: %w", err) + } + sockaddr := &unix.SockaddrInet6{Port: int(dst.Port()), Addr: dst.Addr().As16()} + return fd, sockaddr, nil +} + +func (s *freebsdSpoofer) Inject(payload []byte) error { + if !s.src.Addr().Is4() { + // IPv6: kernel builds the IP header, we supply TCP segment only. + segment, err := buildSpoofTCPSegment(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, payload) + if err != nil { + return err + } + err = unix.Sendto(s.rawFD, segment, 0, s.rawSockAddr) + if err != nil { + return fmt.Errorf("tls_spoof: sendto raw socket: %w", err) + } + return nil + } + // IPv4: we build the full IP+TCP frame with IP_HDRINCL. + frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, nil, payload) + if err != nil { + return err + } + // FreeBSD inherits the historical BSD quirk: with IP_HDRINCL the kernel + // expects ip_len and ip_off in host byte order, not network byte order. + ip := IPv4(frame) + binary.NativeEndian.PutUint16(ip[2:4], ip.TotalLength()) + binary.NativeEndian.PutUint16(ip[6:8], uint16(ip.Flags())<<13|ip.FragmentOffset()) + err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) + if err != nil { + return fmt.Errorf("tls_spoof: sendto raw socket: %w", err) + } + return nil +} + +func (s *freebsdSpoofer) Close() error { + if s.rawFD < 0 { + return nil + } + err := unix.Close(s.rawFD) + s.rawFD = -1 + return err +} diff --git a/transport/internet/tls/tlsspoof/raw_linux.go b/transport/internet/tls/tlsspoof/raw_linux.go new file mode 100644 index 000000000000..dc5c7311869c --- /dev/null +++ b/transport/internet/tls/tlsspoof/raw_linux.go @@ -0,0 +1,166 @@ +package tlsspoof + +import ( + "fmt" + "net" + "net/netip" + + "golang.org/x/sys/unix" +) + +const PlatformSupported = true + +const ( + // Values of enum { TCP_NO_QUEUE, TCP_RECV_QUEUE, TCP_SEND_QUEUE } from + // include/net/tcp.h; not exported by golang.org/x/sys/unix. + tcpRecvQueue = 1 + tcpSendQueue = 2 +) + +type linuxSpoofer struct { + method Method + src netip.AddrPort + dst netip.AddrPort + rawFD int + rawSockAddr unix.Sockaddr + sendNext uint32 + receiveNext uint32 + timestamp uint32 +} + +func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { + tcpConn, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + fd, sockaddr, err := openLinuxRawSocket(dst) + if err != nil { + return nil, err + } + spoofer := &linuxSpoofer{ + method: method, + src: src, + dst: dst, + rawFD: fd, + rawSockAddr: sockaddr, + } + err = spoofer.loadSequenceNumbers(tcpConn) + if err != nil { + unix.Close(fd) + return nil, err + } + return spoofer, nil +} + +func openLinuxRawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) { + if dst.Addr().Is4() { + return openIPv4RawSocket(dst) + } + fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, func(err error, m string) error { return err }(err, "open AF_INET6 SOCK_RAW") + } + err = unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_HDRINCL, 1) + if err != nil { + unix.Close(fd) + return -1, nil, func(err error, m string) error { return err }(err, "set IPV6_HDRINCL") + } + // Linux raw IPv6 sockets interpret sin6_port as a nexthdr protocol number + // (see raw(7)); any value other than 0 or the socket's IPPROTO_TCP causes + // sendto to fail with EINVAL. The destination is already encoded in the + // user-supplied IPv6 header under IPV6_HDRINCL. + sockaddr := &unix.SockaddrInet6{Addr: dst.Addr().As16()} + return fd, sockaddr, nil +} + +// loadSequenceNumbers puts the socket briefly into TCP_REPAIR mode to read +// snd_nxt and rcv_nxt from the kernel. TCP_REPAIR requires CAP_NET_ADMIN; +// callers must run as root or grant both CAP_NET_RAW and CAP_NET_ADMIN. +// +// If the TCP_REPAIR_OFF revert fails, the socket would stay in TCP_REPAIR +// state and subsequent Write() calls would silently buffer instead of sending. +// Surface that error so callers can abort. +func (s *linuxSpoofer) loadSequenceNumbers(tcpConn *net.TCPConn) error { + rawConn, err := tcpConn.SyscallConn() + if err != nil { + return err + } + var ctrlErr error + err = rawConn.Control(func(raw uintptr) { + fd := int(raw) + + if s.method == MethodWrongTimestamp { + timestamp, tsErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_TIMESTAMP) + if tsErr != nil { + ctrlErr = fmt.Errorf("tls_spoof: read timestamp: %w", tsErr) + return + } + s.timestamp = uint32(timestamp) + } + + ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR, unix.TCP_REPAIR_ON) + if ctrlErr != nil { + ctrlErr = fmt.Errorf("tls_spoof: enter TCP_REPAIR (need CAP_NET_ADMIN): %w", ctrlErr) + return + } + defer func() { + offErr := unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR, unix.TCP_REPAIR_OFF) + if offErr != nil { + offErr = fmt.Errorf("tls_spoof: leave TCP_REPAIR: %w", offErr) + if ctrlErr == nil { + ctrlErr = offErr + } else { + ctrlErr = fmt.Errorf("%v; also %w", ctrlErr, offErr) + } + } + }() + + ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR_QUEUE, tcpSendQueue) + if ctrlErr != nil { + ctrlErr = fmt.Errorf("tls_spoof: select TCP_SEND_QUEUE: %w", ctrlErr) + return + } + sendSequence, seqErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_QUEUE_SEQ) + if seqErr != nil { + ctrlErr = fmt.Errorf("tls_spoof: read send queue sequence: %w", seqErr) + return + } + ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR_QUEUE, tcpRecvQueue) + if ctrlErr != nil { + ctrlErr = fmt.Errorf("tls_spoof: select TCP_RECV_QUEUE: %w", ctrlErr) + return + } + receiveSequence, seqErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_QUEUE_SEQ) + if seqErr != nil { + ctrlErr = fmt.Errorf("tls_spoof: read recv queue sequence: %w", seqErr) + return + } + s.sendNext = uint32(sendSequence) + s.receiveNext = uint32(receiveSequence) + }) + if err != nil { + return err + } + return ctrlErr +} + +func (s *linuxSpoofer) Inject(payload []byte) error { + frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, s.timestamp, nil, payload) + if err != nil { + return err + } + err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) + if err != nil { + return func(err error, m string) error { return err }(err, "sendto raw socket") + } + return nil +} + +func (s *linuxSpoofer) Close() error { + if s.rawFD < 0 { + return nil + } + err := unix.Close(s.rawFD) + s.rawFD = -1 + return err +} diff --git a/transport/internet/tls/tlsspoof/raw_stub.go b/transport/internet/tls/tlsspoof/raw_stub.go new file mode 100644 index 000000000000..78be3c23391d --- /dev/null +++ b/transport/internet/tls/tlsspoof/raw_stub.go @@ -0,0 +1,15 @@ +//go:build !linux && !darwin && !freebsd && !(windows && (amd64 || 386)) + +package tlsspoof + +import ( + "net" + + "errors" +) + +const PlatformSupported = false + +func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { + return nil, errors.New("tls_spoof: unsupported platform") +} diff --git a/transport/internet/tls/tlsspoof/raw_unix.go b/transport/internet/tls/tlsspoof/raw_unix.go new file mode 100644 index 000000000000..ae6c8b9f8b04 --- /dev/null +++ b/transport/internet/tls/tlsspoof/raw_unix.go @@ -0,0 +1,25 @@ +//go:build linux || darwin || freebsd + +package tlsspoof + +import ( + "net/netip" + + + "golang.org/x/sys/unix" +) + +func openIPv4RawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) { + fd, err := unix.Socket(unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, func(err error, m string) error { return err }(err, "open AF_INET SOCK_RAW") + } + err = unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_HDRINCL, 1) + if err != nil { + unix.Close(fd) + return -1, nil, func(err error, m string) error { return err }(err, "set IP_HDRINCL") + } + sockaddr := &unix.SockaddrInet4{Port: int(dst.Port())} + sockaddr.Addr = dst.Addr().As4() + return fd, sockaddr, nil +} diff --git a/transport/internet/tls/tlsspoof/spoof_freebsd_test.go b/transport/internet/tls/tlsspoof/spoof_freebsd_test.go new file mode 100644 index 000000000000..a8ab2ccae823 --- /dev/null +++ b/transport/internet/tls/tlsspoof/spoof_freebsd_test.go @@ -0,0 +1,82 @@ +package tlsspoof + +import ( + "net" + "net/netip" + "os/user" + "testing" + + "golang.org/x/sys/unix" +) + +func TestFreeBSDTCPSequence(t *testing.T) { + u, err := user.Current() + if err == nil && u.Uid != "0" { + t.Skip("skipping test; must be root to use raw sockets / TCP_INFO on FreeBSD") + } + + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen failed: %v", err) + } + defer ln.Close() + + serverDone := make(chan struct{}) + go func() { + conn, err := ln.Accept() + if err == nil { + conn.Write([]byte("hello")) + conn.Close() + } + close(serverDone) + }() + + client, err := net.Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dial failed: %v", err) + } + defer client.Close() + + tcpConn, ok := client.(*net.TCPConn) + if !ok { + t.Fatalf("expected *net.TCPConn, got %T", client) + } + + sndNxt, rcvNxt, err := readFreeBSDTCPSequence(tcpConn) + if err != nil { + t.Fatalf("readFreeBSDTCPSequence failed: %v", err) + } + + if sndNxt == 0 && rcvNxt == 0 { + t.Errorf("expected non-zero sequence numbers, got sndNxt=%d rcvNxt=%d", sndNxt, rcvNxt) + } + t.Logf("FreeBSD TCP sequence retrieved: snd_nxt=%d, rcv_nxt=%d", sndNxt, rcvNxt) + <-serverDone +} + +func TestFreeBSDRawSocket(t *testing.T) { + u, err := user.Current() + if err == nil && u.Uid != "0" { + t.Skip("skipping test; must be root to open raw sockets") + } + + dst := netip.MustParseAddrPort("8.8.8.8:443") + src := netip.MustParseAddrPort("127.0.0.1:12345") + + fd, sockaddr, err := openFreeBSDRawSocket(src, dst) + if err != nil { + t.Fatalf("openFreeBSDRawSocket failed: %v", err) + } + defer func() { + if fd >= 0 { + unix.Close(fd) + } + }() + + if fd < 0 { + t.Errorf("expected valid fd, got %d", fd) + } + if sockaddr == nil { + t.Error("expected valid sockaddr, got nil") + } +} From 21bc036b06b0e85b50c691923c3b296cd5328a81 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Fri, 1 May 2026 11:00:00 +0600 Subject: [PATCH 18/42] TLSSpoof: Add Windows WinDivert spoofer --- .../internet/tls/tlsspoof/raw_windows.go | 234 ++++ .../tls/tlsspoof/windivert/assets/LICENSE.txt | 1191 +++++++++++++++++ .../tlsspoof/windivert/assets/WinDivert32.sys | Bin 0 -> 79792 bytes .../tlsspoof/windivert/assets/WinDivert64.sys | Bin 0 -> 94144 bytes .../tls/tlsspoof/windivert/assets_386.go | 14 + .../tls/tlsspoof/windivert/assets_amd64.go | 14 + .../tlsspoof/windivert/assets_unsupported.go | 7 + .../tls/tlsspoof/windivert/driver_windows.go | 211 +++ .../internet/tls/tlsspoof/windivert/filter.go | 181 +++ .../tls/tlsspoof/windivert/handle_windows.go | 323 +++++ .../tls/tlsspoof/windivert/windivert.go | 78 ++ 11 files changed, 2253 insertions(+) create mode 100644 transport/internet/tls/tlsspoof/raw_windows.go create mode 100644 transport/internet/tls/tlsspoof/windivert/assets/LICENSE.txt create mode 100644 transport/internet/tls/tlsspoof/windivert/assets/WinDivert32.sys create mode 100644 transport/internet/tls/tlsspoof/windivert/assets/WinDivert64.sys create mode 100644 transport/internet/tls/tlsspoof/windivert/assets_386.go create mode 100644 transport/internet/tls/tlsspoof/windivert/assets_amd64.go create mode 100644 transport/internet/tls/tlsspoof/windivert/assets_unsupported.go create mode 100644 transport/internet/tls/tlsspoof/windivert/driver_windows.go create mode 100644 transport/internet/tls/tlsspoof/windivert/filter.go create mode 100644 transport/internet/tls/tlsspoof/windivert/handle_windows.go create mode 100644 transport/internet/tls/tlsspoof/windivert/windivert.go diff --git a/transport/internet/tls/tlsspoof/raw_windows.go b/transport/internet/tls/tlsspoof/raw_windows.go new file mode 100644 index 000000000000..17878ffce3dd --- /dev/null +++ b/transport/internet/tls/tlsspoof/raw_windows.go @@ -0,0 +1,234 @@ +//go:build windows && (amd64 || 386) + +package tlsspoof + +import ( + "errors" + "net" + "net/netip" + "slices" + "sync" + "sync/atomic" + "time" + + "github.com/xtls/xray-core/transport/internet/tls/tlsspoof/windivert" + "golang.org/x/sys/windows" +) + +const PlatformSupported = true + +// closeGracePeriod caps how long Close() waits for the divert goroutine to +// observe the kernel-emitted real ClientHello and perform the reorder +// (fake → real). In practice this completes in microseconds; the cap +// bounds the pathological case where the kernel buffers the packet. +const closeGracePeriod = 2 * time.Second + +// windowsSpoofer uses a single WinDivert handle for both capture and +// injection. Sequential Send() calls on one handle traverse one driver queue, +// so the fake provably precedes the released real on the wire — a guarantee +// two separate handles cannot make because cross-handle order depends on the +// scheduler. +type windowsSpoofer struct { + method Method + src, dst netip.AddrPort + divertH *windivert.Handle + + fakeReady chan []byte // buffered(1): staged by Inject + done chan struct{} // closed by run() on exit + closeOnce sync.Once + runErr atomic.Pointer[error] +} + +func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { + _, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + filter, err := windivert.OutboundTCP(src, dst) + if err != nil { + return nil, err + } + divertH, err := windivert.Open(filter, windivert.LayerNetwork, 0, 0) + if err != nil { + return nil, err + } + s := &windowsSpoofer{ + method: method, + src: src, + dst: dst, + divertH: divertH, + fakeReady: make(chan []byte, 1), + done: make(chan struct{}), + } + go s.run() + return s, nil +} + +func (s *windowsSpoofer) Inject(payload []byte) error { + select { + case s.fakeReady <- payload: + return nil + case <-s.done: + if p := s.runErr.Load(); p != nil { + return *p + } + return errors.New("tls_spoof: spoofer closed before Inject") + } +} + +func (s *windowsSpoofer) Close() error { + s.closeOnce.Do(func() { + // Give run() a grace window to finish handling the real packet. + select { + case <-s.done: + case <-time.After(closeGracePeriod): + // Force Recv() to return by closing the divert handle. + s.divertH.Close() + <-s.done + } + }) + if p := s.runErr.Load(); p != nil { + return *p + } + return nil +} + +func (s *windowsSpoofer) recordErr(err error) { s.runErr.Store(&err) } + +func (s *windowsSpoofer) run() { + defer close(s.done) + defer s.divertH.Close() + + buf := make([]byte, windivert.MTUMax) + for { + n, addr, err := s.divertH.Recv(buf) + if err != nil { + if errors.Is(err, windows.ERROR_OPERATION_ABORTED) || + errors.Is(err, windows.ERROR_NO_DATA) { + return + } + s.recordErr(err) + return + } + pkt := buf[:n] + seq, ack, tcpOptions, payloadLen, ok := parseTCPPacket(pkt, addr.IPv6()) + if !ok { + // Our filter is OutboundTCP(src, dst); a non-TCP or truncated + // match means driver state is suspect. Re-inject so the kernel + // still sees the byte stream, then abort — continuing would risk + // reordering against an unknown reference point. + _, sendErr := s.divertH.Send(pkt, &addr) + if sendErr != nil { + s.recordErr(sendErr) + return + } + s.recordErr(errors.New("windivert received malformed packet matching spoof filter")) + return + } + if payloadLen == 0 { + // Handshake ACK, keepalive, FIN — pass through unchanged. + _, err := s.divertH.Send(pkt, &addr) + if err != nil { + s.recordErr(err) + return + } + continue + } + + // Non-empty outbound TCP payload = the real ClientHello. + var fake []byte + select { + case fake = <-s.fakeReady: + default: + // Inject() not yet called — pass through and keep observing. + _, err := s.divertH.Send(pkt, &addr) + if err != nil { + s.recordErr(err) + return + } + continue + } + + var timestamp uint32 + if tsVal, hasTS := ParseTCPOptions(tcpOptions); hasTS { + timestamp = tsVal + } + frame, err := buildSpoofFrame(s.method, s.src, s.dst, seq, ack, timestamp, tcpOptions, fake) + if err != nil { + s.recordErr(err) + return + } + fakeAddr := addr // inherit Outbound, IfIdx + // buildSpoofFrame emits ready-to-wire bytes. The driver recomputes + // checksums on Send when TCPChecksum/IPChecksum are 0 — which would + // overwrite the intentionally corrupt checksum in WrongChecksum mode. + // Force both to 1 to keep our bytes intact. + fakeAddr.SetIPChecksum(true) + fakeAddr.SetTCPChecksum(true) + _, err = s.divertH.Send(frame, &fakeAddr) + if err != nil { + s.recordErr(err) + return + } + _, err = s.divertH.Send(pkt, &addr) + if err != nil { + s.recordErr(err) + return + } + return // single-shot reorder complete + } +} + +func parseTCPPacket(pkt []byte, isV6 bool) (seq, ack uint32, options []byte, payloadLen int, ok bool) { + if isV6 { + if len(pkt) < IPv6MinimumSize+TCPMinimumSize { + return 0, 0, nil, 0, false + } + ip := IPv6(pkt) + if ip.TransportProtocol() != TCPProtocolNumber { + return 0, 0, nil, 0, false + } + tcp := TCP(pkt[IPv6MinimumSize:]) + tcpHdr := int(tcp.DataOffset()) + if tcpHdr < TCPMinimumSize || IPv6MinimumSize+tcpHdr > len(pkt) { + return 0, 0, nil, 0, false + } + total := IPv6MinimumSize + int(ip.PayloadLength()) + if total == IPv6MinimumSize || total > len(pkt) { + total = len(pkt) + } + if total < IPv6MinimumSize+tcpHdr { + return 0, 0, nil, 0, false + } + return tcp.SequenceNumber(), tcp.AckNumber(), slices.Clone(tcp.Options()), + total - IPv6MinimumSize - tcpHdr, true + } + if len(pkt) < IPv4MinimumSize+TCPMinimumSize { + return 0, 0, nil, 0, false + } + ip := IPv4(pkt) + if ip.Protocol() != TCPProtocolNumber { + return 0, 0, nil, 0, false + } + ihl := int(ip.HeaderLength()) + // ihl+TCPMinimumSize guards the TCP-header field reads below; without + // this, an IPv4 packet with options (ihl>20) against a 40-byte buffer + // reads past the TCP slice when calling DataOffset. + if ihl < IPv4MinimumSize || ihl+TCPMinimumSize > len(pkt) { + return 0, 0, nil, 0, false + } + tcp := TCP(pkt[ihl:]) + tcpHdr := int(tcp.DataOffset()) + if tcpHdr < TCPMinimumSize || ihl+tcpHdr > len(pkt) { + return 0, 0, nil, 0, false + } + total := int(ip.TotalLength()) + if total == 0 || total > len(pkt) { + total = len(pkt) + } + if total < ihl+tcpHdr { + return 0, 0, nil, 0, false + } + return tcp.SequenceNumber(), tcp.AckNumber(), slices.Clone(tcp.Options()), + total - ihl - tcpHdr, true +} diff --git a/transport/internet/tls/tlsspoof/windivert/assets/LICENSE.txt b/transport/internet/tls/tlsspoof/windivert/assets/LICENSE.txt new file mode 100644 index 000000000000..8489a8e773c3 --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/assets/LICENSE.txt @@ -0,0 +1,1191 @@ +WinDivert is dual-licensed under your choice of the GNU Lesser General Public +License (LGPL) Version 3 or the GNU General Public License (GPL) Version 2. +Copies of the LGPLv3, GPLv3 and GPLv2 are provided below. + +============================================================================== + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + +============================================================================== + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + +============================================================================== + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + diff --git a/transport/internet/tls/tlsspoof/windivert/assets/WinDivert32.sys b/transport/internet/tls/tlsspoof/windivert/assets/WinDivert32.sys new file mode 100644 index 0000000000000000000000000000000000000000..d06738cbb78351cc57754fd484b77fac0df52cea GIT binary patch literal 79792 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%0DB0KXuLbP(03=gskd! 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�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%0A~=3F;g8l!;0U`QAv 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 E0GFf)F8}}l literal 0 HcmV?d00001 diff --git a/transport/internet/tls/tlsspoof/windivert/assets/WinDivert64.sys b/transport/internet/tls/tlsspoof/windivert/assets/WinDivert64.sys new file mode 100644 index 0000000000000000000000000000000000000000..218ccaf423ef0a67696226f9ef3a09149e4441d0 GIT binary patch literal 94144 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 Mok_m={2+h)7u^}>0RR91 literal 0 HcmV?d00001 diff --git a/transport/internet/tls/tlsspoof/windivert/assets_386.go b/transport/internet/tls/tlsspoof/windivert/assets_386.go new file mode 100644 index 000000000000..0cbf35ed5cbf --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/assets_386.go @@ -0,0 +1,14 @@ +//go:build windows && 386 + +package windivert + +import _ "embed" + +//go:embed assets/WinDivert32.sys +var sysBytes []byte + +func assetFiles() []assetFile { + return []assetFile{{"WinDivert32.sys", sysBytes}} +} + +func driverSysName() string { return "WinDivert32.sys" } diff --git a/transport/internet/tls/tlsspoof/windivert/assets_amd64.go b/transport/internet/tls/tlsspoof/windivert/assets_amd64.go new file mode 100644 index 000000000000..2c9fb6c6ad19 --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/assets_amd64.go @@ -0,0 +1,14 @@ +//go:build windows && amd64 + +package windivert + +import _ "embed" + +//go:embed assets/WinDivert64.sys +var sysBytes []byte + +func assetFiles() []assetFile { + return []assetFile{{"WinDivert64.sys", sysBytes}} +} + +func driverSysName() string { return "WinDivert64.sys" } diff --git a/transport/internet/tls/tlsspoof/windivert/assets_unsupported.go b/transport/internet/tls/tlsspoof/windivert/assets_unsupported.go new file mode 100644 index 000000000000..04698953fa6b --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/assets_unsupported.go @@ -0,0 +1,7 @@ +//go:build windows && !amd64 && !386 + +package windivert + +func assetFiles() []assetFile { return nil } + +func driverSysName() string { return "" } diff --git a/transport/internet/tls/tlsspoof/windivert/driver_windows.go b/transport/internet/tls/tlsspoof/windivert/driver_windows.go new file mode 100644 index 000000000000..50e94c578422 --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/driver_windows.go @@ -0,0 +1,211 @@ +//go:build windows + +package windivert + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "strconv" + "sync" + + "golang.org/x/sys/windows" +) + +const ( + driverServiceName = "WinDivert" + driverDeviceName = `\\.\WinDivert` +) + +var ( + driverOnce sync.Once + driverErr error + // driverDevName is ASCII-safe and must be available before ensureDriver + // so Open can try CreateFile first and only install on FILE_NOT_FOUND. + driverDevName, _ = windows.UTF16PtrFromString(driverDeviceName) +) + +// Requires SeLoadDriverPrivilege (Administrator). Running the 386 build +// under WOW64 on a 64-bit kernel is rejected — use the amd64 build. +func ensureDriver() error { + driverOnce.Do(func() { + driverErr = installDriver() + }) + return driverErr +} + +func installDriver() error { + if runtime.GOARCH == "386" { + var isWow64 bool + err := windows.IsWow64Process(windows.CurrentProcess(), &isWow64) + if err == nil && isWow64 { + return errors.New("windivert: 386 build detected running under WOW64 on a 64-bit kernel; use the amd64 build") + } + } + + dir, err := ensureExtracted() + if err != nil { + return err + } + sysPath := filepath.Join(dir, driverSysName()) + sysPathW, err := windows.UTF16PtrFromString(sysPath) + if err != nil { + return fmt.Errorf("windivert: utf16 driver path: %w", err) + } + + // Serialize driver install across concurrent processes. + mutexName, _ := windows.UTF16PtrFromString("WinDivertDriverInstallMutex") + mutex, err := windows.CreateMutex(nil, false, mutexName) + if err != nil { + return fmt.Errorf("windivert: create install mutex: %w", err) + } + defer windows.CloseHandle(mutex) + _, err = windows.WaitForSingleObject(mutex, windows.INFINITE) + if err != nil { + return fmt.Errorf("windivert: wait install mutex: %w", err) + } + defer windows.ReleaseMutex(mutex) + + manager, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_ALL_ACCESS) + if err != nil { + return fmt.Errorf("windivert: open SCM: %w", err) + } + defer windows.CloseServiceHandle(manager) + + serviceNameW, _ := windows.UTF16PtrFromString(driverServiceName) + service, err := windows.OpenService(manager, serviceNameW, windows.SERVICE_ALL_ACCESS) + if err != nil { + service, err = windows.CreateService( + manager, + serviceNameW, + serviceNameW, + windows.SERVICE_ALL_ACCESS, + windows.SERVICE_KERNEL_DRIVER, + windows.SERVICE_DEMAND_START, + windows.SERVICE_ERROR_NORMAL, + sysPathW, + nil, nil, nil, nil, nil, + ) + if err != nil { + if errors.Is(err, windows.ERROR_SERVICE_EXISTS) { + service, err = windows.OpenService(manager, serviceNameW, windows.SERVICE_ALL_ACCESS) + } + if err != nil { + return wrapDriverInstallError(err) + } + } + } + defer windows.CloseServiceHandle(service) + + err = windows.StartService(service, 0, nil) + if err != nil && errors.Is(err, windows.ERROR_SERVICE_DISABLED) { + // A prior process called DeleteService on a still-running kernel + // driver: SCM marks the record for deletion and flips START_TYPE + // to DISABLED until the last handle closes. Re-enable so we can + // start it instead of waiting for a reboot. + err = windows.ChangeServiceConfig( + service, + windows.SERVICE_NO_CHANGE, + windows.SERVICE_DEMAND_START, + windows.SERVICE_NO_CHANGE, + nil, nil, nil, nil, nil, nil, nil, + ) + if err != nil { + return fmt.Errorf("windivert: re-enable disabled service: %w", err) + } + err = windows.StartService(service, 0, nil) + } + if err == nil { + // Mark for deletion so the driver unregisters when the last handle + // closes or on next reboot. Matches the upstream DLL's behavior: + // only the process that actually started the service takes on the + // cleanup responsibility. If another process already started it, + // we leave DeleteService to them. + _ = windows.DeleteService(service) + } else if !errors.Is(err, windows.ERROR_SERVICE_ALREADY_RUNNING) { + return fmt.Errorf("windivert: start service: %w", err) + } + return nil +} + +func wrapDriverInstallError(err error) error { + if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + return fmt.Errorf("windivert: installing the kernel driver requires Administrator privileges: %w", err) + } + return fmt.Errorf("windivert: create service: %w", err) +} + +type assetFile struct { + name string + data []byte +} + +var ( + extractOnce sync.Once + extractErr error + extractDir string +) + +// The on-disk copy is protected by Windows Authenticode signature +// enforcement, which rejects any tampered .sys at StartService time. +func ensureExtracted() (string, error) { + extractOnce.Do(func() { + extractDir, extractErr = extractImpl() + }) + return extractDir, extractErr +} + +func extractImpl() (string, error) { + files := assetFiles() + if len(files) == 0 { + return "", fmt.Errorf("windivert: unsupported architecture %s", runtime.GOARCH) + } + + base, err := os.UserCacheDir() + if err != nil { + return "", fmt.Errorf("windivert: locate user cache dir: %w", err) + } + dir := filepath.Join(base, "xray-core", "windivert", "v"+AssetVersion) + err = os.MkdirAll(dir, 0o755) + if err != nil { + return "", fmt.Errorf("windivert: mkdir %s: %w", dir, err) + } + + for _, asset := range files { + err = ensureAsset(dir, asset) + if err != nil { + return "", err + } + } + return dir, nil +} + +// Concurrent sing-box processes race on os.Rename (atomic on NTFS); +// whichever wins creates the final file. Writers that lose the race +// silently discard their temp copy. +func ensureAsset(dir string, asset assetFile) error { + target := filepath.Join(dir, asset.name) + _, err := os.Stat(target) + if err == nil { + return nil + } + if !os.IsNotExist(err) { + return fmt.Errorf("windivert: stat %s: %w", asset.name, err) + } + tmp := target + ".tmp-" + strconv.Itoa(os.Getpid()) + err = os.WriteFile(tmp, asset.data, 0o644) + if err != nil { + return fmt.Errorf("windivert: write %s: %w", asset.name, err) + } + err = os.Rename(tmp, target) + if err != nil { + os.Remove(tmp) + if _, statErr := os.Stat(target); statErr == nil { + return nil + } + return fmt.Errorf("windivert: rename %s: %w", asset.name, err) + } + return nil +} diff --git a/transport/internet/tls/tlsspoof/windivert/filter.go b/transport/internet/tls/tlsspoof/windivert/filter.go new file mode 100644 index 000000000000..d63adae2b630 --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/filter.go @@ -0,0 +1,181 @@ +package windivert + +import ( + "encoding/binary" + "errors" + "net/netip" +) + +// WINDIVERT_FILTER VM instruction layout (24 bytes, #pragma pack(1)): +// +// word 0 (LE): field:11 | test:5 | success:16 +// word 1 (LE): failure:16 | neg:1 | reserved:15 +// words 2..5: arg[4] (native-endian uint32 each) +// +// The driver walks this as a decision tree: evaluate the test at inst i; +// on success jump to success; on failure jump to failure. Continuations +// 0x7FFE and 0x7FFF are ACCEPT and REJECT terminals. +const ( + filterInstBytes = 24 + filterMaxInsts = 256 + + fieldZero = 0 + fieldOutbound = 2 + fieldIP = 5 + fieldIPv6 = 6 + fieldTCP = 8 + fieldIPSrcAddr = 21 + fieldIPDstAddr = 22 + fieldIPv6SrcAddr = 28 + fieldIPv6DstAddr = 29 + fieldTCPSrcPort = 38 + fieldTCPDstPort = 39 + + testEQ = 0 + + resultAccept uint16 = 0x7FFE + resultReject uint16 = 0x7FFF +) + +// Filter flags passed to IOCTL_WINDIVERT_STARTUP alongside the compiled +// filter. These tell the driver what *kinds* of packets the filter might +// match, used as a kernel-side fast-reject. +const ( + filterFlagOutbound uint64 = 0x0020 + filterFlagIP uint64 = 0x0040 + filterFlagIPv6 uint64 = 0x0080 +) + +type filterInst struct { + field uint16 // 11 bits used + test uint8 // 5 bits used + success uint16 + failure uint16 + neg bool + arg [4]uint32 +} + +// Filter is a typed specification of packets to capture. It replaces +// WinDivert's filter string language. +// +// Zero value = "reject all" (match nothing), suitable for send-only handles. +type Filter struct { + insts []filterInst + flags uint64 // filter flags for STARTUP ioctl +} + +// reject returns a filter that matches no packet. The empty insts slice +// is encoded as a single rejecting instruction by encode(). +func reject() *Filter { + return &Filter{} +} + +// OutboundTCP returns a filter matching outbound TCP packets on the given +// 5-tuple. Both addresses must share an address family (IPv4 or IPv6). +func OutboundTCP(src, dst netip.AddrPort) (*Filter, error) { + if !src.IsValid() || !dst.IsValid() { + return nil, errors.New("windivert: filter: invalid address port") + } + if src.Addr().Is4() != dst.Addr().Is4() { + return nil, errors.New("windivert: filter: mixed IPv4/IPv6") + } + f := &Filter{ + flags: filterFlagOutbound, + } + // Insts chain as AND: each test's failure = REJECT, success = next inst. + // The final inst's success = ACCEPT. + f.add(fieldOutbound, testEQ, argUint32(1)) + if src.Addr().Is4() { + f.flags |= filterFlagIP + f.add(fieldIP, testEQ, argUint32(1)) + f.add(fieldTCP, testEQ, argUint32(1)) + f.add(fieldIPSrcAddr, testEQ, argIPv4(src.Addr())) + f.add(fieldIPDstAddr, testEQ, argIPv4(dst.Addr())) + } else { + f.flags |= filterFlagIPv6 + f.add(fieldIPv6, testEQ, argUint32(1)) + f.add(fieldTCP, testEQ, argUint32(1)) + f.add(fieldIPv6SrcAddr, testEQ, argIPv6(src.Addr())) + f.add(fieldIPv6DstAddr, testEQ, argIPv6(dst.Addr())) + } + f.add(fieldTCPSrcPort, testEQ, argUint32(uint32(src.Port()))) + f.add(fieldTCPDstPort, testEQ, argUint32(uint32(dst.Port()))) + return f, nil +} + +func (f *Filter) add(field uint16, test uint8, arg [4]uint32) { + f.insts = append(f.insts, filterInst{field: field, test: test, arg: arg}) +} + +func argUint32(v uint32) [4]uint32 { return [4]uint32{v, 0, 0, 0} } + +// argIPv4 encodes an IPv4 address for IP_SRCADDR/IP_DSTADDR. The driver +// compares against an IPv4-mapped-IPv6 form: {host_order_u32, 0x0000FFFF, +// 0, 0} (see sys/windivert.c windivert_get_ipv4_addr and the IPv4_SRCADDR +// val-word construction). Omitting the 0x0000FFFF marker causes the EQ +// test to fail for every packet. +func argIPv4(addr netip.Addr) [4]uint32 { + b := addr.As4() + return [4]uint32{binary.BigEndian.Uint32(b[:]), 0x0000FFFF, 0, 0} +} + +// argIPv6 encodes an IPv6 address for IPV6_SRCADDR/IPV6_DSTADDR. The +// driver stores the address as four host-order uint32s in REVERSED word +// order: val[0]=low (bytes 12..15), val[3]=high (bytes 0..3). See +// sys/windivert.c windivert_outbound_network_v6_classify val-word +// construction. +func argIPv6(addr netip.Addr) [4]uint32 { + b := addr.As16() + return [4]uint32{ + binary.BigEndian.Uint32(b[12:16]), + binary.BigEndian.Uint32(b[8:12]), + binary.BigEndian.Uint32(b[4:8]), + binary.BigEndian.Uint32(b[0:4]), + } +} + +// encode serializes the Filter to the on-wire WINDIVERT_FILTER[] format +// plus the filter_flags for STARTUP ioctl. +func (f *Filter) encode() ([]byte, uint64, error) { + if len(f.insts) == 0 { + // "Reject all" — one instruction, ZERO == 0 is always true, but we + // invert by setting both success and failure to REJECT. + return encodeInst(filterInst{ + field: fieldZero, + test: testEQ, + success: resultReject, + failure: resultReject, + }), 0, nil + } + if len(f.insts) > filterMaxInsts-1 { + return nil, 0, errors.New("windivert: filter too long") + } + buf := make([]byte, 0, filterInstBytes*len(f.insts)) + for i, inst := range f.insts { + if i == len(f.insts)-1 { + inst.success = resultAccept + } else { + inst.success = uint16(i + 1) + } + inst.failure = resultReject + buf = append(buf, encodeInst(inst)...) + } + return buf, f.flags, nil +} + +func encodeInst(inst filterInst) []byte { + out := make([]byte, filterInstBytes) + word0 := uint32(inst.field&0x7FF) | uint32(inst.test&0x1F)<<11 | + uint32(inst.success)<<16 + word1 := uint32(inst.failure) + if inst.neg { + word1 |= 1 << 16 + } + binary.LittleEndian.PutUint32(out[0:4], word0) + binary.LittleEndian.PutUint32(out[4:8], word1) + binary.LittleEndian.PutUint32(out[8:12], inst.arg[0]) + binary.LittleEndian.PutUint32(out[12:16], inst.arg[1]) + binary.LittleEndian.PutUint32(out[16:20], inst.arg[2]) + binary.LittleEndian.PutUint32(out[20:24], inst.arg[3]) + return out +} diff --git a/transport/internet/tls/tlsspoof/windivert/handle_windows.go b/transport/internet/tls/tlsspoof/windivert/handle_windows.go new file mode 100644 index 000000000000..c48e6214c11b --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/handle_windows.go @@ -0,0 +1,323 @@ +//go:build windows + +package windivert + +import ( + "encoding/binary" + "errors" + "fmt" + "runtime" + "sync" + "unsafe" + + "golang.org/x/sys/windows" +) + +// Handle owns a WinDivert kernel device handle plus a private event for +// overlapped I/O. Methods on *Handle are not safe for concurrent use +// across goroutines (there is a single shared event per Handle). +// +// addr is a per-Handle Address buffer the IOCTL struct embeds a pointer +// to. It lives on the heap (as a field of a heap-allocated Handle) so +// the pointer value stored as bytes in the ioctl buffer remains valid +// across stack growth between buildIoctl* and the DeviceIoControl +// syscall — stack-local Address values are not safe for this pattern +// because Go's escape analysis does not see the pointer through the +// unsafe.Pointer → uintptr → bytes conversion. +type Handle struct { + device windows.Handle + event windows.Handle + closing sync.Once + closeErr error + addr Address +} + +// Filter may be nil for "reject all", suitable for send-only handles. +// Requires Administrator on first call per process (installs the kernel +// driver via SCM); subsequent calls reuse the running driver. +func Open(filter *Filter, layer Layer, priority int16, flags Flag) (*Handle, error) { + err := validateOpenArgs(layer, priority, flags) + if err != nil { + return nil, err + } + if filter == nil { + filter = reject() + } + filterBin, filterFlags, err := filter.encode() + if err != nil { + return nil, err + } + device, err := openDevice() + if err != nil { + if !errors.Is(err, windows.ERROR_FILE_NOT_FOUND) && + !errors.Is(err, windows.ERROR_PATH_NOT_FOUND) { + if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + return nil, fmt.Errorf("windivert: open device (administrator required): %w", err) + } + return nil, fmt.Errorf("windivert: open device: %w", err) + } + // Device node missing: kernel driver not loaded. Install + retry. + // Matches WinDivertOpen's lazy-install path; avoids racing StartService + // against a still-loaded driver whose SCM record is marked for deletion. + err = ensureDriver() + if err != nil { + return nil, err + } + device, err = openDevice() + if err != nil { + if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + return nil, fmt.Errorf("windivert: open device (administrator required): %w", err) + } + return nil, fmt.Errorf("windivert: open device: %w", err) + } + } + event, err := windows.CreateEvent(nil, 1, 0, nil) // manual reset, unsignaled + if err != nil { + windows.CloseHandle(device) + return nil, fmt.Errorf("windivert: create event: %w", err) + } + h := &Handle{device: device, event: event} + + err = h.initialize(layer, priority, flags) + if err != nil { + h.Close() + return nil, err + } + err = h.startup(filterBin, filterFlags) + if err != nil { + h.Close() + return nil, err + } + return h, nil +} + +func openDevice() (windows.Handle, error) { + return windows.CreateFile( + driverDevName, + windows.GENERIC_READ|windows.GENERIC_WRITE, + 0, nil, + windows.OPEN_EXISTING, + windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_OVERLAPPED, + 0, + ) +} + +func validateOpenArgs(layer Layer, priority int16, flags Flag) error { + if layer != LayerNetwork { + return fmt.Errorf("windivert: invalid layer %d", uint32(layer)) + } + if priority < PriorityLowest || priority > PriorityHighest { + return errors.New("windivert: priority out of range") + } + const supportedFlags = FlagSniff | FlagSendOnly + if flags&^supportedFlags != 0 { + return errors.New("windivert: unknown flag bits") + } + if flags&FlagSniff != 0 && flags&FlagSendOnly != 0 { + return errors.New("windivert: FlagSniff and FlagSendOnly are mutually exclusive") + } + return nil +} + +func (h *Handle) initialize(layer Layer, priority int16, flags Flag) error { + in := buildIoctlInitialize(layer, priority, flags) + // WINDIVERT_VERSION is a 64-byte packed struct; only the first 20 + // bytes (magic, major, minor, bits) carry data, the rest is reserved. + var outBuf [versionStructSize]byte + binary.LittleEndian.PutUint64(outBuf[0:8], magicDLL) + binary.LittleEndian.PutUint32(outBuf[8:12], versionMajor) + binary.LittleEndian.PutUint32(outBuf[12:16], versionMinor) + binary.LittleEndian.PutUint32(outBuf[16:20], uint32(unsafe.Sizeof(uintptr(0))*8)) + _, err := doIoctl(h.device, ioctlInitialize, in[:], outBuf[:], h.event) + if err != nil { + return fmt.Errorf("windivert: initialize ioctl: %w", err) + } + gotMagic := binary.LittleEndian.Uint64(outBuf[0:8]) + if gotMagic != magicSYS { + return fmt.Errorf("windivert: driver magic mismatch (got %d)", gotMagic) + } + gotMajor := binary.LittleEndian.Uint32(outBuf[8:12]) + if gotMajor < versionMajor { + gotMinor := binary.LittleEndian.Uint32(outBuf[12:16]) + return fmt.Errorf("windivert: driver version too old: %d.%d", gotMajor, gotMinor) + } + return nil +} + +func (h *Handle) startup(filterBin []byte, filterFlags uint64) error { + in := buildIoctlStartup(filterFlags) + _, err := doIoctl(h.device, ioctlStartup, in[:], filterBin, h.event) + if err != nil { + return fmt.Errorf("windivert: startup ioctl: %w", err) + } + return nil +} + +// If the handle is closed mid-Recv the error wraps ERROR_OPERATION_ABORTED. +func (h *Handle) Recv(buf []byte) (int, Address, error) { + if len(buf) == 0 { + return 0, Address{}, errors.New("windivert: recv: zero-length buffer") + } + h.addr = Address{} + in := buildIoctlRecv(&h.addr) + n, err := doIoctl(h.device, ioctlRecv, in[:], buf, h.event) + runtime.KeepAlive(h) + if err != nil { + return 0, Address{}, err + } + return int(n), h.addr, nil +} + +// The address's Outbound flag controls whether the packet is sent toward +// the wire (outbound=true) or delivered up the stack (outbound=false). +// IfIdx and SubIfIdx can stay zero — the driver uses the routing table +// when IfIdx=0. +func (h *Handle) Send(packet []byte, addr *Address) (int, error) { + if len(packet) == 0 { + return 0, errors.New("windivert: send: empty packet") + } + if addr == nil { + return 0, errors.New("windivert: send: nil address") + } + h.addr = *addr + in := buildIoctlSend(&h.addr) + n, err := doIoctl(h.device, ioctlSend, in[:], packet, h.event) + runtime.KeepAlive(h) + if err != nil { + return 0, err + } + return int(n), nil +} + +// Idempotent. Aborts any in-flight I/O on the handle. +func (h *Handle) Close() error { + h.closing.Do(func() { + var errs []error + if h.device != 0 { + err := windows.CloseHandle(h.device) + if err != nil { + errs = append(errs, err) + } + h.device = 0 + } + if h.event != 0 { + err := windows.CloseHandle(h.event) + if err != nil { + errs = append(errs, err) + } + h.event = 0 + } + h.closeErr = errors.Join(errs...) + }) + return h.closeErr +} + +// IOCTL codes from windivert_device.h. CTL_CODE macro layout: +// +// (DeviceType << 16) | (Access << 14) | (Function << 2) | Method +const ( + fileDeviceNetwork uint32 = 0x12 + accessReadWrite uint32 = 3 // FILE_READ_DATA | FILE_WRITE_DATA + accessRead uint32 = 1 + + methodInDirect uint32 = 1 + methodOutDirect uint32 = 2 +) + +func ctlCode(deviceType, access, function, method uint32) uint32 { + return (deviceType << 16) | (access << 14) | (function << 2) | method +} + +var ( + ioctlInitialize = ctlCode(fileDeviceNetwork, accessReadWrite, 0x921, methodOutDirect) + ioctlStartup = ctlCode(fileDeviceNetwork, accessReadWrite, 0x922, methodInDirect) + ioctlRecv = ctlCode(fileDeviceNetwork, accessRead, 0x923, methodOutDirect) + ioctlSend = ctlCode(fileDeviceNetwork, accessReadWrite, 0x924, methodInDirect) +) + +// Magic numbers exchanged during INITIALIZE. DLL sends magicDLL in the +// version struct; driver returns magicSYS on success. +const ( + magicDLL uint64 = 0x4C4C447669645724 // "$WdivDLL" in LE bytes + magicSYS uint64 = 0x5359537669645723 // "#WdivSYS" in LE bytes +) + +const ( + versionMajor uint32 = 2 + versionMinor uint32 = 2 +) + +// Size of the WINDIVERT_IOCTL union on wire (packed). +const ioctlSize = 16 + +// Size of WINDIVERT_VERSION on wire (packed). Only the first 20 bytes +// carry data; the rest is reserved zero padding. +const versionStructSize = 64 + +// doIoctl performs a single synchronous (blocking) overlapped +// DeviceIoControl. The handle is opened with FILE_FLAG_OVERLAPPED so +// DeviceIoControl returns ERROR_IO_PENDING; we then wait for completion +// via GetOverlappedResult. Event is passed in so callers can reuse it +// across calls on the same handle (avoids per-call CreateEvent). +func doIoctl(handle windows.Handle, code uint32, in []byte, out []byte, event windows.Handle) (uint32, error) { + var overlapped windows.Overlapped + overlapped.HEvent = event + _ = windows.ResetEvent(event) + + var inPtr *byte + var inLen uint32 + if len(in) > 0 { + inPtr = &in[0] + inLen = uint32(len(in)) + } + var outPtr *byte + var outLen uint32 + if len(out) > 0 { + outPtr = &out[0] + outLen = uint32(len(out)) + } + var returned uint32 + err := windows.DeviceIoControl(handle, code, inPtr, inLen, outPtr, outLen, &returned, &overlapped) + if err != nil && !errors.Is(err, windows.ERROR_IO_PENDING) { + return 0, err + } + err = windows.GetOverlappedResult(handle, &overlapped, &returned, true) + if err != nil { + return 0, err + } + return returned, nil +} + +func buildIoctlInitialize(layer Layer, priority int16, flags Flag) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint32(buf[0:4], uint32(layer)) + // The driver expects priority + WINDIVERT_PRIORITY_HIGHEST (30000) so + // the low range maps to non-negative integers. + binary.LittleEndian.PutUint32(buf[4:8], uint32(int32(priority)+int32(PriorityHighest))) + binary.LittleEndian.PutUint64(buf[8:16], uint64(flags)) + return buf +} + +func buildIoctlStartup(filterFlags uint64) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint64(buf[0:8], filterFlags) + return buf +} + +// buildIoctlRecv packs a user-space pointer to a WINDIVERT_ADDRESS into +// the ioctl struct. The driver dereferences it to write the address for +// the received packet. Caller must keep the Address alive via +// runtime.KeepAlive. +func buildIoctlRecv(addr *Address) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint64(buf[0:8], uint64(uintptr(unsafe.Pointer(addr)))) + binary.LittleEndian.PutUint64(buf[8:16], 0) + return buf +} + +func buildIoctlSend(addr *Address) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint64(buf[0:8], uint64(uintptr(unsafe.Pointer(addr)))) + binary.LittleEndian.PutUint64(buf[8:16], uint64(unsafe.Sizeof(Address{}))) + return buf +} diff --git a/transport/internet/tls/tlsspoof/windivert/windivert.go b/transport/internet/tls/tlsspoof/windivert/windivert.go new file mode 100644 index 000000000000..9d309886cbe3 --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/windivert.go @@ -0,0 +1,78 @@ +// Package windivert provides a pure-Go binding to the WinDivert kernel +// driver on Windows (amd64 and 386). User-mode WinDivert calls are +// reimplemented in Go; only the signed kernel driver is embedded as an +// asset, since SCM-installed drivers must live on disk and their +// Authenticode signature forbids modification. +// +// Administrator is required for the first Open in a process so SCM can +// load the driver. Upstream: https://github.com/basil00/WinDivert v2.2.2, +// redistributed under its LGPL v3 option; see assets/LICENSE.txt. +package windivert + +import "unsafe" + +const AssetVersion = "2.2.2" + +// MTUMax is WINDIVERT_MTU_MAX from windivert.h (40 + 0xFFFF). Suitable as +// a single-packet receive buffer size. +const MTUMax = 40 + 0xFFFF + +type Layer uint32 + +const LayerNetwork Layer = 0 + +type Flag uint64 + +const ( + // FlagSniff opens a passive observer: the driver copies matching packets + // to userspace without removing them from the network stack. Send is not + // required (and not allowed) on a sniffing handle. + FlagSniff Flag = 0x0001 + // FlagSendOnly opens a write-only injection handle; Recv is not allowed. + FlagSendOnly Flag = 0x0008 +) + +const ( + PriorityHighest int16 = 30000 + PriorityLowest int16 = -30000 +) + +// Address mirrors WINDIVERT_ADDRESS from windivert.h (80 bytes, +// little-endian on both amd64 and 386): +// +// 0: INT64 Timestamp +// 8: UINT32 bitfield: Layer:8 | Event:8 | flags | Reserved1:8 +// 12: UINT32 Reserved2 +// 16: 64 bytes union (WINDIVERT_DATA_NETWORK / FLOW / SOCKET / REFLECT) +type Address struct { + Timestamp int64 + bits uint32 + Reserved2 uint32 + union [64]byte +} + +var _ [80]byte = [unsafe.Sizeof(Address{})]byte{} + +// Bit positions inside the Address's packed flags word. +const ( + addrBitIPv6 = 20 + addrBitIPChecksum = 21 + addrBitTCPChecksum = 22 +) + +func getFlagBit(bits uint32, pos uint) bool { return bits&(1< Date: Mon, 4 May 2026 13:00:00 +0600 Subject: [PATCH 19/42] TLS: Add spoof, spoof_method and spoof_count options --- transport/internet/tls/config.pb.go | 48 +++++++++++++++++++++++------ transport/internet/tls/config.proto | 7 +++++ transport/internet/tls/tls.go | 33 ++++++++++++++++++++ 3 files changed, 79 insertions(+), 9 deletions(-) diff --git a/transport/internet/tls/config.pb.go b/transport/internet/tls/config.pb.go index 37628755eb4f..2d0e60a19448 100644 --- a/transport/internet/tls/config.pb.go +++ b/transport/internet/tls/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v6.33.5 +// protoc v7.34.1 // source: transport/internet/tls/config.proto package tls @@ -201,15 +201,20 @@ type Config struct { RejectUnknownSni bool `protobuf:"varint,12,opt,name=reject_unknown_sni,json=rejectUnknownSni,proto3" json:"reject_unknown_sni,omitempty"` MasterKeyLog string `protobuf:"bytes,15,opt,name=master_key_log,json=masterKeyLog,proto3" json:"master_key_log,omitempty"` // Lists of string as CurvePreferences values. - CurvePreferences []string `protobuf:"bytes,16,rep,name=curve_preferences,json=curvePreferences,proto3" json:"curve_preferences,omitempty"` - VerifyPeerCertByName []string `protobuf:"bytes,17,rep,name=verify_peer_cert_by_name,json=verifyPeerCertByName,proto3" json:"verify_peer_cert_by_name,omitempty"` - EchServerKeys []byte `protobuf:"bytes,18,opt,name=ech_server_keys,json=echServerKeys,proto3" json:"ech_server_keys,omitempty"` - EchConfigList string `protobuf:"bytes,19,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"` + CurvePreferences []string `protobuf:"bytes,16,rep,name=curve_preferences,json=curvePreferences,proto3" json:"curve_preferences,omitempty"` + VerifyPeerCertByName []string `protobuf:"bytes,17,rep,name=verify_peer_cert_by_name,json=verifyPeerCertByName,proto3" json:"verify_peer_cert_by_name,omitempty"` + EchServerKeys []byte `protobuf:"bytes,18,opt,name=ech_server_keys,json=echServerKeys,proto3" json:"ech_server_keys,omitempty"` + EchConfigList string `protobuf:"bytes,19,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"` + // Deprecated EchForceQuery string `protobuf:"bytes,20,opt,name=ech_force_query,json=echForceQuery,proto3" json:"ech_force_query,omitempty"` EchSocketSettings *internet.SocketConfig `protobuf:"bytes,21,opt,name=ech_socket_settings,json=echSocketSettings,proto3" json:"ech_socket_settings,omitempty"` PinnedPeerCertSha256 [][]byte `protobuf:"bytes,22,rep,name=pinned_peer_cert_sha256,json=pinnedPeerCertSha256,proto3" json:"pinned_peer_cert_sha256,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Spoof string `protobuf:"bytes,23,opt,name=spoof,proto3" json:"spoof,omitempty"` + SpoofMethod string `protobuf:"bytes,24,opt,name=spoof_method,json=spoofMethod,proto3" json:"spoof_method,omitempty"` + // Number of times to inject the fake ClientHello (0 or 1 = single-shot). + SpoofCount int32 `protobuf:"varint,25,opt,name=spoof_count,json=spoofCount,proto3" json:"spoof_count,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Config) Reset() { @@ -375,6 +380,27 @@ func (x *Config) GetPinnedPeerCertSha256() [][]byte { return nil } +func (x *Config) GetSpoof() string { + if x != nil { + return x.Spoof + } + return "" +} + +func (x *Config) GetSpoofMethod() string { + if x != nil { + return x.SpoofMethod + } + return "" +} + +func (x *Config) GetSpoofCount() int32 { + if x != nil { + return x.SpoofCount + } + return 0 +} + var File_transport_internet_tls_config_proto protoreflect.FileDescriptor const file_transport_internet_tls_config_proto_rawDesc = "" + @@ -393,7 +419,7 @@ const file_transport_internet_tls_config_proto_rawDesc = "" + "\x05Usage\x12\x10\n" + "\fENCIPHERMENT\x10\x00\x12\x14\n" + "\x10AUTHORITY_VERIFY\x10\x01\x12\x13\n" + - "\x0fAUTHORITY_ISSUE\x10\x02\"\xf5\x06\n" + + "\x0fAUTHORITY_ISSUE\x10\x02\"\xcf\a\n" + "\x06Config\x12%\n" + "\x0eallow_insecure\x18\x01 \x01(\bR\rallowInsecure\x12J\n" + "\vcertificate\x18\x02 \x03(\v2(.xray.transport.internet.tls.CertificateR\vcertificate\x12\x1f\n" + @@ -416,7 +442,11 @@ const file_transport_internet_tls_config_proto_rawDesc = "" + "\x0fech_config_list\x18\x13 \x01(\tR\rechConfigList\x12&\n" + "\x0fech_force_query\x18\x14 \x01(\tR\rechForceQuery\x12U\n" + "\x13ech_socket_settings\x18\x15 \x01(\v2%.xray.transport.internet.SocketConfigR\x11echSocketSettings\x125\n" + - "\x17pinned_peer_cert_sha256\x18\x16 \x03(\fR\x14pinnedPeerCertSha256Bs\n" + + "\x17pinned_peer_cert_sha256\x18\x16 \x03(\fR\x14pinnedPeerCertSha256\x12\x14\n" + + "\x05spoof\x18\x17 \x01(\tR\x05spoof\x12!\n" + + "\fspoof_method\x18\x18 \x01(\tR\vspoofMethod\x12\x1f\n" + + "\vspoof_count\x18\x19 \x01(\x05R\n" + + "spoofCountBs\n" + "\x1fcom.xray.transport.internet.tlsP\x01Z0github.com/xtls/xray-core/transport/internet/tls\xaa\x02\x1bXray.Transport.Internet.Tlsb\x06proto3" var ( diff --git a/transport/internet/tls/config.proto b/transport/internet/tls/config.proto index 4592822649c3..0039d0901a7d 100644 --- a/transport/internet/tls/config.proto +++ b/transport/internet/tls/config.proto @@ -87,4 +87,11 @@ message Config { SocketConfig ech_socket_settings = 21; repeated bytes pinned_peer_cert_sha256 = 22; + + string spoof = 23; + + string spoof_method = 24; + + // Number of times to inject the fake ClientHello (0 or 1 = single-shot). + int32 spoof_count = 25; } diff --git a/transport/internet/tls/tls.go b/transport/internet/tls/tls.go index 7fa3c25be55d..b8bc4102a31f 100644 --- a/transport/internet/tls/tls.go +++ b/transport/internet/tls/tls.go @@ -5,13 +5,17 @@ import ( "crypto/rand" "crypto/tls" "math/big" + gonet "net" "slices" + "strings" "time" utls "github.com/refraction-networking/utls" "github.com/xtls/xray-core/common/buf" + "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/utils" + "github.com/xtls/xray-core/transport/internet/tls/tlsspoof" ) type Interface interface { @@ -64,6 +68,35 @@ func Client(c net.Conn, config *tls.Config) net.Conn { return &Conn{Conn: tlsConn} } +// WrapWithSpoof wraps a connection with TLS spoofing if the config has +// spoof settings. The spoofed ClientHello is injected via raw sockets +// before the real TLS handshake, causing DPI middleboxes to see the +// forged SNI while the actual connection proceeds normally. +// spoofCount controls how many Write() calls trigger injection (0 = single-shot). +func WrapWithSpoof(c net.Conn, spoofSNI string, spoofMethodStr string, spoofCount int32, serverName string) (net.Conn, error) { + spoofSNI, method, err := tlsspoof.ParseOptions(spoofSNI, spoofMethodStr) + if err != nil { + return nil, errors.New("tls_spoof: invalid options").Base(err) + } + if spoofSNI == "" { + return c, nil + } + if serverName == "" { + return nil, errors.New("tls_spoof: requires a TLS server name (SNI)") + } + if gonet.ParseIP(serverName) != nil { + return nil, errors.New("tls_spoof: cannot spoof when server name is an IP literal") + } + if strings.EqualFold(spoofSNI, serverName) { + return nil, errors.New("tls_spoof: spoof must differ from server_name") + } + wrapped, err := tlsspoof.NewConn(c, method, spoofSNI, int(spoofCount)) + if err != nil { + return nil, errors.New("tls_spoof: failed to create spoof conn").Base(err) + } + return wrapped, nil +} + // Server initiates a TLS server handshake on the given connection. func Server(c net.Conn, config *tls.Config) net.Conn { tlsConn := tls.Server(c, config) From 291b5f9e407bcf7074aaa5443e91c58630ec907f Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Thu, 7 May 2026 15:00:00 +0600 Subject: [PATCH 20/42] Transport: Integrate TLS spoof into dialers --- transport/internet/grpc/dial.go | 6 ++++++ transport/internet/httpupgrade/dialer.go | 6 ++++++ transport/internet/kcp/dialer.go | 9 ++++++++- transport/internet/splithttp/dialer.go | 6 ++++++ transport/internet/tcp/dialer.go | 10 ++++++++++ transport/internet/tls/config.pb.go | 2 +- transport/internet/websocket/dialer.go | 8 ++++++++ 7 files changed, 45 insertions(+), 2 deletions(-) diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index c8b8423c6579..b17caa9730fc 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -140,6 +140,12 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in if config.ServerName == "" && address.Family().IsDomain() { config.ServerName = address.Domain() } + if spoofConn, err := tls.WrapWithSpoof(c, tlsConfig.Spoof, tlsConfig.SpoofMethod, tlsConfig.SpoofCount, config.ServerName); err != nil { + c.Close() + return nil, err + } else { + c = spoofConn + } if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil { return tls.UClient(c, config, fingerprint), nil } else { // Fallback to normal gRPC TLS diff --git a/transport/internet/httpupgrade/dialer.go b/transport/internet/httpupgrade/dialer.go index 571797f6172d..bb9df1c912fb 100644 --- a/transport/internet/httpupgrade/dialer.go +++ b/transport/internet/httpupgrade/dialer.go @@ -66,6 +66,12 @@ func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings * tConfig := tls.ConfigFromStreamSettings(streamSettings) if tConfig != nil { tlsConfig := tConfig.GetTLSConfig(tls.WithDestination(dest), tls.WithNextProto("http/1.1")) + if spoofConn, err := tls.WrapWithSpoof(pconn, tConfig.Spoof, tConfig.SpoofMethod, tConfig.SpoofCount, tlsConfig.ServerName); err != nil { + pconn.Close() + return nil, err + } else { + pconn = spoofConn + } if fingerprint := tls.GetFingerprint(tConfig.Fingerprint); fingerprint != nil { conn = tls.UClient(pconn, tlsConfig, fingerprint) if err := conn.(*tls.UConn).WebsocketHandshakeContext(ctx); err != nil { diff --git a/transport/internet/kcp/dialer.go b/transport/internet/kcp/dialer.go index 175998ec7dd3..e3ff0bdc9a19 100644 --- a/transport/internet/kcp/dialer.go +++ b/transport/internet/kcp/dialer.go @@ -97,7 +97,14 @@ func DialKCP(ctx context.Context, dest net.Destination, streamSettings *internet var iConn stat.Connection = session if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { - iConn = tls.Client(iConn, config.GetTLSConfig(tls.WithDestination(dest))) + tlsConfig := config.GetTLSConfig(tls.WithDestination(dest)) + if spoofConn, err := tls.WrapWithSpoof(iConn, config.Spoof, config.SpoofMethod, config.SpoofCount, tlsConfig.ServerName); err != nil { + iConn.Close() + return nil, err + } else { + iConn = spoofConn.(stat.Connection) + } + iConn = tls.Client(iConn, tlsConfig) } return iConn, nil diff --git a/transport/internet/splithttp/dialer.go b/transport/internet/splithttp/dialer.go index 1329713c5f81..1b8c00d0a2bd 100644 --- a/transport/internet/splithttp/dialer.go +++ b/transport/internet/splithttp/dialer.go @@ -138,6 +138,12 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea } if gotlsConfig != nil { + if spoofConn, err := tls.WrapWithSpoof(conn, tlsConfig.Spoof, tlsConfig.SpoofMethod, tlsConfig.SpoofCount, gotlsConfig.ServerName); err != nil { + conn.Close() + return nil, err + } else { + conn = spoofConn + } if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil { conn = tls.UClient(conn, gotlsConfig, fingerprint) if err := conn.(*tls.UConn).HandshakeContext(ctxInner); err != nil { diff --git a/transport/internet/tcp/dialer.go b/transport/internet/tcp/dialer.go index 92fa7557f13a..e226a5657cb3 100644 --- a/transport/internet/tcp/dialer.go +++ b/transport/internet/tcp/dialer.go @@ -74,6 +74,11 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me } } if fingerprint := tls.GetFingerprint(config.Fingerprint); fingerprint != nil { + if spoofConn, err := tls.WrapWithSpoof(conn, config.Spoof, config.SpoofMethod, config.SpoofCount, tlsConfig.ServerName); err != nil { + return nil, err + } else { + conn = spoofConn + } conn = tls.UClient(conn, tlsConfig, fingerprint) if len(tlsConfig.NextProtos) == 1 && tlsConfig.NextProtos[0] == "http/1.1" { // allow manually specify err = conn.(*tls.UConn).WebsocketHandshakeContext(ctx) @@ -81,6 +86,11 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me err = conn.(*tls.UConn).HandshakeContext(ctx) } } else { + if spoofConn, err := tls.WrapWithSpoof(conn, config.Spoof, config.SpoofMethod, config.SpoofCount, tlsConfig.ServerName); err != nil { + return nil, err + } else { + conn = spoofConn + } conn = tls.Client(conn, tlsConfig) err = conn.(*tls.Conn).HandshakeContext(ctx) } diff --git a/transport/internet/tls/config.pb.go b/transport/internet/tls/config.pb.go index 2d0e60a19448..700c70883ab1 100644 --- a/transport/internet/tls/config.pb.go +++ b/transport/internet/tls/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v7.34.1 +// protoc v6.33.5 // source: transport/internet/tls/config.proto package tls diff --git a/transport/internet/websocket/dialer.go b/transport/internet/websocket/dialer.go index 8e295da062e8..f6eb73e1edae 100644 --- a/transport/internet/websocket/dialer.go +++ b/transport/internet/websocket/dialer.go @@ -94,6 +94,14 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in pconn = newConn } + // Wrap with TLS spoofing if configured + if spoofConn, err := tls.WrapWithSpoof(pconn, tConfig.Spoof, tConfig.SpoofMethod, tConfig.SpoofCount, tlsConfig.ServerName); err != nil { + pconn.Close() + return nil, err + } else { + pconn = spoofConn + } + // TLS and apply the handshake cn := tls.UClient(pconn, tlsConfig, fingerprint).(*tls.UConn) if err := cn.WebsocketHandshakeContext(ctx); err != nil { From e1616a450d16232bcb9a7a96666a9e8e3a763bc4 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Sat, 9 May 2026 10:30:00 +0600 Subject: [PATCH 21/42] Rawpacket: Add raw socket spoofers for Linux, Darwin, FreeBSD and Windows --- .../internet/finalmask/rawpacket/endpoints.go | 27 + .../internet/finalmask/rawpacket/packet.go | 163 +++ .../finalmask/rawpacket/raw_darwin.go | 200 +++ .../finalmask/rawpacket/raw_freebsd.go | 174 +++ .../internet/finalmask/rawpacket/raw_linux.go | 168 +++ .../internet/finalmask/rawpacket/raw_stub.go | 15 + .../internet/finalmask/rawpacket/raw_unix.go | 25 + .../finalmask/rawpacket/raw_windows.go | 236 ++++ .../internet/finalmask/rawpacket/tcpip.go | 155 +++ .../rawpacket/windivert/assets/LICENSE.txt | 1191 +++++++++++++++++ .../windivert/assets/WinDivert32.sys | Bin 0 -> 79792 bytes .../windivert/assets/WinDivert64.sys | Bin 0 -> 94144 bytes .../rawpacket/windivert/assets_386.go | 14 + .../rawpacket/windivert/assets_amd64.go | 14 + .../rawpacket/windivert/assets_unsupported.go | 7 + .../rawpacket/windivert/driver_windows.go | 211 +++ .../finalmask/rawpacket/windivert/filter.go | 181 +++ .../rawpacket/windivert/handle_windows.go | 323 +++++ .../rawpacket/windivert/windivert.go | 78 ++ 19 files changed, 3182 insertions(+) create mode 100644 transport/internet/finalmask/rawpacket/endpoints.go create mode 100644 transport/internet/finalmask/rawpacket/packet.go create mode 100644 transport/internet/finalmask/rawpacket/raw_darwin.go create mode 100644 transport/internet/finalmask/rawpacket/raw_freebsd.go create mode 100644 transport/internet/finalmask/rawpacket/raw_linux.go create mode 100644 transport/internet/finalmask/rawpacket/raw_stub.go create mode 100644 transport/internet/finalmask/rawpacket/raw_unix.go create mode 100644 transport/internet/finalmask/rawpacket/raw_windows.go create mode 100644 transport/internet/finalmask/rawpacket/tcpip.go create mode 100644 transport/internet/finalmask/rawpacket/windivert/assets/LICENSE.txt create mode 100644 transport/internet/finalmask/rawpacket/windivert/assets/WinDivert32.sys create mode 100644 transport/internet/finalmask/rawpacket/windivert/assets/WinDivert64.sys create mode 100644 transport/internet/finalmask/rawpacket/windivert/assets_386.go create mode 100644 transport/internet/finalmask/rawpacket/windivert/assets_amd64.go create mode 100644 transport/internet/finalmask/rawpacket/windivert/assets_unsupported.go create mode 100644 transport/internet/finalmask/rawpacket/windivert/driver_windows.go create mode 100644 transport/internet/finalmask/rawpacket/windivert/filter.go create mode 100644 transport/internet/finalmask/rawpacket/windivert/handle_windows.go create mode 100644 transport/internet/finalmask/rawpacket/windivert/windivert.go diff --git a/transport/internet/finalmask/rawpacket/endpoints.go b/transport/internet/finalmask/rawpacket/endpoints.go new file mode 100644 index 000000000000..6c7107eb3987 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/endpoints.go @@ -0,0 +1,27 @@ +package rawpacket + +import ( + "net" + "net/netip" + + "errors" +) + +// The returned addresses are v4-unmapped and share the same family. +func tcpEndpoints(conn net.Conn) (*net.TCPConn, netip.AddrPort, netip.AddrPort, error) { + tcpConn, isTCP := conn.(*net.TCPConn) + if !isTCP { + return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("rawpacket: underlying conn is not *net.TCPConn") + } + local := tcpConn.LocalAddr().(*net.TCPAddr).AddrPort() + remote := tcpConn.RemoteAddr().(*net.TCPAddr).AddrPort() + if !local.IsValid() || !remote.IsValid() { + return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("rawpacket: invalid conn address") + } + local = netip.AddrPortFrom(local.Addr().Unmap(), local.Port()) + remote = netip.AddrPortFrom(remote.Addr().Unmap(), remote.Port()) + if local.Addr().Is4() != remote.Addr().Is4() { + return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("rawpacket: local/remote address family mismatch") + } + return tcpConn, local, remote, nil +} diff --git a/transport/internet/finalmask/rawpacket/packet.go b/transport/internet/finalmask/rawpacket/packet.go new file mode 100644 index 000000000000..914dc04760b8 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/packet.go @@ -0,0 +1,163 @@ +package rawpacket + +import ( + "encoding/binary" + "net/netip" + + "fmt" +) + +const ( + defaultWindowSize uint16 = 0xFFFF + tcpHeaderLen = TCPMinimumSize + + tcpOptionMD5Signature = 19 + tcpOptionMD5SignatureLength = 18 + tcpTimestampBackdate = 3600000 +) + +type spoofPacketInfo struct { + seqNum uint32 + ackNum uint32 + corrupt bool + options []byte +} + +func buildTCPSegment( + src netip.AddrPort, + dst netip.AddrPort, + packetInfo spoofPacketInfo, + payload []byte, + ttl uint8, +) []byte { + if src.Addr().Is4() != dst.Addr().Is4() { + panic("rawpacket: mixed IPv4/IPv6 address family") + } + var ( + frame []byte + ipHeaderLen int + ) + ipPayloadLen := tcpHeaderLen + len(packetInfo.options) + len(payload) + if src.Addr().Is4() { + ipHeaderLen = IPv4MinimumSize + frame = make([]byte, ipHeaderLen+ipPayloadLen) + ip := IPv4(frame[:ipHeaderLen]) + ip.Encode(uint16(len(frame)), 0, ttl, TCPProtocolNumber, src.Addr(), dst.Addr()) + } else { + ipHeaderLen = IPv6MinimumSize + frame = make([]byte, ipHeaderLen+ipPayloadLen) + ip := IPv6(frame[:ipHeaderLen]) + ip.Encode(uint16(ipPayloadLen), TCPProtocolNumber, ttl, src.Addr(), dst.Addr()) + } + encodeTCP(frame, ipHeaderLen, src, dst, packetInfo, payload) + return frame +} + +func encodeTCP(frame []byte, ipHeaderLen int, src, dst netip.AddrPort, packetInfo spoofPacketInfo, payload []byte) { + tcp := TCP(frame[ipHeaderLen:]) + copy(frame[ipHeaderLen+tcpHeaderLen:], packetInfo.options) + optionsLen := len(packetInfo.options) + copy(frame[ipHeaderLen+tcpHeaderLen+optionsLen:], payload) + tcp.Encode(src.Port(), dst.Port(), packetInfo.seqNum, packetInfo.ackNum, uint8(tcpHeaderLen+optionsLen), TCPFlagAck|TCPFlagPsh, defaultWindowSize) + applyTCPChecksum(tcp, src.Addr(), dst.Addr(), payload, packetInfo.corrupt) +} + +func buildSpoofFrame(method Method, src, dst netip.AddrPort, sendNext, receiveNext, timestamp uint32, tcpOptions, payload []byte, ttl uint8) ([]byte, error) { + packetInfo, err := resolveSpoofPacketInfo(method, sendNext, receiveNext, timestamp, tcpOptions, payload) + if err != nil { + return nil, err + } + return buildTCPSegment(src, dst, packetInfo, payload, ttl), nil +} + +// buildSpoofTCPSegment returns a TCP segment without an IP header, for +// platforms where the kernel synthesises the IP header (darwin IPv6). +func buildSpoofTCPSegment(method Method, src, dst netip.AddrPort, sendNext, receiveNext, timestamp uint32, payload []byte) ([]byte, error) { + packetInfo, err := resolveSpoofPacketInfo(method, sendNext, receiveNext, timestamp, nil, payload) + if err != nil { + return nil, err + } + segment := make([]byte, tcpHeaderLen+len(packetInfo.options)+len(payload)) + encodeTCP(segment, 0, src, dst, packetInfo, payload) + return segment, nil +} + +func resolveSpoofPacketInfo(method Method, sendNext, receiveNext, timestamp uint32, tcpOptions, payload []byte) (spoofPacketInfo, error) { + packetInfo := spoofPacketInfo{seqNum: sendNext, ackNum: receiveNext} + switch method { + case MethodWrongSequence: + packetInfo.seqNum = sendNext - uint32(len(payload)) + case MethodWrongChecksum: + packetInfo.corrupt = true + case MethodWrongAcknowledgment: + packetInfo.ackNum = receiveNext - uint32(defaultWindowSize/2) + case MethodWrongMD5Sig: + packetInfo.options = buildMD5SignatureOptions() + case MethodWrongTimestamp: + packetInfo.options = buildWrongTimestampOptions(timestamp, tcpOptions) + default: + return packetInfo, fmt.Errorf("rawpacket: unknown method %v", method) + } + return packetInfo, nil +} + +func buildMD5SignatureOptions() []byte { + options := make([]byte, tcpOptionMD5SignatureLength+2) + options[0] = tcpOptionMD5Signature + options[1] = tcpOptionMD5SignatureLength + return options +} + +func buildWrongTimestampOptions(timestamp uint32, tcpOptions []byte) []byte { + spoofedTimestamp := timestamp + if spoofedTimestamp > tcpTimestampBackdate { + spoofedTimestamp -= tcpTimestampBackdate + } else { + spoofedTimestamp = 0 + } + if rewriteTCPOptionTimestamp(tcpOptions, spoofedTimestamp) { + return tcpOptions + } + options := make([]byte, TCPOptionTSLength+2) + EncodeTSOption(spoofedTimestamp, 0, options) + return options +} + +// rewriteTCPOptionTimestamp finds the TS option in tcpOptions and writes +// timestamp into its TSVal field in place. The caller must own tcpOptions +// (parseTCPPacket already returns a private copy on Windows). +func rewriteTCPOptionTimestamp(tcpOptions []byte, timestamp uint32) bool { + for i := 0; i < len(tcpOptions); { + switch tcpOptions[i] { + case TCPOptionEOL: + return false + case TCPOptionNOP: + i++ + continue + } + if i+1 >= len(tcpOptions) { + return false + } + optionLen := int(tcpOptions[i+1]) + if optionLen < 2 || i+optionLen > len(tcpOptions) { + return false + } + if tcpOptions[i] == TCPOptionTS && optionLen == TCPOptionTSLength { + binary.BigEndian.PutUint32(tcpOptions[i+2:], timestamp) + return true + } + i += optionLen + } + return false +} + +func applyTCPChecksum(tcp TCP, srcAddr, dstAddr netip.Addr, payload []byte, corrupt bool) { + tcpLen := int(tcp.DataOffset()) + len(payload) + pseudo := PseudoHeaderChecksum(TCPProtocolNumber, srcAddr.AsSlice(), dstAddr.AsSlice(), uint16(tcpLen)) + payloadChecksum := Checksum(payload, 0) + tcpChecksum := ^tcp.CalculateChecksum(CombineChecksum(pseudo, payloadChecksum)) + if corrupt { + tcpChecksum ^= 0xFFFF + } + tcp.SetChecksum(tcpChecksum) +} diff --git a/transport/internet/finalmask/rawpacket/raw_darwin.go b/transport/internet/finalmask/rawpacket/raw_darwin.go new file mode 100644 index 000000000000..1b2335565ae2 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/raw_darwin.go @@ -0,0 +1,200 @@ +package rawpacket + +import ( + "encoding/binary" + "net" + "net/netip" + "strconv" + "strings" + "sync" + "syscall" + + "errors" + "fmt" + + "golang.org/x/sys/unix" +) + +const PlatformSupported = true + +// Offsets into xinpcb_n within each net.inet.tcp.pcblist_n record, identical +// to the values used by common/process/searcher_darwin_shared.go. +const ( + darwinXinpgenSize = 24 + darwinXsocketOffset = 104 + darwinXinpcbForeignPort = 16 + darwinXinpcbLocalPort = 18 + darwinXinpcbVFlag = 44 + darwinXinpcbForeignAddr = 48 + darwinXinpcbLocalAddr = 64 + darwinXinpcbIPv4Offset = 12 + + darwinTCPExtraSize = 208 + + darwinXtcpcbSndNxtOffset = 56 + darwinXtcpcbRcvNxtOffset = 80 +) + +// darwinStructSize returns the size of xinpcb_n for the running Darwin kernel. +// Darwin 22 (macOS 13 Ventura) grew the struct from 384 to 408 bytes; there is +// no ABI-stable way to read it, so we key off the kernel version. +var darwinStructSize = sync.OnceValues(func() (int, error) { + value, err := syscall.Sysctl("kern.osrelease") + if err != nil { + return 0, fmt.Errorf("sysctl kern.osrelease: %w", err) + } + major, _, ok := strings.Cut(value, ".") + if !ok { + return 0, fmt.Errorf("unexpected kern.osrelease format: %s", value) + } + n, err := strconv.ParseInt(major, 10, 64) + if err != nil { + return 0, fmt.Errorf("parse kern.osrelease major version: : %w", err) + } + if n >= 22 { + return 408, nil + } + return 384, nil +}) + +type darwinSpoofer struct { + method Method + src netip.AddrPort + dst netip.AddrPort + rawFD int + rawSockAddr unix.Sockaddr + sendNext uint32 + receiveNext uint32 + ttl uint8 +} + +func newRawSpoofer(conn net.Conn, method Method, ttl uint8) (rawSpoofer, error) { + if method == MethodWrongTimestamp { + return nil, errors.New("rawpacket: wrong-timestamp is not supported on macOS") + } + _, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + fd, sockaddr, err := openDarwinRawSocket(src, dst) + if err != nil { + return nil, err + } + sendNext, receiveNext, err := readDarwinTCPSequence(src, dst) + if err != nil { + unix.Close(fd) + return nil, err + } + return &darwinSpoofer{ + method: method, + src: src, + dst: dst, + rawFD: fd, + rawSockAddr: sockaddr, + sendNext: sendNext, + receiveNext: receiveNext, + ttl: ttl, + }, nil +} + +// readDarwinTCPSequence scans net.inet.tcp.pcblist_n for the PCB that matches +// src -> dst and returns (snd_nxt, rcv_nxt). These live in xtcpcb_n at the end +// of each record; see darwin-xnu bsd/netinet/in_pcblist.c:get_pcblist_n. +func readDarwinTCPSequence(src, dst netip.AddrPort) (uint32, uint32, error) { + buffer, err := unix.SysctlRaw("net.inet.tcp.pcblist_n") + if err != nil { + return 0, 0, fmt.Errorf("sysctl net.inet.tcp.pcblist_n: %w", err) + } + structSize, err := darwinStructSize() + if err != nil { + return 0, 0, err + } + itemSize := structSize + darwinTCPExtraSize + for i := darwinXinpgenSize; i+itemSize <= len(buffer); i += itemSize { + inpcb := buffer[i : i+darwinXsocketOffset] + xtcpcb := buffer[i+structSize : i+itemSize] + localPort := binary.BigEndian.Uint16(inpcb[darwinXinpcbLocalPort : darwinXinpcbLocalPort+2]) + remotePort := binary.BigEndian.Uint16(inpcb[darwinXinpcbForeignPort : darwinXinpcbForeignPort+2]) + if localPort != src.Port() || remotePort != dst.Port() { + continue + } + versionFlag := inpcb[darwinXinpcbVFlag] + var localAddr, remoteAddr netip.Addr + switch { + case versionFlag&0x1 != 0: + localAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset : darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset+4])) + remoteAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset : darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset+4])) + case versionFlag&0x2 != 0: + localAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbLocalAddr : darwinXinpcbLocalAddr+16])) + remoteAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbForeignAddr : darwinXinpcbForeignAddr+16])) + default: + continue + } + if localAddr.Unmap() != src.Addr() || remoteAddr.Unmap() != dst.Addr() { + continue + } + sendNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbSndNxtOffset : darwinXtcpcbSndNxtOffset+4]) + receiveNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbRcvNxtOffset : darwinXtcpcbRcvNxtOffset+4]) + return sendNext, receiveNext, nil + } + return 0, 0, fmt.Errorf("rawpacket: connection %v->%v not found in pcblist_n", src, dst) +} + +func openDarwinRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) { + if dst.Addr().Is4() { + return openIPv4RawSocket(dst) + } + // macOS does not accept IPV6_HDRINCL on AF_INET6 SOCK_RAW IPPROTO_TCP + // sockets, so the kernel builds the IPv6 header itself. Bind to the real + // connection's source address so in6_selectsrc returns it, and rely on + // in6p_cksum defaulting to -1 so the user-supplied TCP checksum is + // preserved (including deliberately corrupted ones). + fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, fmt.Errorf("open AF_INET6 SOCK_RAW: %w", err) + } + err = unix.Bind(fd, &unix.SockaddrInet6{Addr: src.Addr().As16()}) + if err != nil { + unix.Close(fd) + return -1, nil, fmt.Errorf("bind AF_INET6 SOCK_RAW: %w", err) + } + sockaddr := &unix.SockaddrInet6{Port: int(dst.Port()), Addr: dst.Addr().As16()} + return fd, sockaddr, nil +} + +func (s *darwinSpoofer) Inject(payload []byte) error { + if !s.src.Addr().Is4() { + segment, err := buildSpoofTCPSegment(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, payload) + if err != nil { + return err + } + err = unix.Sendto(s.rawFD, segment, 0, s.rawSockAddr) + if err != nil { + return fmt.Errorf("sendto raw socket: %w", err) + } + return nil + } + frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, nil, payload, s.ttl) + if err != nil { + return err + } + // Darwin inherits the historical BSD quirk: with IP_HDRINCL the kernel + // expects ip_len and ip_off in host byte order, not network byte order. + ip := IPv4(frame) + binary.NativeEndian.PutUint16(ip[2:4], ip.TotalLength()) + binary.NativeEndian.PutUint16(ip[6:8], uint16(ip.Flags())<<13|ip.FragmentOffset()) + err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) + if err != nil { + return fmt.Errorf("sendto raw socket: %w", err) + } + return nil +} + +func (s *darwinSpoofer) Close() error { + if s.rawFD < 0 { + return nil + } + err := unix.Close(s.rawFD) + s.rawFD = -1 + return err +} diff --git a/transport/internet/finalmask/rawpacket/raw_freebsd.go b/transport/internet/finalmask/rawpacket/raw_freebsd.go new file mode 100644 index 000000000000..b3d2e13492a8 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/raw_freebsd.go @@ -0,0 +1,174 @@ +package rawpacket + +import ( + "encoding/binary" + "errors" + "fmt" + "net" + "net/netip" + "syscall" + "unsafe" + + "golang.org/x/sys/unix" +) + +const PlatformSupported = true + +// FreeBSD tcp_info offsets for snd_nxt and rcv_nxt. +// Derived from FreeBSD sys/netinet/tcp.h struct tcp_info layout. +// +// struct tcp_info { +// u8 state, __ca, __retrans, __probes, __backoff, opts, wscale = 8 bytes (with pad) +// u32 rto, __ato, snd_mss, rcv_mss = 16 bytes (offset 8) +// u32 __unacked, __sacked, __lost, __retrans, __fackets = 20 bytes (offset 24) +// u32 __last_data_sent, __last_ack_sent, last_data_recv, __last_ack_recv = 16 bytes (offset 44) +// u32 __pmtu, __rcv_ssthresh, rtt, rttvar, snd_ssthresh, snd_cwnd, __advmss, __reordering = 32 bytes (offset 60) +// u32 __rcv_rtt, rcv_space = 8 bytes (offset 92) +// u32 snd_wnd, snd_bwnd = 8 bytes (offset 100) +// u32 snd_nxt, rcv_nxt = 8 bytes (offset 108) +// ... remaining fields +// } +const ( + freebsdTCPInfoSndNxtOffset = 108 + freebsdTCPInfoRcvNxtOffset = 112 + freebsdTCPInfoMinSize = 116 // must read at least through rcv_nxt +) + +type freebsdSpoofer struct { + method Method + src netip.AddrPort + dst netip.AddrPort + rawFD int + rawSockAddr unix.Sockaddr + sendNext uint32 + receiveNext uint32 + ttl uint8 +} + +func newRawSpoofer(conn net.Conn, method Method, ttl uint8) (rawSpoofer, error) { + if method == MethodWrongTimestamp { + return nil, errors.New("rawpacket: wrong-timestamp is not supported on FreeBSD") + } + tcpConn, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + fd, sockaddr, err := openFreeBSDRawSocket(src, dst) + if err != nil { + return nil, err + } + sendNext, receiveNext, err := readFreeBSDTCPSequence(tcpConn) + if err != nil { + unix.Close(fd) + return nil, err + } + return &freebsdSpoofer{ + method: method, + src: src, + dst: dst, + rawFD: fd, + rawSockAddr: sockaddr, + sendNext: sendNext, + receiveNext: receiveNext, + ttl: ttl, + }, nil +} + +// readFreeBSDTCPSequence retrieves snd_nxt and rcv_nxt via TCP_INFO getsockopt. +func readFreeBSDTCPSequence(conn *net.TCPConn) (uint32, uint32, error) { + raw, err := conn.SyscallConn() + if err != nil { + return 0, 0, fmt.Errorf("rawpacket: SyscallConn: %w", err) + } + var sendNext, receiveNext uint32 + var sockErr error + err = raw.Control(func(fd uintptr) { + buf := make([]byte, 256) // generous buffer for tcp_info + bufLen := uint32(len(buf)) + _, _, errno := syscall.Syscall6( + syscall.SYS_GETSOCKOPT, + fd, + uintptr(syscall.IPPROTO_TCP), + uintptr(0x20), // TCP_INFO = 0x20 + uintptr(unsafe.Pointer(&buf[0])), + uintptr(unsafe.Pointer(&bufLen)), + 0, + ) + if errno != 0 { + sockErr = fmt.Errorf("rawpacket: getsockopt TCP_INFO: %w", errno) + return + } + if bufLen < freebsdTCPInfoMinSize { + sockErr = fmt.Errorf("rawpacket: TCP_INFO too short: %d < %d", bufLen, freebsdTCPInfoMinSize) + return + } + sendNext = binary.NativeEndian.Uint32(buf[freebsdTCPInfoSndNxtOffset : freebsdTCPInfoSndNxtOffset+4]) + receiveNext = binary.NativeEndian.Uint32(buf[freebsdTCPInfoRcvNxtOffset : freebsdTCPInfoRcvNxtOffset+4]) + }) + if err != nil { + return 0, 0, err + } + if sockErr != nil { + return 0, 0, sockErr + } + return sendNext, receiveNext, nil +} + +func openFreeBSDRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) { + if dst.Addr().Is4() { + return openIPv4RawSocket(dst) + } + // FreeBSD, like macOS, does not support IPV6_HDRINCL on SOCK_RAW/IPPROTO_TCP. + // The kernel constructs the IPv6 header. Bind to the source address + // and let the kernel fill in the IPv6 header automatically. + fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, fmt.Errorf("rawpacket: open AF_INET6 SOCK_RAW: %w", err) + } + err = unix.Bind(fd, &unix.SockaddrInet6{Addr: src.Addr().As16()}) + if err != nil { + unix.Close(fd) + return -1, nil, fmt.Errorf("rawpacket: bind AF_INET6 SOCK_RAW: %w", err) + } + sockaddr := &unix.SockaddrInet6{Port: int(dst.Port()), Addr: dst.Addr().As16()} + return fd, sockaddr, nil +} + +func (s *freebsdSpoofer) Inject(payload []byte) error { + if !s.src.Addr().Is4() { + // IPv6: kernel builds the IP header, we supply TCP segment only. + segment, err := buildSpoofTCPSegment(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, payload) + if err != nil { + return err + } + err = unix.Sendto(s.rawFD, segment, 0, s.rawSockAddr) + if err != nil { + return fmt.Errorf("rawpacket: sendto raw socket: %w", err) + } + return nil + } + // IPv4: we build the full IP+TCP frame with IP_HDRINCL. + frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, nil, payload, s.ttl) + if err != nil { + return err + } + // FreeBSD inherits the historical BSD quirk: with IP_HDRINCL the kernel + // expects ip_len and ip_off in host byte order, not network byte order. + ip := IPv4(frame) + binary.NativeEndian.PutUint16(ip[2:4], ip.TotalLength()) + binary.NativeEndian.PutUint16(ip[6:8], uint16(ip.Flags())<<13|ip.FragmentOffset()) + err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) + if err != nil { + return fmt.Errorf("rawpacket: sendto raw socket: %w", err) + } + return nil +} + +func (s *freebsdSpoofer) Close() error { + if s.rawFD < 0 { + return nil + } + err := unix.Close(s.rawFD) + s.rawFD = -1 + return err +} diff --git a/transport/internet/finalmask/rawpacket/raw_linux.go b/transport/internet/finalmask/rawpacket/raw_linux.go new file mode 100644 index 000000000000..1de3ff862839 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/raw_linux.go @@ -0,0 +1,168 @@ +package rawpacket + +import ( + "fmt" + "net" + "net/netip" + + "golang.org/x/sys/unix" +) + +const PlatformSupported = true + +const ( + // Values of enum { TCP_NO_QUEUE, TCP_RECV_QUEUE, TCP_SEND_QUEUE } from + // include/net/tcp.h; not exported by golang.org/x/sys/unix. + tcpRecvQueue = 1 + tcpSendQueue = 2 +) + +type linuxSpoofer struct { + method Method + src netip.AddrPort + dst netip.AddrPort + rawFD int + rawSockAddr unix.Sockaddr + sendNext uint32 + receiveNext uint32 + timestamp uint32 + ttl uint8 +} + +func newRawSpoofer(conn net.Conn, method Method, ttl uint8) (rawSpoofer, error) { + tcpConn, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + fd, sockaddr, err := openLinuxRawSocket(dst) + if err != nil { + return nil, err + } + spoofer := &linuxSpoofer{ + method: method, + src: src, + dst: dst, + rawFD: fd, + rawSockAddr: sockaddr, + ttl: ttl, + } + err = spoofer.loadSequenceNumbers(tcpConn) + if err != nil { + unix.Close(fd) + return nil, err + } + return spoofer, nil +} + +func openLinuxRawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) { + if dst.Addr().Is4() { + return openIPv4RawSocket(dst) + } + fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, fmt.Errorf("open AF_INET6 SOCK_RAW: %w", err) + } + err = unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_HDRINCL, 1) + if err != nil { + unix.Close(fd) + return -1, nil, fmt.Errorf("set IPV6_HDRINCL: %w", err) + } + // Linux raw IPv6 sockets interpret sin6_port as a nexthdr protocol number + // (see raw(7)); any value other than 0 or the socket's IPPROTO_TCP causes + // sendto to fail with EINVAL. The destination is already encoded in the + // user-supplied IPv6 header under IPV6_HDRINCL. + sockaddr := &unix.SockaddrInet6{Addr: dst.Addr().As16()} + return fd, sockaddr, nil +} + +// loadSequenceNumbers puts the socket briefly into TCP_REPAIR mode to read +// snd_nxt and rcv_nxt from the kernel. TCP_REPAIR requires CAP_NET_ADMIN; +// callers must run as root or grant both CAP_NET_RAW and CAP_NET_ADMIN. +// +// If the TCP_REPAIR_OFF revert fails, the socket would stay in TCP_REPAIR +// state and subsequent Write() calls would silently buffer instead of sending. +// Surface that error so callers can abort. +func (s *linuxSpoofer) loadSequenceNumbers(tcpConn *net.TCPConn) error { + rawConn, err := tcpConn.SyscallConn() + if err != nil { + return err + } + var ctrlErr error + err = rawConn.Control(func(raw uintptr) { + fd := int(raw) + + if s.method == MethodWrongTimestamp { + timestamp, tsErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_TIMESTAMP) + if tsErr != nil { + ctrlErr = fmt.Errorf("rawpacket: read timestamp: %w", tsErr) + return + } + s.timestamp = uint32(timestamp) + } + + ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR, unix.TCP_REPAIR_ON) + if ctrlErr != nil { + ctrlErr = fmt.Errorf("rawpacket: enter TCP_REPAIR (need CAP_NET_ADMIN): %w", ctrlErr) + return + } + defer func() { + offErr := unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR, unix.TCP_REPAIR_OFF) + if offErr != nil { + offErr = fmt.Errorf("rawpacket: leave TCP_REPAIR: %w", offErr) + if ctrlErr == nil { + ctrlErr = offErr + } else { + ctrlErr = fmt.Errorf("%v; also %w", ctrlErr, offErr) + } + } + }() + + ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR_QUEUE, tcpSendQueue) + if ctrlErr != nil { + ctrlErr = fmt.Errorf("rawpacket: select TCP_SEND_QUEUE: %w", ctrlErr) + return + } + sendSequence, seqErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_QUEUE_SEQ) + if seqErr != nil { + ctrlErr = fmt.Errorf("rawpacket: read send queue sequence: %w", seqErr) + return + } + ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR_QUEUE, tcpRecvQueue) + if ctrlErr != nil { + ctrlErr = fmt.Errorf("rawpacket: select TCP_RECV_QUEUE: %w", ctrlErr) + return + } + receiveSequence, seqErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_QUEUE_SEQ) + if seqErr != nil { + ctrlErr = fmt.Errorf("rawpacket: read recv queue sequence: %w", seqErr) + return + } + s.sendNext = uint32(sendSequence) + s.receiveNext = uint32(receiveSequence) + }) + if err != nil { + return err + } + return ctrlErr +} + +func (s *linuxSpoofer) Inject(payload []byte) error { + frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, s.timestamp, nil, payload, s.ttl) + if err != nil { + return err + } + err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) + if err != nil { + return fmt.Errorf("sendto raw socket: %w", err) + } + return nil +} + +func (s *linuxSpoofer) Close() error { + if s.rawFD < 0 { + return nil + } + err := unix.Close(s.rawFD) + s.rawFD = -1 + return err +} diff --git a/transport/internet/finalmask/rawpacket/raw_stub.go b/transport/internet/finalmask/rawpacket/raw_stub.go new file mode 100644 index 000000000000..c06a40f48bb2 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/raw_stub.go @@ -0,0 +1,15 @@ +//go:build !linux && !darwin && !freebsd && !(windows && (amd64 || 386)) + +package rawpacket + +import ( + "net" + + "errors" +) + +const PlatformSupported = false + +func newRawSpoofer(conn net.Conn, method Method, ttl uint8) (rawSpoofer, error) { + return nil, errors.New("rawpacket: unsupported platform") +} diff --git a/transport/internet/finalmask/rawpacket/raw_unix.go b/transport/internet/finalmask/rawpacket/raw_unix.go new file mode 100644 index 000000000000..bccd0fefe0de --- /dev/null +++ b/transport/internet/finalmask/rawpacket/raw_unix.go @@ -0,0 +1,25 @@ +//go:build linux || darwin || freebsd + +package rawpacket + +import ( + "fmt" + "net/netip" + + "golang.org/x/sys/unix" +) + +func openIPv4RawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) { + fd, err := unix.Socket(unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, fmt.Errorf("open AF_INET SOCK_RAW: %w", err) + } + err = unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_HDRINCL, 1) + if err != nil { + unix.Close(fd) + return -1, nil, fmt.Errorf("set IP_HDRINCL: %w", err) + } + sockaddr := &unix.SockaddrInet4{Port: int(dst.Port())} + sockaddr.Addr = dst.Addr().As4() + return fd, sockaddr, nil +} diff --git a/transport/internet/finalmask/rawpacket/raw_windows.go b/transport/internet/finalmask/rawpacket/raw_windows.go new file mode 100644 index 000000000000..acfed30ff0c8 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/raw_windows.go @@ -0,0 +1,236 @@ +//go:build windows && (amd64 || 386) + +package rawpacket + +import ( + "errors" + "net" + "net/netip" + "slices" + "sync" + "sync/atomic" + "time" + + "github.com/xtls/xray-core/transport/internet/finalmask/rawpacket/windivert" + "golang.org/x/sys/windows" +) + +const PlatformSupported = true + +// closeGracePeriod caps how long Close() waits for the divert goroutine to +// observe the kernel-emitted real ClientHello and perform the reorder +// (fake → real). In practice this completes in microseconds; the cap +// bounds the pathological case where the kernel buffers the packet. +const closeGracePeriod = 2 * time.Second + +// windowsSpoofer uses a single WinDivert handle for both capture and +// injection. Sequential Send() calls on one handle traverse one driver queue, +// so the fake provably precedes the released real on the wire — a guarantee +// two separate handles cannot make because cross-handle order depends on the +// scheduler. +type windowsSpoofer struct { + method Method + src, dst netip.AddrPort + divertH *windivert.Handle + ttl uint8 + + fakeReady chan []byte // buffered(1): staged by Inject + done chan struct{} // closed by run() on exit + closeOnce sync.Once + runErr atomic.Pointer[error] +} + +func newRawSpoofer(conn net.Conn, method Method, ttl uint8) (rawSpoofer, error) { + _, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + filter, err := windivert.OutboundTCP(src, dst) + if err != nil { + return nil, err + } + divertH, err := windivert.Open(filter, windivert.LayerNetwork, 0, 0) + if err != nil { + return nil, err + } + s := &windowsSpoofer{ + method: method, + src: src, + dst: dst, + divertH: divertH, + ttl: ttl, + fakeReady: make(chan []byte, 1), + done: make(chan struct{}), + } + go s.run() + return s, nil +} + +func (s *windowsSpoofer) Inject(payload []byte) error { + select { + case s.fakeReady <- payload: + return nil + case <-s.done: + if p := s.runErr.Load(); p != nil { + return *p + } + return errors.New("rawpacket: spoofer closed before Inject") + } +} + +func (s *windowsSpoofer) Close() error { + s.closeOnce.Do(func() { + // Give run() a grace window to finish handling the real packet. + select { + case <-s.done: + case <-time.After(closeGracePeriod): + // Force Recv() to return by closing the divert handle. + s.divertH.Close() + <-s.done + } + }) + if p := s.runErr.Load(); p != nil { + return *p + } + return nil +} + +func (s *windowsSpoofer) recordErr(err error) { s.runErr.Store(&err) } + +func (s *windowsSpoofer) run() { + defer close(s.done) + defer s.divertH.Close() + + buf := make([]byte, windivert.MTUMax) + for { + n, addr, err := s.divertH.Recv(buf) + if err != nil { + if errors.Is(err, windows.ERROR_OPERATION_ABORTED) || + errors.Is(err, windows.ERROR_NO_DATA) { + return + } + s.recordErr(err) + return + } + pkt := buf[:n] + seq, ack, tcpOptions, payloadLen, ok := parseTCPPacket(pkt, addr.IPv6()) + if !ok { + // Our filter is OutboundTCP(src, dst); a non-TCP or truncated + // match means driver state is suspect. Re-inject so the kernel + // still sees the byte stream, then abort — continuing would risk + // reordering against an unknown reference point. + _, sendErr := s.divertH.Send(pkt, &addr) + if sendErr != nil { + s.recordErr(sendErr) + return + } + s.recordErr(errors.New("windivert received malformed packet matching spoof filter")) + return + } + if payloadLen == 0 { + // Handshake ACK, keepalive, FIN — pass through unchanged. + _, err := s.divertH.Send(pkt, &addr) + if err != nil { + s.recordErr(err) + return + } + continue + } + + // Non-empty outbound TCP payload = the real ClientHello. + var fake []byte + select { + case fake = <-s.fakeReady: + default: + // Inject() not yet called — pass through and keep observing. + _, err := s.divertH.Send(pkt, &addr) + if err != nil { + s.recordErr(err) + return + } + continue + } + + var timestamp uint32 + if tsVal, hasTS := ParseTCPOptions(tcpOptions); hasTS { + timestamp = tsVal + } + frame, err := buildSpoofFrame(s.method, s.src, s.dst, seq, ack, timestamp, tcpOptions, fake, s.ttl) + if err != nil { + s.recordErr(err) + return + } + fakeAddr := addr // inherit Outbound, IfIdx + // buildSpoofFrame emits ready-to-wire bytes. The driver recomputes + // checksums on Send when TCPChecksum/IPChecksum are 0 — which would + // overwrite the intentionally corrupt checksum in WrongChecksum mode. + // Force both to 1 to keep our bytes intact. + fakeAddr.SetIPChecksum(true) + fakeAddr.SetTCPChecksum(true) + _, err = s.divertH.Send(frame, &fakeAddr) + if err != nil { + s.recordErr(err) + return + } + _, err = s.divertH.Send(pkt, &addr) + if err != nil { + s.recordErr(err) + return + } + return // single-shot reorder complete + } +} + +func parseTCPPacket(pkt []byte, isV6 bool) (seq, ack uint32, options []byte, payloadLen int, ok bool) { + if isV6 { + if len(pkt) < IPv6MinimumSize+TCPMinimumSize { + return 0, 0, nil, 0, false + } + ip := IPv6(pkt) + if ip.TransportProtocol() != TCPProtocolNumber { + return 0, 0, nil, 0, false + } + tcp := TCP(pkt[IPv6MinimumSize:]) + tcpHdr := int(tcp.DataOffset()) + if tcpHdr < TCPMinimumSize || IPv6MinimumSize+tcpHdr > len(pkt) { + return 0, 0, nil, 0, false + } + total := IPv6MinimumSize + int(ip.PayloadLength()) + if total == IPv6MinimumSize || total > len(pkt) { + total = len(pkt) + } + if total < IPv6MinimumSize+tcpHdr { + return 0, 0, nil, 0, false + } + return tcp.SequenceNumber(), tcp.AckNumber(), slices.Clone(tcp.Options()), + total - IPv6MinimumSize - tcpHdr, true + } + if len(pkt) < IPv4MinimumSize+TCPMinimumSize { + return 0, 0, nil, 0, false + } + ip := IPv4(pkt) + if ip.Protocol() != TCPProtocolNumber { + return 0, 0, nil, 0, false + } + ihl := int(ip.HeaderLength()) + // ihl+TCPMinimumSize guards the TCP-header field reads below; without + // this, an IPv4 packet with options (ihl>20) against a 40-byte buffer + // reads past the TCP slice when calling DataOffset. + if ihl < IPv4MinimumSize || ihl+TCPMinimumSize > len(pkt) { + return 0, 0, nil, 0, false + } + tcp := TCP(pkt[ihl:]) + tcpHdr := int(tcp.DataOffset()) + if tcpHdr < TCPMinimumSize || ihl+tcpHdr > len(pkt) { + return 0, 0, nil, 0, false + } + total := int(ip.TotalLength()) + if total == 0 || total > len(pkt) { + total = len(pkt) + } + if total < ihl+tcpHdr { + return 0, 0, nil, 0, false + } + return tcp.SequenceNumber(), tcp.AckNumber(), slices.Clone(tcp.Options()), + total - ihl - tcpHdr, true +} diff --git a/transport/internet/finalmask/rawpacket/tcpip.go b/transport/internet/finalmask/rawpacket/tcpip.go new file mode 100644 index 000000000000..8814422e35ed --- /dev/null +++ b/transport/internet/finalmask/rawpacket/tcpip.go @@ -0,0 +1,155 @@ +package rawpacket + +import ( + "encoding/binary" + "net/netip" +) + +const ( + IPv4MinimumSize = 20 + IPv6MinimumSize = 40 + TCPMinimumSize = 20 + TCPProtocolNumber = 6 + + TCPOptionEOL = 0 + TCPOptionNOP = 1 + TCPOptionTS = 8 + TCPOptionTSLength = 10 + + TCPFlagFin = 0x01 + TCPFlagSyn = 0x02 + TCPFlagRst = 0x04 + TCPFlagPsh = 0x08 + TCPFlagAck = 0x10 +) + +func Checksum(data []byte, initial uint16) uint16 { + var csum uint32 = uint32(initial) + for i := 0; i < len(data)-1; i += 2 { + csum += uint32(binary.BigEndian.Uint16(data[i:])) + } + if len(data)%2 == 1 { + csum += uint32(data[len(data)-1]) << 8 + } + for csum > 0xffff { + csum = (csum >> 16) + (csum & 0xffff) + } + return uint16(csum) +} + +func PseudoHeaderChecksum(protocol uint8, srcAddr, dstAddr []byte, totalLen uint16) uint16 { + var csum uint32 + for i := 0; i < len(srcAddr); i += 2 { + csum += uint32(binary.BigEndian.Uint16(srcAddr[i:])) + } + for i := 0; i < len(dstAddr); i += 2 { + csum += uint32(binary.BigEndian.Uint16(dstAddr[i:])) + } + csum += uint32(protocol) + csum += uint32(totalLen) + for csum > 0xffff { + csum = (csum >> 16) + (csum & 0xffff) + } + return uint16(csum) +} + +func CombineChecksum(c1, c2 uint16) uint16 { + csum := uint32(c1) + uint32(c2) + for csum > 0xffff { + csum = (csum >> 16) + (csum & 0xffff) + } + return uint16(csum) +} + +func EncodeTSOption(val uint32, ecr uint32, b []byte) { + b[0] = TCPOptionTS + b[1] = TCPOptionTSLength + binary.BigEndian.PutUint32(b[2:], val) + binary.BigEndian.PutUint32(b[6:], ecr) +} + +func ParseTCPOptions(b []byte) (tsVal uint32, hasTS bool) { + for i := 0; i < len(b); { + if b[i] == TCPOptionEOL { + break + } + if b[i] == TCPOptionNOP { + i++ + continue + } + if i+1 >= len(b) { + break + } + optLen := int(b[i+1]) + if optLen < 2 || i+optLen > len(b) { + break + } + if b[i] == TCPOptionTS && optLen == TCPOptionTSLength { + return binary.BigEndian.Uint32(b[i+2:]), true + } + i += optLen + } + return 0, false +} + +// IPv4 header representation +type IPv4 []byte + +func (b IPv4) TotalLength() uint16 { return binary.BigEndian.Uint16(b[2:]) } +func (b IPv4) Flags() uint8 { return uint8(binary.BigEndian.Uint16(b[6:]) >> 13) } +func (b IPv4) FragmentOffset() uint16 { return binary.BigEndian.Uint16(b[6:]) & 0x1fff } +func (b IPv4) Protocol() uint8 { return b[9] } +func (b IPv4) HeaderLength() uint8 { return (b[0] & 0x0f) * 4 } + +func (b IPv4) Encode(totalLength uint16, id uint16, ttl uint8, protocol uint8, src, dst netip.Addr) { + b[0] = (4 << 4) | 5 // IPv4, Header Length = 20 + b[1] = 0 // TOS + binary.BigEndian.PutUint16(b[2:], totalLength) + binary.BigEndian.PutUint16(b[4:], id) + binary.BigEndian.PutUint16(b[6:], 0) // Flags and Fragment Offset + b[8] = ttl + b[9] = protocol + b[10] = 0 // Checksum (0 for calculation) + copy(b[12:16], src.AsSlice()) + copy(b[16:20], dst.AsSlice()) + csum := Checksum(b[:20], 0) + binary.BigEndian.PutUint16(b[10:], ^csum) +} + +type IPv6 []byte + +func (b IPv6) PayloadLength() uint16 { return binary.BigEndian.Uint16(b[4:]) } +func (b IPv6) TransportProtocol() uint8 { return b[6] } + +func (b IPv6) Encode(payloadLength uint16, transportProtocol uint8, hopLimit uint8, src, dst netip.Addr) { + binary.BigEndian.PutUint32(b[0:], 6<<28) // Version 6, Traffic Class 0, Flow Label 0 + binary.BigEndian.PutUint16(b[4:], payloadLength) + b[6] = transportProtocol + b[7] = hopLimit + copy(b[8:24], src.AsSlice()) + copy(b[24:40], dst.AsSlice()) +} + +type TCP []byte + +func (b TCP) DataOffset() uint8 { return (b[12] >> 4) * 4 } +func (b TCP) SequenceNumber() uint32 { return binary.BigEndian.Uint32(b[4:]) } +func (b TCP) AckNumber() uint32 { return binary.BigEndian.Uint32(b[8:]) } +func (b TCP) Options() []byte { return b[TCPMinimumSize:b.DataOffset()] } +func (b TCP) SetChecksum(csum uint16) { binary.BigEndian.PutUint16(b[16:], csum) } + +func (b TCP) Encode(srcPort, dstPort uint16, seqNum, ackNum uint32, dataOffset uint8, flags uint8, windowSize uint16) { + binary.BigEndian.PutUint16(b[0:], srcPort) + binary.BigEndian.PutUint16(b[2:], dstPort) + binary.BigEndian.PutUint32(b[4:], seqNum) + binary.BigEndian.PutUint32(b[8:], ackNum) + b[12] = (dataOffset / 4) << 4 + b[13] = flags + binary.BigEndian.PutUint16(b[14:], windowSize) + b[16] = 0 // Checksum + binary.BigEndian.PutUint16(b[18:], 0) // Urgent pointer +} + +func (b TCP) CalculateChecksum(initial uint16) uint16 { + return Checksum(b, initial) +} diff --git a/transport/internet/finalmask/rawpacket/windivert/assets/LICENSE.txt b/transport/internet/finalmask/rawpacket/windivert/assets/LICENSE.txt new file mode 100644 index 000000000000..8489a8e773c3 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/windivert/assets/LICENSE.txt @@ -0,0 +1,1191 @@ +WinDivert is dual-licensed under your choice of the GNU Lesser General Public +License (LGPL) Version 3 or the GNU General Public License (GPL) Version 2. +Copies of the LGPLv3, GPLv3 and GPLv2 are provided below. + +============================================================================== + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + +============================================================================== + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + +============================================================================== + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + diff --git a/transport/internet/finalmask/rawpacket/windivert/assets/WinDivert32.sys b/transport/internet/finalmask/rawpacket/windivert/assets/WinDivert32.sys new file mode 100644 index 0000000000000000000000000000000000000000..d06738cbb78351cc57754fd484b77fac0df52cea GIT binary patch literal 79792 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%0DB0KXuLbP(03=gskd! zYT#yujVHpz6oQfBE9gdPp!8d4ClS}Gp?f8C{)M^E(!%=y;kv-5Su-K2AGE(_=|DKN z5G|Fw1H}lTBIus!pbJ0bvQLPdak-u4re=36H*|g&8dG&A%QdqFr?Xpm_=c+CfXZ^o zD3sE#suE!$g|jXcfSu%sCRXfY393bv)Ric%`w0qyzN_s)QZS;^Z}Nq1=ANf~q3f7G zG?WK^j`Po=M{NdlL|0M@0%SZ2YQ?+em6JV6|ll%+WKUh6(Qb z9H-h2z@i!}%7rL{D_AoHUQNViABc+t5iZFHdy&XD8;?)73FL;%vI*D0-2~U*un7*h z58;G}fz`La1)Z8F`bpID?caV|4#65N-+F6L9|r?$>ay!@Uo8O|eaQ5Uv*PdAK*>n&FPYc}r}< zJ*8+zxcP7^;C>DF8r)vE|AZSb*CyNyXMt6_5 z;Wog%2zRWPetR@=(rB&3%RUyKuunJ8}-VF>ot2_blEFZm;Hoc>fu0 z@*>~^Hw&%^Za&;YaB=$&&vqyMX;7ui;65n|@T2=vj@arY?-V z96^*S;U{6O0&g`3=_BVO#CK+$;qfnZ*s$~4wH4T4&wz8T$w=1hrRdus2*QHUdN`zq z{jE2Bw*QnKR`kqZWgV&#lxr5xCkVn#0yO56g^?(FT@YbxUx?dh=o6TKGMUC!CQlCI zwPMhcY|Qj)=^$cuK$PB6bF%q*FqpkPa+rk__t1m@;ahX`^GFIINWZ8@aQ|DgV2NONF0=T#C7WgO6 z6cSLOG(^Y_1t+z{ZEM?q1hQp#w&Df3ns_-s763m4Lm>z9p9A|Ugh%F-5bztL=o4S(7@-{hS1h%ez zQdd9GLwnUI0V{DK=)nVi5wFlHc%Y3CLd(fB9G(jDAS!e}P{Ae)Wu>}>3VDJkgbHvQ zk*a`Ec*Zg6?L@VlgY!%{`E5s9_{k9T>K*VP+^)VT8X*+H71pRCHa+|yvwjcmoq|di z+g*0k>_Xhr()~7XyM}|nfpop6q2R?vQju~>P59}nNI;`vUdg~N8&0>n(+S+F1e4$% zgclBtrr?!|g~wXTy5KA`T?-K!+p8(>p`Xg8YnfrJ6?W!m1S{!8Wy7^O;`Y3;I3kmG zEhNaiu{cwZcddcJSrLHPtwYOpjiETZi3vzFvb?b-yiItU@kV=%O~pG^+~H>jw^`4v4?#NrHg`2PP-O64_|% z*FS~w@&Bd)cw^7=ILaZ{2KO199%aSx7pXl1hs`q1<y>vZ>uf4_PQlJ2wzAqUTNu;Os7w$i4(S=}f9c0`0y?_1x3Nq(wZb18dRT zbgV^lEvl{M1T$qMlWlAO$o7oLCVy%MZ0;N|9OlM(?rz#I3cD>~cRKb=umcJPk;*SI zDLP3V&1Hl2nT3unpr65>CcT8R^Ser5)y8uYgbD%nkr=(pO5l`b3j)*^-yDh z7VIYU60#|Ei)s!hk2mcIzJkA4m18Hszda}y78vQz5jBadEhWmo02Q@{ z%YimT)ZA2QZz`E^X0TjCR+{@dGBtA?m#>#I`FHYiL%2n=UbG0BEl&(03NBIXQJ@t7 z${Uj!A_JxhWJw$SI@+|+m`KOe-q?Q!LC~=3%`y0%jFQO0Qq6#LIxoUz_BL}ZQFFp{ zF_M*kz~QAbtUb+iZUG0Bs}tBE{g>HiR@vAB%c#BC9Z%jx7L1;At5f*qWU1n^;;;q! zQmOJg%tWj;E~yAfoG;K#$t2SDzA;(Fzl6?_gS$=gr!be_;2MFZUKp^clb8((M=)rc zfxqa%52N60ei&disT|%nT@Q_AOtVB6I zmQdBM0nn(+&nwimljUE$J*V0Gl!BuXbiodp^2?sH2*FYVy-ohtBk;>_`snFt##6Hz z<_y6#!t13k)!|ykZ85ZyvU|s1Y{4|v40GcC{u2iMJ0yRzurg?+#SwqwL{~eUFUSiv%QeriBr@s9N6mJ@{DqF{=bOr@%CL_0 zdb$9f49~vI1T+s-QQDU#^JwPb73D?9o^Ha(L$#DW_zej7sp_)FK>j4z!;j6N6i)_4 zpdimk?g5QPlPAXA;Oi@W^kH!6ti?8ad!`!k=gZ zzW+L(VsQsiCcV*Co^jlRvLf`cLOW_L- znSTRVySE7+6nY)ag-#_6lz^Pqz<2Fmr@jT_!x3s2 zD-o05H1b=NMB^9D3Na)*8vlz(Jf}30Mr%pctBRD4JJrvtm-Q;O?jD-&)KXoysHJ-C z?M|fvUo7m=(f@J>VDOI4JTyuI_3JF?j~HSR3+*ym`tsxBzl-KAQXU5+4)SV3Vm~r0 z#BuU@%KB&?8noaib?vo)6+d2$SQS)c!c^94e}=l$v-F9>Yz>pHvzAJh+RQLtO$oU5 za!oD8Zc<|{G{GTm6ZA5;QK3n2CG#y=940u7vwKt!kK9e!5qXZj-9UkxASCnY5npeh z*akHgEv&(8pjcRAQC_e(Ew-LwH>$DbXzWHvbQbKnzaP<*Bv=o?l*cso7&n@_24z9~ zp0wPCdeuU?G5}gZnASDZRX2asWz}qQ4MGeQ{(Ecp>H>M5X1ebN`39i}#l_N%H^?m3 zmjeOMIp*Fh)-QrL_8r}S6C@p(R={Y4|ESZlj_;9`uD}W2sAe4m`;tHEBwspkqHAzu z`!m`%)S1O??|Abqz)a5bJ`>3EHq)&*!B0UMlv0sZvw_!zuEn9cG{j>}ciUvu)W=8B zH8rewW6%21-887L1vR<2mk4ki&|=l1t8XA};j445N2$O#qR`bly-KCqb9$9Zm*Mm* zlCHl21$&fA*Ym_mr3-dYs(bU;aQ!jY6fd{WdY^#^PCS}%$$+wo?ie5&?Q1Ru?*6*6 zjpP76G=4D(eV7lU@>fBq5~VT&5rJs>@@P8p^8-}s5u|z1_>EURhV?hx3veI9?X9#4 z7(ZA{#|95q*r#uy7Q>X#f$0Zx&vRtID%+1dH<7nZk(mm=iwSF_ zOnk2RM*%&$0|K`~9KDU>P8O}ayv$g6B&^`nTg7M^r=653kH*b3}`(E8EPC%B30W_ z)}o%%&`d~c(nu|Eq%KUScVRg6Ed;D?N8B3G?5OW^@o*}qZ{`D zz~J590aN;J)y|zS@lXsM=2N-x8th%cFb&7~{tgBKIspy`oW_sa(ZM#o%cL6Mo|TKc zLZivp*K0r96JS~t;27Er1DIkQjzp8CsThmv3IHA&5Ach+FX&(&ETZ6-zQEzW%@~Pw zU(o43(UVPV>Vi%5JOWSM#EjC#4~w1$$X{((kvC(8=qaWg)kfS#FdaVVpQQ^HEW_!W zyD8!-w$dmUr{NGUo5pW&v4PgJ+Iz&;SUjUdogYL%c;0w||9m~RX1Gp6vwCafpjeYlfQW%!Av`rMw zc(eQo8(Kb%wD~>KPN`{u9DI+)(ovJe zpuUY%uCw@JAl7xSQeNCE4Y5wSN%TPt;m55PqK6Lj^u&D&popGV5#;zIXdH;BcC3c!ib-}sEiQU+ zPZhzh)0SdQ;TC33b1H9O(@G`u-+>{bP`J!j=&DNbp*H!C)kwkqy|`@;ns_eHh9+Lb zJ?*lzn0t=PQZ0FMtI^ivQCIg;1NYZcRiq8@FJZiw0MbT;=uqNmgS>MU{A4&THNr!G zva|_LlHl!a9OW=BJx<$*ejn-|G3dql|H%}?K4#-L;VtdevG-s$D;+|o!o?cyv<@+V zd!~+O2i$+o{=j`G<)ZsG9K4Na4^~0Mj2^vdyb)IkmTbYPA(&q(BY*~TA1P7#r%~&| z1XBsSPnRf9pF<0E1P^g$L5FU|`YrHFU*NWrp8;X%3-G<&kX(Gh|3Ey;!ni>@M1~Hv zsOY&Cmm^AF=v=qZ!$yz6(eAW+0Db!yz70qlksBWBYXOP0C=^h;LXmO0MHqy0EP8bO!NwIX!VTXnDgOx;Cd z(^U&l=Vsgq3rNsj9CJCK6)RF!-iQ^_wRCbm*Ps_NaV_ZbOxzH0`xuwKL@7Zc#Dykl z|FtE`Gz2ha_(6Na51dLC!qUD>Am<~Hk<;I8H-OL(+4x0+L_GX)`DY@tj|>P&79`s< zpyH*qJZ#WBwD(Fk0pj!xKQ#(X_>LJSm(Goau8zw~hrvQg;qi1vOc8)ER*+C&MkYCEN+NeG|1|+vMDAFo8A!Whxshmx z-$F8R?HINKSOYP+@*yI+u%nGNkd_nt*m0Z{RI3sej=P@5ea!L#@N-pjsm*lS{)%)_ z=UR^6-Elo4T}9hKg2{UwOLa9ZYK^V6+;nbL8n?w zt6H$I4&?(76dl2#YVT}7i(`SdMStP9C_^=c_Q-%N-(B@fHQR7x3muhe(gnWI16bE@ z)Rb|Q@+Mv5f}$35sOP(3lMG9BC{WXIUzr&?RT@5+p^B7~&D5uH%il2d6WmU8IAp+6 z{be^5=KX+4(qOBG5wC(+{~-86v>HQDjd>AFv9}Bx8T`T#KeHq|(#85NkO;dNM5)pe zI-J}E;aIhp2TEYtVED7S$VpdHqp%Doa5XApFj32P>?RP^21JYdHP``F-?f+x!&rbV z{&CL!sg6()_o`+0JK->H#HK=Xdl!l+qi3;nEKDIW1qCJ%boamU7Ex*OVlP@qHAgU2 z%mqio@Bjg%f^?mMA?2I zXy^}Mpz16VA-XzL`P1s_F?`kWn@akHNGFA|^@0~Yq&4m&x;pC(M-ySQl8%UUv%*w1 zb1p8PVKe7vLqO_60?rm8M`ge4Vxzc@?&QJL1>rgSJipyI$ozx;^=g$z= z5IR-;vS${EYkv(dTRqx4b9B}$gL=gszpFs>yiX~~Ja!+vS=C7136FMfLA#WXTNz4l zE5p>(Tl1aLLfp!rDw}5YRy9L3Bc*hdLKi?8P9bd?{sAXX{RuvIwQEptF_H4KQ$Mp>78C<&^+2mQ(x@$)wZb2{YkK*L%8SGwzJbk3%-p!e1YLPPP(@QZ(Wi=C7fs^^!A>1i=9jwp&pC!N~ zC#df{+vGbE~8Q$JF-rULfSKSEq&x1W$)M9ju* z@jj8kw~zw47G!|55}Jt(3CTAV^%U!~k*Tj*4krM1GXMY#7dSw9lbnls=Y92}QyG3O zNd`DLHW9`_xN(rV^Q5*j{K7kn{}Vk7!i&?bzJ?;R{v1sGaF(>82&Wxa5+grg4eTNl>4Ex9&kR@VqGHPt;wtdQ#TvNmc#oR!^jR zD^`qRt24YaGr)h`mFjr^G3Q3^#o$ElGR}qY9azvLDD`<(u!gB>R-2q6=NjZR`91^7 zcSQ$D%gwlBB#I>&b36*szf?JjjryLwUgH!j?2>E(xjX7rSB`f)N8s|C3HJ(c#Z}M2 z>l)&9U<;fIs)}HQ7GDdY;cO~c+t~(GQ;9R*=bcKNdA7P0;=WhA|EEV~ zqO}+uyZom7L$o_E2NS0?7qINvCazsPuQng`*sP7rN0TsWOX$!cOxxWfu-)1Bb}w}q zT%OCfEo;uQso({u1LyElQDt!WmJK%SvuGESVVN<6?hzoZX9qZB!!5|fU{F7V-z0F* zd@hIIB#>hY4Tg+~f4D}cP`gQhZsImU1n$2Rd+5GkJLE0)7_JgX!_}TRn+cH}TlXeh zu%s?d5A|4?V)dw9#i>~_F(X-^;sk&35pSUpt2k(cVcFya`lJv|iqJb!Bi#z9@eFPo ztFqu$F!VyY2VQE!ecgHbK=zpxIH27z0(jv{f-~w}KWkE)f~)5>i>nfN&1N~3Aoe4+8_HhLV{rJ zFZV|tvc3@vu)dLbViP4A1#43*(X1wxnyoE#)Z4j0M3aSP$ZCxC$*W z#Q=n`Z6wt4@Agm81HDMa4OJu2jHl6zP>f3G4;A2;Bg~v2SEEfj!SzWF}6v5 zCx~c+42&=oPIOS;aXi~`L|jKw>tyOAqLd0R6e!kn-r{}QIMVD~-axu{I=|zKWE?go zYvyI7$s``jrO9RS(j+Uv*aKSv!$@5<7#sUw7%i3kM}Y>Z!|cXT3hWW=#!`G->Faa`q*Hwc)at)>s*jFgd3Ii_czV1zuOU3$M5`bBTfZf*yfZk$o9_q$ee>R2h zq*pVARJ_@S1-7B znHnm5v`G1&lLR1*zCBuqw@)6w!qi*bT8djZYG7c>o2f&qLB1JkJnYS=HDK4Z5aGY3 zrz$#l*K)#QYYzLH-am5U4}Aj%r~<}DpK6OHP%ce zrTCpJr}ETGB**($%`S)x*rJ8pz!yk8o28vA#j$n*640|i?F5zJS)~iNO)Y`WCgE72gD|7ISnt`hV|icTqS%q zGzE8D8ob*npR1e=pzct&(m*fV5QB_t0QHx;EH!33VggCzr`d3fJf0GnyPJTx`W4N_ zuVP;K8Y{`K`?(8wn`*6`My(8~iK|gV6W9RiVkn1R?uXa_>VI`vJXAV6kVGC7;FQOd z%Ht_bG2pmfgz9|~-SZPHTFd4U75TgZWI;&2%=w1-gnA|n{%T0_!5qxynqh(wf5WGy zkAiw$wfy_6KUXtx#XR~qt>jn9#7@x9e?ss7Y)HyMT)~9}%a_lD!<}SiYs3N> z;gi2oS=!%FX_fC40rA(iqJ8K(LVlMjB03P^wHxWTE-*Eq>x|z_^fjYSn7A6Y;%ode z4O^IAdtT?xVnbX=C4mxl<4J7DGK8>t?)AI zi#q)z&$>qPOXMhk?-TibbI~uU^dhJ7Z$pqdvnGMd?mQc)1TC{$MA z3+U-bl6H6@{HPT*OrJ!DxD;hcoFApdsbA?gCheAUwltk9N!wF#aK+j*Z4VL`PS<5| zjiYA-#;LS}FXu!LT|LX{YC*3e_Dh-b4U4bvp#CaAa;jt^9>)`uuzJ zj^J+O`t=hR@oV2)!7dl1@sXBJTuhzYRZ%WIDtt!xXWZ{aX_CUOQe0^5U2S0R%lNq) zZOS!eC2{QwY&3}0zaleKuVYsj20KN{Pm_tfI9s_grAYY+0^I(~%WnhKwEW)Pi|`%x z665YC@)qxkDx%0x+_5VwN%Tx4`0L1^5;ql)e7KG*F3Ey+T>(W-poUyGgU5BTWX!zy zO$ID`u*QQRLkm2d?1-!gM$o=qk#ZYw!rVB4Y!HhSGeVG69r%fs>JbnSXutM|6fjHn zX55H`%e)clGKp*EqYTdhs?!@u_`=;9G8ZZT4lgR2RHVE}ku@#eUs*s4t(tZFx1hg8 zw)l&8vG*ec#3_1HmeIY6C83@yn<_@uvmM-e_AKULbjtu1=rob?mc zq_1(wpNwd68&32Z=(6`)all#hj6|IDH7?X!3Xgg-p0=Y*xA~os_Kyq=LNspoLo|4U zz@xK264zwo0~|o9KZjVZ)Gz6AZUxo=V*PQXp+93!cM)>4t4V>s=O&uvzt2<;i;Lu^ zFx7*BV5Yws!LEPiEFQN9whh{Xwk3V5%Ko7w&}MwO2EX4V))yivI_Y=gr=NOE`ojo4 z5)K;fK)%|V6rFoA9na>|JXC|2@M-#IHK#j2EGoAwr3t?qJWreRLz4e$%=uW<%vMeU zKoQXe2;0r9zdes6Q=mYsr);Al1cg0>7J&2n*PB~qnVv3jrSUSQK(4x z0(|gVU;*Z<(C#NKv-s=}`<{2fsf*7_0Vl4)(|>@UCK>e+ERt}Uh_v5nLnosSg=G^= z3fxDs-7VtUy|k`(cSIOa8#*+25ZACQ)b=7w0v`&hp)B~F2|tL&J1S;Qtnx59;{sfa zFj562gIkdxf?e<)zsnr=@mnJ>uHwSY*#kzR%5ME0OkmVySr{Ck5LTcd()iIx3Xi7@ zsBw8GlEXvVP(X9|rsH^pZ))dHg+EX8rBk`2v47@Mk@L*7D~n{;cLt7k@6}&!zlX&Yz39gLuyu zO5uA1aStQzA)!)mfn@Vx&T~&6X~n`!VTLeE$iX)lD3|&A(m0E-43Hn?r7q^B2;T@} ztZ;`g7SR9i-~R*()LV7Jui+kmqra1QUxce1rxRwwO@JE;_j5STq@E8IAx4Z zXocGaM}OO%wek0*{afv?WkQkQ79K#)zF%0$dwCARF8rWzF8cl>`1cUToJ*J{I585& zaai=|3!X(-&fzVM!n44qLbL!s=<~gR3kBrHCJW8+Lueu?S1z;9NO&1>0@HX1ETxiH$a4BU~$7ZJSM?Tg;cG z+c<^kXx_*jh=*%~%X|ZQ;o9IjKE^#7$Xk0|EbqjfNQdiyv+cq?6L1}HEyzo^pKrN7 zmbc(d6O?%5GEP`u<6MT^T;Woo<{n#eZt?A1wvAhc6 zC;E{Nr@$5Lfgdij%_h)|>X|ph@^&EJwikH8b-+3I!4D@OFWt6Ydt)qb7vi07qf9tq zKk$Lu3TH!Jx@q1y1oat#`8N!aEP!-4ADp=f_`&(;jl8cR@0OvlybUO)6>j1?xQ_*{ z4KA}8@Q^nTc{dJ<;n0BiHaOe6h==Qdv%Lp+$U6sl&2f1*BVK_kcpvqL>wvSh;5*Q4 zG4gI59D}RsA@7!}V{ls$pZOu` z1=j)R{0rd2`H=Sr@@`CvUJ7=7hMy}oA#ebhiieee1x*$ zTHwscdlq?}*T(XiU&Xg@jc~#rP)E2eaEp-FfcCTui{+)?zNR0&rXRMZ-?XORrlwz? zrr)xrADE`!sHR_+rk|jupO(J(>PNM%1-mOG< z;jW2UIa6};?DuN|_$V#vgga_QNH`IzQn zn(Zqw=SAl6NJIAp_j2;VL%kr=932n)ACVKT6zjh6?JX~SA8fC*{jU_;zTx$jAHFNL zebM3j^@r~_?ynr@*a+(z@1Anwl~DM*@qzDyZQt$JlOJ3uwtd6<{{7**@`s4bI|KY- zIhTVUiQ@`=wa?}FL2SLSG!E9~xIo|a?rXhpdF=Xz^G)l8Z^Evx`u1$!Z>AaX(!SvI zZr3ZNSzquj9p8Q9_RZtto7R7qr&w$h_g&vBZ_jU=CwuerZ=>9|jf2ar`!4Ne5l+|_ z{l9B_MrhU-yuRCWHeb=Nz#8>I{Cfy9di?6XFL>WPPDuiI5c&aHug0KWZp8KlzpwWF zW^DWJFW)q7uN>RH;a`3ne;c-a!|S{KuMFG1>Drfmr*B-p|M>nkTqC`j^jxQ|H zAX@;5B!S+r{}H}$rC9fkZ*TtpeXzaK{&1z(_6@H$U-+)r_C<$F=Z7o7H8P8R6TX+u z|5uLlcjpgRigjOfxOATQKG^n6kDl|ym15gByzk#1RN6lH=p(L&s$FHvF{~>edaNuy zym{|H`|q#++cZzxjXxc5Yt{{QKmYeNvi_vwcelS#c;C>QllCX?+?t(zF-Kypcl~Fh zJ177DVed`gq5Qtb@frJC_6S)bWP4_xv5kF8_9dchgULPyDLZLXDU!9cp`ui_79paA zl8{oODD9F&QUCjlM2p_<_h~qdnG~%)FMV#!U}ojYDf*o8~X3UE_Uukp7CaI@Y4%tX@&J$r>lU zBbboOU3O6~TMd(1g|JTrcl*6=Ei>dl&SJ8sD7(GNd4pf2sb3iHr5)x4mmf1dP(oi^ zwbSVR(%ZQPmAZ7TX1o3PL{B_Zc3P3a-i8ylo6n9sFh4ly9vRVYbiq})=j4iw{!)7T z)UCa^jB1T6xa)h!u`l18+BlqCwqmzbtfy+2oQg<$d_v7xA>C~1;YSN~JT`eB!pU!c z)nay}D=Fdb+7nMpkBykEiB~kZaDR_eg{X$^0{IK|?7b)S65o_={3>`87bTC};L@zL zvdV;|RBI!eYWL7a;)uVQ-qw8*9ouchWB#P)f1Q73APVBi*xi8RES5lb(h-s1o`&$B zqmAd6<6q|;ymvE;1J=6muIX$W(%!1ByYUjuIv;O_3yq%8^zskd3%kDq*(|@J)_xfPg3eV4nf36Sysh|GOsGXm$ zbI1Sh@$^r6`>)6U&pe%<&Y#z5e@9mTHGk|^&dxW(W+h>My8pR8__N&p?4SQD)c#3s z=gPz1=ka{~|7&^pGjHd^`>Q@MX7~b;U^PYiulWZ)KeX|yRrnKMf0Tp2NA3Lh{*GPv z6Sed6b?*58T`K=Xrw{O|X7WCyfq(V0&-EuN=g0f6jQ^kK)!*?Ce^2YI7Wfmr|8)HS zeS7dHYX4K?Kcivi!}r(o|4&r@iJw2)130D>##kTF7;#%Ya2Ez-&lkvCASes^m*co_ zWN9vDIEwo#?%zdi{zU(e^6<~7pC9k9df{KAe!lViasBX5ss0n)KkA8pjq3UF|Ee$k zKJEWhZ~Rl*{}ui5�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%0A~=3F;g8l!;0U`QAv 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 E0GFf)F8}}l literal 0 HcmV?d00001 diff --git a/transport/internet/finalmask/rawpacket/windivert/assets/WinDivert64.sys b/transport/internet/finalmask/rawpacket/windivert/assets/WinDivert64.sys new file mode 100644 index 0000000000000000000000000000000000000000..218ccaf423ef0a67696226f9ef3a09149e4441d0 GIT binary patch literal 94144 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 Mok_m={2+h)7u^}>0RR91 literal 0 HcmV?d00001 diff --git a/transport/internet/finalmask/rawpacket/windivert/assets_386.go b/transport/internet/finalmask/rawpacket/windivert/assets_386.go new file mode 100644 index 000000000000..0cbf35ed5cbf --- /dev/null +++ b/transport/internet/finalmask/rawpacket/windivert/assets_386.go @@ -0,0 +1,14 @@ +//go:build windows && 386 + +package windivert + +import _ "embed" + +//go:embed assets/WinDivert32.sys +var sysBytes []byte + +func assetFiles() []assetFile { + return []assetFile{{"WinDivert32.sys", sysBytes}} +} + +func driverSysName() string { return "WinDivert32.sys" } diff --git a/transport/internet/finalmask/rawpacket/windivert/assets_amd64.go b/transport/internet/finalmask/rawpacket/windivert/assets_amd64.go new file mode 100644 index 000000000000..2c9fb6c6ad19 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/windivert/assets_amd64.go @@ -0,0 +1,14 @@ +//go:build windows && amd64 + +package windivert + +import _ "embed" + +//go:embed assets/WinDivert64.sys +var sysBytes []byte + +func assetFiles() []assetFile { + return []assetFile{{"WinDivert64.sys", sysBytes}} +} + +func driverSysName() string { return "WinDivert64.sys" } diff --git a/transport/internet/finalmask/rawpacket/windivert/assets_unsupported.go b/transport/internet/finalmask/rawpacket/windivert/assets_unsupported.go new file mode 100644 index 000000000000..04698953fa6b --- /dev/null +++ b/transport/internet/finalmask/rawpacket/windivert/assets_unsupported.go @@ -0,0 +1,7 @@ +//go:build windows && !amd64 && !386 + +package windivert + +func assetFiles() []assetFile { return nil } + +func driverSysName() string { return "" } diff --git a/transport/internet/finalmask/rawpacket/windivert/driver_windows.go b/transport/internet/finalmask/rawpacket/windivert/driver_windows.go new file mode 100644 index 000000000000..50e94c578422 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/windivert/driver_windows.go @@ -0,0 +1,211 @@ +//go:build windows + +package windivert + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "strconv" + "sync" + + "golang.org/x/sys/windows" +) + +const ( + driverServiceName = "WinDivert" + driverDeviceName = `\\.\WinDivert` +) + +var ( + driverOnce sync.Once + driverErr error + // driverDevName is ASCII-safe and must be available before ensureDriver + // so Open can try CreateFile first and only install on FILE_NOT_FOUND. + driverDevName, _ = windows.UTF16PtrFromString(driverDeviceName) +) + +// Requires SeLoadDriverPrivilege (Administrator). Running the 386 build +// under WOW64 on a 64-bit kernel is rejected — use the amd64 build. +func ensureDriver() error { + driverOnce.Do(func() { + driverErr = installDriver() + }) + return driverErr +} + +func installDriver() error { + if runtime.GOARCH == "386" { + var isWow64 bool + err := windows.IsWow64Process(windows.CurrentProcess(), &isWow64) + if err == nil && isWow64 { + return errors.New("windivert: 386 build detected running under WOW64 on a 64-bit kernel; use the amd64 build") + } + } + + dir, err := ensureExtracted() + if err != nil { + return err + } + sysPath := filepath.Join(dir, driverSysName()) + sysPathW, err := windows.UTF16PtrFromString(sysPath) + if err != nil { + return fmt.Errorf("windivert: utf16 driver path: %w", err) + } + + // Serialize driver install across concurrent processes. + mutexName, _ := windows.UTF16PtrFromString("WinDivertDriverInstallMutex") + mutex, err := windows.CreateMutex(nil, false, mutexName) + if err != nil { + return fmt.Errorf("windivert: create install mutex: %w", err) + } + defer windows.CloseHandle(mutex) + _, err = windows.WaitForSingleObject(mutex, windows.INFINITE) + if err != nil { + return fmt.Errorf("windivert: wait install mutex: %w", err) + } + defer windows.ReleaseMutex(mutex) + + manager, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_ALL_ACCESS) + if err != nil { + return fmt.Errorf("windivert: open SCM: %w", err) + } + defer windows.CloseServiceHandle(manager) + + serviceNameW, _ := windows.UTF16PtrFromString(driverServiceName) + service, err := windows.OpenService(manager, serviceNameW, windows.SERVICE_ALL_ACCESS) + if err != nil { + service, err = windows.CreateService( + manager, + serviceNameW, + serviceNameW, + windows.SERVICE_ALL_ACCESS, + windows.SERVICE_KERNEL_DRIVER, + windows.SERVICE_DEMAND_START, + windows.SERVICE_ERROR_NORMAL, + sysPathW, + nil, nil, nil, nil, nil, + ) + if err != nil { + if errors.Is(err, windows.ERROR_SERVICE_EXISTS) { + service, err = windows.OpenService(manager, serviceNameW, windows.SERVICE_ALL_ACCESS) + } + if err != nil { + return wrapDriverInstallError(err) + } + } + } + defer windows.CloseServiceHandle(service) + + err = windows.StartService(service, 0, nil) + if err != nil && errors.Is(err, windows.ERROR_SERVICE_DISABLED) { + // A prior process called DeleteService on a still-running kernel + // driver: SCM marks the record for deletion and flips START_TYPE + // to DISABLED until the last handle closes. Re-enable so we can + // start it instead of waiting for a reboot. + err = windows.ChangeServiceConfig( + service, + windows.SERVICE_NO_CHANGE, + windows.SERVICE_DEMAND_START, + windows.SERVICE_NO_CHANGE, + nil, nil, nil, nil, nil, nil, nil, + ) + if err != nil { + return fmt.Errorf("windivert: re-enable disabled service: %w", err) + } + err = windows.StartService(service, 0, nil) + } + if err == nil { + // Mark for deletion so the driver unregisters when the last handle + // closes or on next reboot. Matches the upstream DLL's behavior: + // only the process that actually started the service takes on the + // cleanup responsibility. If another process already started it, + // we leave DeleteService to them. + _ = windows.DeleteService(service) + } else if !errors.Is(err, windows.ERROR_SERVICE_ALREADY_RUNNING) { + return fmt.Errorf("windivert: start service: %w", err) + } + return nil +} + +func wrapDriverInstallError(err error) error { + if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + return fmt.Errorf("windivert: installing the kernel driver requires Administrator privileges: %w", err) + } + return fmt.Errorf("windivert: create service: %w", err) +} + +type assetFile struct { + name string + data []byte +} + +var ( + extractOnce sync.Once + extractErr error + extractDir string +) + +// The on-disk copy is protected by Windows Authenticode signature +// enforcement, which rejects any tampered .sys at StartService time. +func ensureExtracted() (string, error) { + extractOnce.Do(func() { + extractDir, extractErr = extractImpl() + }) + return extractDir, extractErr +} + +func extractImpl() (string, error) { + files := assetFiles() + if len(files) == 0 { + return "", fmt.Errorf("windivert: unsupported architecture %s", runtime.GOARCH) + } + + base, err := os.UserCacheDir() + if err != nil { + return "", fmt.Errorf("windivert: locate user cache dir: %w", err) + } + dir := filepath.Join(base, "xray-core", "windivert", "v"+AssetVersion) + err = os.MkdirAll(dir, 0o755) + if err != nil { + return "", fmt.Errorf("windivert: mkdir %s: %w", dir, err) + } + + for _, asset := range files { + err = ensureAsset(dir, asset) + if err != nil { + return "", err + } + } + return dir, nil +} + +// Concurrent sing-box processes race on os.Rename (atomic on NTFS); +// whichever wins creates the final file. Writers that lose the race +// silently discard their temp copy. +func ensureAsset(dir string, asset assetFile) error { + target := filepath.Join(dir, asset.name) + _, err := os.Stat(target) + if err == nil { + return nil + } + if !os.IsNotExist(err) { + return fmt.Errorf("windivert: stat %s: %w", asset.name, err) + } + tmp := target + ".tmp-" + strconv.Itoa(os.Getpid()) + err = os.WriteFile(tmp, asset.data, 0o644) + if err != nil { + return fmt.Errorf("windivert: write %s: %w", asset.name, err) + } + err = os.Rename(tmp, target) + if err != nil { + os.Remove(tmp) + if _, statErr := os.Stat(target); statErr == nil { + return nil + } + return fmt.Errorf("windivert: rename %s: %w", asset.name, err) + } + return nil +} diff --git a/transport/internet/finalmask/rawpacket/windivert/filter.go b/transport/internet/finalmask/rawpacket/windivert/filter.go new file mode 100644 index 000000000000..d63adae2b630 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/windivert/filter.go @@ -0,0 +1,181 @@ +package windivert + +import ( + "encoding/binary" + "errors" + "net/netip" +) + +// WINDIVERT_FILTER VM instruction layout (24 bytes, #pragma pack(1)): +// +// word 0 (LE): field:11 | test:5 | success:16 +// word 1 (LE): failure:16 | neg:1 | reserved:15 +// words 2..5: arg[4] (native-endian uint32 each) +// +// The driver walks this as a decision tree: evaluate the test at inst i; +// on success jump to success; on failure jump to failure. Continuations +// 0x7FFE and 0x7FFF are ACCEPT and REJECT terminals. +const ( + filterInstBytes = 24 + filterMaxInsts = 256 + + fieldZero = 0 + fieldOutbound = 2 + fieldIP = 5 + fieldIPv6 = 6 + fieldTCP = 8 + fieldIPSrcAddr = 21 + fieldIPDstAddr = 22 + fieldIPv6SrcAddr = 28 + fieldIPv6DstAddr = 29 + fieldTCPSrcPort = 38 + fieldTCPDstPort = 39 + + testEQ = 0 + + resultAccept uint16 = 0x7FFE + resultReject uint16 = 0x7FFF +) + +// Filter flags passed to IOCTL_WINDIVERT_STARTUP alongside the compiled +// filter. These tell the driver what *kinds* of packets the filter might +// match, used as a kernel-side fast-reject. +const ( + filterFlagOutbound uint64 = 0x0020 + filterFlagIP uint64 = 0x0040 + filterFlagIPv6 uint64 = 0x0080 +) + +type filterInst struct { + field uint16 // 11 bits used + test uint8 // 5 bits used + success uint16 + failure uint16 + neg bool + arg [4]uint32 +} + +// Filter is a typed specification of packets to capture. It replaces +// WinDivert's filter string language. +// +// Zero value = "reject all" (match nothing), suitable for send-only handles. +type Filter struct { + insts []filterInst + flags uint64 // filter flags for STARTUP ioctl +} + +// reject returns a filter that matches no packet. The empty insts slice +// is encoded as a single rejecting instruction by encode(). +func reject() *Filter { + return &Filter{} +} + +// OutboundTCP returns a filter matching outbound TCP packets on the given +// 5-tuple. Both addresses must share an address family (IPv4 or IPv6). +func OutboundTCP(src, dst netip.AddrPort) (*Filter, error) { + if !src.IsValid() || !dst.IsValid() { + return nil, errors.New("windivert: filter: invalid address port") + } + if src.Addr().Is4() != dst.Addr().Is4() { + return nil, errors.New("windivert: filter: mixed IPv4/IPv6") + } + f := &Filter{ + flags: filterFlagOutbound, + } + // Insts chain as AND: each test's failure = REJECT, success = next inst. + // The final inst's success = ACCEPT. + f.add(fieldOutbound, testEQ, argUint32(1)) + if src.Addr().Is4() { + f.flags |= filterFlagIP + f.add(fieldIP, testEQ, argUint32(1)) + f.add(fieldTCP, testEQ, argUint32(1)) + f.add(fieldIPSrcAddr, testEQ, argIPv4(src.Addr())) + f.add(fieldIPDstAddr, testEQ, argIPv4(dst.Addr())) + } else { + f.flags |= filterFlagIPv6 + f.add(fieldIPv6, testEQ, argUint32(1)) + f.add(fieldTCP, testEQ, argUint32(1)) + f.add(fieldIPv6SrcAddr, testEQ, argIPv6(src.Addr())) + f.add(fieldIPv6DstAddr, testEQ, argIPv6(dst.Addr())) + } + f.add(fieldTCPSrcPort, testEQ, argUint32(uint32(src.Port()))) + f.add(fieldTCPDstPort, testEQ, argUint32(uint32(dst.Port()))) + return f, nil +} + +func (f *Filter) add(field uint16, test uint8, arg [4]uint32) { + f.insts = append(f.insts, filterInst{field: field, test: test, arg: arg}) +} + +func argUint32(v uint32) [4]uint32 { return [4]uint32{v, 0, 0, 0} } + +// argIPv4 encodes an IPv4 address for IP_SRCADDR/IP_DSTADDR. The driver +// compares against an IPv4-mapped-IPv6 form: {host_order_u32, 0x0000FFFF, +// 0, 0} (see sys/windivert.c windivert_get_ipv4_addr and the IPv4_SRCADDR +// val-word construction). Omitting the 0x0000FFFF marker causes the EQ +// test to fail for every packet. +func argIPv4(addr netip.Addr) [4]uint32 { + b := addr.As4() + return [4]uint32{binary.BigEndian.Uint32(b[:]), 0x0000FFFF, 0, 0} +} + +// argIPv6 encodes an IPv6 address for IPV6_SRCADDR/IPV6_DSTADDR. The +// driver stores the address as four host-order uint32s in REVERSED word +// order: val[0]=low (bytes 12..15), val[3]=high (bytes 0..3). See +// sys/windivert.c windivert_outbound_network_v6_classify val-word +// construction. +func argIPv6(addr netip.Addr) [4]uint32 { + b := addr.As16() + return [4]uint32{ + binary.BigEndian.Uint32(b[12:16]), + binary.BigEndian.Uint32(b[8:12]), + binary.BigEndian.Uint32(b[4:8]), + binary.BigEndian.Uint32(b[0:4]), + } +} + +// encode serializes the Filter to the on-wire WINDIVERT_FILTER[] format +// plus the filter_flags for STARTUP ioctl. +func (f *Filter) encode() ([]byte, uint64, error) { + if len(f.insts) == 0 { + // "Reject all" — one instruction, ZERO == 0 is always true, but we + // invert by setting both success and failure to REJECT. + return encodeInst(filterInst{ + field: fieldZero, + test: testEQ, + success: resultReject, + failure: resultReject, + }), 0, nil + } + if len(f.insts) > filterMaxInsts-1 { + return nil, 0, errors.New("windivert: filter too long") + } + buf := make([]byte, 0, filterInstBytes*len(f.insts)) + for i, inst := range f.insts { + if i == len(f.insts)-1 { + inst.success = resultAccept + } else { + inst.success = uint16(i + 1) + } + inst.failure = resultReject + buf = append(buf, encodeInst(inst)...) + } + return buf, f.flags, nil +} + +func encodeInst(inst filterInst) []byte { + out := make([]byte, filterInstBytes) + word0 := uint32(inst.field&0x7FF) | uint32(inst.test&0x1F)<<11 | + uint32(inst.success)<<16 + word1 := uint32(inst.failure) + if inst.neg { + word1 |= 1 << 16 + } + binary.LittleEndian.PutUint32(out[0:4], word0) + binary.LittleEndian.PutUint32(out[4:8], word1) + binary.LittleEndian.PutUint32(out[8:12], inst.arg[0]) + binary.LittleEndian.PutUint32(out[12:16], inst.arg[1]) + binary.LittleEndian.PutUint32(out[16:20], inst.arg[2]) + binary.LittleEndian.PutUint32(out[20:24], inst.arg[3]) + return out +} diff --git a/transport/internet/finalmask/rawpacket/windivert/handle_windows.go b/transport/internet/finalmask/rawpacket/windivert/handle_windows.go new file mode 100644 index 000000000000..c48e6214c11b --- /dev/null +++ b/transport/internet/finalmask/rawpacket/windivert/handle_windows.go @@ -0,0 +1,323 @@ +//go:build windows + +package windivert + +import ( + "encoding/binary" + "errors" + "fmt" + "runtime" + "sync" + "unsafe" + + "golang.org/x/sys/windows" +) + +// Handle owns a WinDivert kernel device handle plus a private event for +// overlapped I/O. Methods on *Handle are not safe for concurrent use +// across goroutines (there is a single shared event per Handle). +// +// addr is a per-Handle Address buffer the IOCTL struct embeds a pointer +// to. It lives on the heap (as a field of a heap-allocated Handle) so +// the pointer value stored as bytes in the ioctl buffer remains valid +// across stack growth between buildIoctl* and the DeviceIoControl +// syscall — stack-local Address values are not safe for this pattern +// because Go's escape analysis does not see the pointer through the +// unsafe.Pointer → uintptr → bytes conversion. +type Handle struct { + device windows.Handle + event windows.Handle + closing sync.Once + closeErr error + addr Address +} + +// Filter may be nil for "reject all", suitable for send-only handles. +// Requires Administrator on first call per process (installs the kernel +// driver via SCM); subsequent calls reuse the running driver. +func Open(filter *Filter, layer Layer, priority int16, flags Flag) (*Handle, error) { + err := validateOpenArgs(layer, priority, flags) + if err != nil { + return nil, err + } + if filter == nil { + filter = reject() + } + filterBin, filterFlags, err := filter.encode() + if err != nil { + return nil, err + } + device, err := openDevice() + if err != nil { + if !errors.Is(err, windows.ERROR_FILE_NOT_FOUND) && + !errors.Is(err, windows.ERROR_PATH_NOT_FOUND) { + if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + return nil, fmt.Errorf("windivert: open device (administrator required): %w", err) + } + return nil, fmt.Errorf("windivert: open device: %w", err) + } + // Device node missing: kernel driver not loaded. Install + retry. + // Matches WinDivertOpen's lazy-install path; avoids racing StartService + // against a still-loaded driver whose SCM record is marked for deletion. + err = ensureDriver() + if err != nil { + return nil, err + } + device, err = openDevice() + if err != nil { + if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + return nil, fmt.Errorf("windivert: open device (administrator required): %w", err) + } + return nil, fmt.Errorf("windivert: open device: %w", err) + } + } + event, err := windows.CreateEvent(nil, 1, 0, nil) // manual reset, unsignaled + if err != nil { + windows.CloseHandle(device) + return nil, fmt.Errorf("windivert: create event: %w", err) + } + h := &Handle{device: device, event: event} + + err = h.initialize(layer, priority, flags) + if err != nil { + h.Close() + return nil, err + } + err = h.startup(filterBin, filterFlags) + if err != nil { + h.Close() + return nil, err + } + return h, nil +} + +func openDevice() (windows.Handle, error) { + return windows.CreateFile( + driverDevName, + windows.GENERIC_READ|windows.GENERIC_WRITE, + 0, nil, + windows.OPEN_EXISTING, + windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_OVERLAPPED, + 0, + ) +} + +func validateOpenArgs(layer Layer, priority int16, flags Flag) error { + if layer != LayerNetwork { + return fmt.Errorf("windivert: invalid layer %d", uint32(layer)) + } + if priority < PriorityLowest || priority > PriorityHighest { + return errors.New("windivert: priority out of range") + } + const supportedFlags = FlagSniff | FlagSendOnly + if flags&^supportedFlags != 0 { + return errors.New("windivert: unknown flag bits") + } + if flags&FlagSniff != 0 && flags&FlagSendOnly != 0 { + return errors.New("windivert: FlagSniff and FlagSendOnly are mutually exclusive") + } + return nil +} + +func (h *Handle) initialize(layer Layer, priority int16, flags Flag) error { + in := buildIoctlInitialize(layer, priority, flags) + // WINDIVERT_VERSION is a 64-byte packed struct; only the first 20 + // bytes (magic, major, minor, bits) carry data, the rest is reserved. + var outBuf [versionStructSize]byte + binary.LittleEndian.PutUint64(outBuf[0:8], magicDLL) + binary.LittleEndian.PutUint32(outBuf[8:12], versionMajor) + binary.LittleEndian.PutUint32(outBuf[12:16], versionMinor) + binary.LittleEndian.PutUint32(outBuf[16:20], uint32(unsafe.Sizeof(uintptr(0))*8)) + _, err := doIoctl(h.device, ioctlInitialize, in[:], outBuf[:], h.event) + if err != nil { + return fmt.Errorf("windivert: initialize ioctl: %w", err) + } + gotMagic := binary.LittleEndian.Uint64(outBuf[0:8]) + if gotMagic != magicSYS { + return fmt.Errorf("windivert: driver magic mismatch (got %d)", gotMagic) + } + gotMajor := binary.LittleEndian.Uint32(outBuf[8:12]) + if gotMajor < versionMajor { + gotMinor := binary.LittleEndian.Uint32(outBuf[12:16]) + return fmt.Errorf("windivert: driver version too old: %d.%d", gotMajor, gotMinor) + } + return nil +} + +func (h *Handle) startup(filterBin []byte, filterFlags uint64) error { + in := buildIoctlStartup(filterFlags) + _, err := doIoctl(h.device, ioctlStartup, in[:], filterBin, h.event) + if err != nil { + return fmt.Errorf("windivert: startup ioctl: %w", err) + } + return nil +} + +// If the handle is closed mid-Recv the error wraps ERROR_OPERATION_ABORTED. +func (h *Handle) Recv(buf []byte) (int, Address, error) { + if len(buf) == 0 { + return 0, Address{}, errors.New("windivert: recv: zero-length buffer") + } + h.addr = Address{} + in := buildIoctlRecv(&h.addr) + n, err := doIoctl(h.device, ioctlRecv, in[:], buf, h.event) + runtime.KeepAlive(h) + if err != nil { + return 0, Address{}, err + } + return int(n), h.addr, nil +} + +// The address's Outbound flag controls whether the packet is sent toward +// the wire (outbound=true) or delivered up the stack (outbound=false). +// IfIdx and SubIfIdx can stay zero — the driver uses the routing table +// when IfIdx=0. +func (h *Handle) Send(packet []byte, addr *Address) (int, error) { + if len(packet) == 0 { + return 0, errors.New("windivert: send: empty packet") + } + if addr == nil { + return 0, errors.New("windivert: send: nil address") + } + h.addr = *addr + in := buildIoctlSend(&h.addr) + n, err := doIoctl(h.device, ioctlSend, in[:], packet, h.event) + runtime.KeepAlive(h) + if err != nil { + return 0, err + } + return int(n), nil +} + +// Idempotent. Aborts any in-flight I/O on the handle. +func (h *Handle) Close() error { + h.closing.Do(func() { + var errs []error + if h.device != 0 { + err := windows.CloseHandle(h.device) + if err != nil { + errs = append(errs, err) + } + h.device = 0 + } + if h.event != 0 { + err := windows.CloseHandle(h.event) + if err != nil { + errs = append(errs, err) + } + h.event = 0 + } + h.closeErr = errors.Join(errs...) + }) + return h.closeErr +} + +// IOCTL codes from windivert_device.h. CTL_CODE macro layout: +// +// (DeviceType << 16) | (Access << 14) | (Function << 2) | Method +const ( + fileDeviceNetwork uint32 = 0x12 + accessReadWrite uint32 = 3 // FILE_READ_DATA | FILE_WRITE_DATA + accessRead uint32 = 1 + + methodInDirect uint32 = 1 + methodOutDirect uint32 = 2 +) + +func ctlCode(deviceType, access, function, method uint32) uint32 { + return (deviceType << 16) | (access << 14) | (function << 2) | method +} + +var ( + ioctlInitialize = ctlCode(fileDeviceNetwork, accessReadWrite, 0x921, methodOutDirect) + ioctlStartup = ctlCode(fileDeviceNetwork, accessReadWrite, 0x922, methodInDirect) + ioctlRecv = ctlCode(fileDeviceNetwork, accessRead, 0x923, methodOutDirect) + ioctlSend = ctlCode(fileDeviceNetwork, accessReadWrite, 0x924, methodInDirect) +) + +// Magic numbers exchanged during INITIALIZE. DLL sends magicDLL in the +// version struct; driver returns magicSYS on success. +const ( + magicDLL uint64 = 0x4C4C447669645724 // "$WdivDLL" in LE bytes + magicSYS uint64 = 0x5359537669645723 // "#WdivSYS" in LE bytes +) + +const ( + versionMajor uint32 = 2 + versionMinor uint32 = 2 +) + +// Size of the WINDIVERT_IOCTL union on wire (packed). +const ioctlSize = 16 + +// Size of WINDIVERT_VERSION on wire (packed). Only the first 20 bytes +// carry data; the rest is reserved zero padding. +const versionStructSize = 64 + +// doIoctl performs a single synchronous (blocking) overlapped +// DeviceIoControl. The handle is opened with FILE_FLAG_OVERLAPPED so +// DeviceIoControl returns ERROR_IO_PENDING; we then wait for completion +// via GetOverlappedResult. Event is passed in so callers can reuse it +// across calls on the same handle (avoids per-call CreateEvent). +func doIoctl(handle windows.Handle, code uint32, in []byte, out []byte, event windows.Handle) (uint32, error) { + var overlapped windows.Overlapped + overlapped.HEvent = event + _ = windows.ResetEvent(event) + + var inPtr *byte + var inLen uint32 + if len(in) > 0 { + inPtr = &in[0] + inLen = uint32(len(in)) + } + var outPtr *byte + var outLen uint32 + if len(out) > 0 { + outPtr = &out[0] + outLen = uint32(len(out)) + } + var returned uint32 + err := windows.DeviceIoControl(handle, code, inPtr, inLen, outPtr, outLen, &returned, &overlapped) + if err != nil && !errors.Is(err, windows.ERROR_IO_PENDING) { + return 0, err + } + err = windows.GetOverlappedResult(handle, &overlapped, &returned, true) + if err != nil { + return 0, err + } + return returned, nil +} + +func buildIoctlInitialize(layer Layer, priority int16, flags Flag) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint32(buf[0:4], uint32(layer)) + // The driver expects priority + WINDIVERT_PRIORITY_HIGHEST (30000) so + // the low range maps to non-negative integers. + binary.LittleEndian.PutUint32(buf[4:8], uint32(int32(priority)+int32(PriorityHighest))) + binary.LittleEndian.PutUint64(buf[8:16], uint64(flags)) + return buf +} + +func buildIoctlStartup(filterFlags uint64) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint64(buf[0:8], filterFlags) + return buf +} + +// buildIoctlRecv packs a user-space pointer to a WINDIVERT_ADDRESS into +// the ioctl struct. The driver dereferences it to write the address for +// the received packet. Caller must keep the Address alive via +// runtime.KeepAlive. +func buildIoctlRecv(addr *Address) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint64(buf[0:8], uint64(uintptr(unsafe.Pointer(addr)))) + binary.LittleEndian.PutUint64(buf[8:16], 0) + return buf +} + +func buildIoctlSend(addr *Address) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint64(buf[0:8], uint64(uintptr(unsafe.Pointer(addr)))) + binary.LittleEndian.PutUint64(buf[8:16], uint64(unsafe.Sizeof(Address{}))) + return buf +} diff --git a/transport/internet/finalmask/rawpacket/windivert/windivert.go b/transport/internet/finalmask/rawpacket/windivert/windivert.go new file mode 100644 index 000000000000..9d309886cbe3 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/windivert/windivert.go @@ -0,0 +1,78 @@ +// Package windivert provides a pure-Go binding to the WinDivert kernel +// driver on Windows (amd64 and 386). User-mode WinDivert calls are +// reimplemented in Go; only the signed kernel driver is embedded as an +// asset, since SCM-installed drivers must live on disk and their +// Authenticode signature forbids modification. +// +// Administrator is required for the first Open in a process so SCM can +// load the driver. Upstream: https://github.com/basil00/WinDivert v2.2.2, +// redistributed under its LGPL v3 option; see assets/LICENSE.txt. +package windivert + +import "unsafe" + +const AssetVersion = "2.2.2" + +// MTUMax is WINDIVERT_MTU_MAX from windivert.h (40 + 0xFFFF). Suitable as +// a single-packet receive buffer size. +const MTUMax = 40 + 0xFFFF + +type Layer uint32 + +const LayerNetwork Layer = 0 + +type Flag uint64 + +const ( + // FlagSniff opens a passive observer: the driver copies matching packets + // to userspace without removing them from the network stack. Send is not + // required (and not allowed) on a sniffing handle. + FlagSniff Flag = 0x0001 + // FlagSendOnly opens a write-only injection handle; Recv is not allowed. + FlagSendOnly Flag = 0x0008 +) + +const ( + PriorityHighest int16 = 30000 + PriorityLowest int16 = -30000 +) + +// Address mirrors WINDIVERT_ADDRESS from windivert.h (80 bytes, +// little-endian on both amd64 and 386): +// +// 0: INT64 Timestamp +// 8: UINT32 bitfield: Layer:8 | Event:8 | flags | Reserved1:8 +// 12: UINT32 Reserved2 +// 16: 64 bytes union (WINDIVERT_DATA_NETWORK / FLOW / SOCKET / REFLECT) +type Address struct { + Timestamp int64 + bits uint32 + Reserved2 uint32 + union [64]byte +} + +var _ [80]byte = [unsafe.Sizeof(Address{})]byte{} + +// Bit positions inside the Address's packed flags word. +const ( + addrBitIPv6 = 20 + addrBitIPChecksum = 21 + addrBitTCPChecksum = 22 +) + +func getFlagBit(bits uint32, pos uint) bool { return bits&(1< Date: Sat, 9 May 2026 14:45:00 +0600 Subject: [PATCH 22/42] Rawpacket: Add tcpmask config, proto and conn wrapper with configurable TTL --- .../internet/finalmask/rawpacket/config.go | 14 ++ .../internet/finalmask/rawpacket/config.pb.go | 156 ++++++++++++++++ .../internet/finalmask/rawpacket/config.proto | 23 +++ .../internet/finalmask/rawpacket/conn.go | 170 ++++++++++++++++++ .../internet/finalmask/rawpacket/conn_test.go | 46 +++++ 5 files changed, 409 insertions(+) create mode 100644 transport/internet/finalmask/rawpacket/config.go create mode 100644 transport/internet/finalmask/rawpacket/config.pb.go create mode 100644 transport/internet/finalmask/rawpacket/config.proto create mode 100644 transport/internet/finalmask/rawpacket/conn.go create mode 100644 transport/internet/finalmask/rawpacket/conn_test.go diff --git a/transport/internet/finalmask/rawpacket/config.go b/transport/internet/finalmask/rawpacket/config.go new file mode 100644 index 000000000000..e4ee5d717d25 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/config.go @@ -0,0 +1,14 @@ +package rawpacket + +import "net" + +func (c *Config) TCP() {} + +func (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) { + return NewConnClient(c, raw) +} + +func (c *Config) WrapConnServer(raw net.Conn) (net.Conn, error) { + // Raw packet injection is client-side only. + return raw, nil +} diff --git a/transport/internet/finalmask/rawpacket/config.pb.go b/transport/internet/finalmask/rawpacket/config.pb.go new file mode 100644 index 000000000000..f1e3982bb2d4 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/config.pb.go @@ -0,0 +1,156 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v7.34.1 +// source: transport/internet/finalmask/rawpacket/config.proto + +package rawpacket + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Config struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Base64-encoded fake payload bytes to inject before the real traffic. + Payload string `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + // Corruption method to make the fake packet dropped by the server. + // Available: wrong-sequence, wrong-checksum, wrong-ack, wrong-md5, wrong-timestamp. + Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"` + // TTL of the fake packet. A low value (e.g. 3-5) ensures the packet + // is seen by middleboxes but does not reach the destination server. + Ttl uint32 `protobuf:"varint,3,opt,name=ttl,proto3" json:"ttl,omitempty"` + // How many Write() calls trigger injection. 0 or 1 = single-shot (default). + Count int32 `protobuf:"varint,4,opt,name=count,proto3" json:"count,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Config) Reset() { + *x = Config{} + mi := &file_transport_internet_finalmask_rawpacket_config_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Config) ProtoMessage() {} + +func (x *Config) ProtoReflect() protoreflect.Message { + mi := &file_transport_internet_finalmask_rawpacket_config_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Config.ProtoReflect.Descriptor instead. +func (*Config) Descriptor() ([]byte, []int) { + return file_transport_internet_finalmask_rawpacket_config_proto_rawDescGZIP(), []int{0} +} + +func (x *Config) GetPayload() string { + if x != nil { + return x.Payload + } + return "" +} + +func (x *Config) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +func (x *Config) GetTtl() uint32 { + if x != nil { + return x.Ttl + } + return 0 +} + +func (x *Config) GetCount() int32 { + if x != nil { + return x.Count + } + return 0 +} + +var File_transport_internet_finalmask_rawpacket_config_proto protoreflect.FileDescriptor + +const file_transport_internet_finalmask_rawpacket_config_proto_rawDesc = "" + + "\n" + + "3transport/internet/finalmask/rawpacket/config.proto\x12+xray.transport.internet.finalmask.rawpacket\"b\n" + + "\x06Config\x12\x18\n" + + "\apayload\x18\x01 \x01(\tR\apayload\x12\x16\n" + + "\x06method\x18\x02 \x01(\tR\x06method\x12\x10\n" + + "\x03ttl\x18\x03 \x01(\rR\x03ttl\x12\x14\n" + + "\x05count\x18\x04 \x01(\x05R\x05countB\xa3\x01\n" + + "/com.xray.transport.internet.finalmask.rawpacketP\x01Z@github.com/xtls/xray-core/transport/internet/finalmask/rawpacket\xaa\x02+Xray.Transport.Internet.Finalmask.Rawpacketb\x06proto3" + +var ( + file_transport_internet_finalmask_rawpacket_config_proto_rawDescOnce sync.Once + file_transport_internet_finalmask_rawpacket_config_proto_rawDescData []byte +) + +func file_transport_internet_finalmask_rawpacket_config_proto_rawDescGZIP() []byte { + file_transport_internet_finalmask_rawpacket_config_proto_rawDescOnce.Do(func() { + file_transport_internet_finalmask_rawpacket_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_rawpacket_config_proto_rawDesc), len(file_transport_internet_finalmask_rawpacket_config_proto_rawDesc))) + }) + return file_transport_internet_finalmask_rawpacket_config_proto_rawDescData +} + +var file_transport_internet_finalmask_rawpacket_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_transport_internet_finalmask_rawpacket_config_proto_goTypes = []any{ + (*Config)(nil), // 0: xray.transport.internet.finalmask.rawpacket.Config +} +var file_transport_internet_finalmask_rawpacket_config_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_transport_internet_finalmask_rawpacket_config_proto_init() } +func file_transport_internet_finalmask_rawpacket_config_proto_init() { + if File_transport_internet_finalmask_rawpacket_config_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_rawpacket_config_proto_rawDesc), len(file_transport_internet_finalmask_rawpacket_config_proto_rawDesc)), + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_transport_internet_finalmask_rawpacket_config_proto_goTypes, + DependencyIndexes: file_transport_internet_finalmask_rawpacket_config_proto_depIdxs, + MessageInfos: file_transport_internet_finalmask_rawpacket_config_proto_msgTypes, + }.Build() + File_transport_internet_finalmask_rawpacket_config_proto = out.File + file_transport_internet_finalmask_rawpacket_config_proto_goTypes = nil + file_transport_internet_finalmask_rawpacket_config_proto_depIdxs = nil +} diff --git a/transport/internet/finalmask/rawpacket/config.proto b/transport/internet/finalmask/rawpacket/config.proto new file mode 100644 index 000000000000..f6b3e8dcb10e --- /dev/null +++ b/transport/internet/finalmask/rawpacket/config.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package xray.transport.internet.finalmask.rawpacket; +option csharp_namespace = "Xray.Transport.Internet.Finalmask.Rawpacket"; +option go_package = "github.com/xtls/xray-core/transport/internet/finalmask/rawpacket"; +option java_package = "com.xray.transport.internet.finalmask.rawpacket"; +option java_multiple_files = true; + +message Config { + // Base64-encoded fake payload bytes to inject before the real traffic. + string payload = 1; + + // Corruption method to make the fake packet dropped by the server. + // Available: wrong-sequence, wrong-checksum, wrong-ack, wrong-md5, wrong-timestamp. + string method = 2; + + // TTL of the fake packet. A low value (e.g. 3-5) ensures the packet + // is seen by middleboxes but does not reach the destination server. + uint32 ttl = 3; + + // How many Write() calls trigger injection. 0 or 1 = single-shot (default). + int32 count = 4; +} diff --git a/transport/internet/finalmask/rawpacket/conn.go b/transport/internet/finalmask/rawpacket/conn.go new file mode 100644 index 000000000000..188b145ea2f7 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/conn.go @@ -0,0 +1,170 @@ +package rawpacket + +import ( + "encoding/base64" + "errors" + "fmt" + "net" + "runtime" + "syscall" +) + +type Method int + +const ( + MethodWrongSequence Method = iota + MethodWrongChecksum + MethodWrongAcknowledgment + MethodWrongMD5Sig + MethodWrongTimestamp +) + +const ( + MethodNameWrongSequence = "wrong-sequence" + MethodNameWrongChecksum = "wrong-checksum" + MethodNameWrongAcknowledgment = "wrong-ack" + MethodNameWrongMD5Sig = "wrong-md5" + MethodNameWrongTimestamp = "wrong-timestamp" +) + +func ParseMethod(s string) (Method, error) { + switch s { + case "", MethodNameWrongSequence: + return MethodWrongSequence, nil + case MethodNameWrongChecksum: + return MethodWrongChecksum, nil + case MethodNameWrongAcknowledgment: + return MethodWrongAcknowledgment, nil + case MethodNameWrongMD5Sig: + return MethodWrongMD5Sig, nil + case MethodNameWrongTimestamp: + return MethodWrongTimestamp, nil + default: + return 0, fmt.Errorf("rawpacket: unknown method: %s", s) + } +} + +func (m Method) String() string { + switch m { + case MethodWrongSequence: + return MethodNameWrongSequence + case MethodWrongChecksum: + return MethodNameWrongChecksum + case MethodWrongAcknowledgment: + return MethodNameWrongAcknowledgment + case MethodWrongMD5Sig: + return MethodNameWrongMD5Sig + case MethodWrongTimestamp: + return MethodNameWrongTimestamp + default: + return "unknown" + } +} + +type rawSpoofer interface { + Inject(payload []byte) error + Close() error +} + +type Conn struct { + net.Conn + spoofer rawSpoofer + fakePayload []byte + injectionCount int + maxInjections int +} + +func NewConnClient(cfg *Config, conn net.Conn) (net.Conn, error) { + if cfg.Payload == "" { + return conn, nil + } + if !PlatformSupported { + return nil, errors.New("rawpacket is not supported on this platform") + } + payload, err := base64.StdEncoding.DecodeString(cfg.Payload) + if err != nil { + return nil, fmt.Errorf("rawpacket: invalid base64 payload: %w", err) + } + if len(payload) == 0 { + return nil, errors.New("rawpacket: payload is empty") + } + method, err := ParseMethod(cfg.Method) + if err != nil { + return nil, err + } + ttl := uint8(cfg.Ttl) + if ttl == 0 { + ttl = 3 + } + spoofer, err := newRawSpoofer(conn, method, ttl) + if err != nil { + return nil, wrapPermissionError(err) + } + maxInjections := int(cfg.Count) + if maxInjections <= 0 { + maxInjections = 1 + } + return &Conn{ + Conn: conn, + spoofer: spoofer, + fakePayload: payload, + maxInjections: maxInjections, + }, nil +} + +func NewConnServer(_ *Config, conn net.Conn) (net.Conn, error) { + return conn, nil +} + +func (c *Conn) Write(b []byte) (n int, err error) { + if c.injectionCount >= c.maxInjections { + return c.Conn.Write(b) + } + err = c.spoofer.Inject(c.fakePayload) + if err != nil { + return 0, fmt.Errorf("rawpacket: inject: %w", err) + } + c.injectionCount++ + if c.injectionCount >= c.maxInjections { + closeErr := c.spoofer.Close() + if closeErr != nil { + return 0, fmt.Errorf("rawpacket: close spoofer: %w", closeErr) + } + } + return c.Conn.Write(b) +} + +func (c *Conn) Close() error { + connErr := c.Conn.Close() + spooferErr := c.spoofer.Close() + if connErr != nil { + return connErr + } + return spooferErr +} + +func (c *Conn) TcpMaskConn() {} + +func (c *Conn) RawConn() net.Conn { + return c.Conn +} + +func (c *Conn) Splice() bool { + return c.injectionCount >= c.maxInjections +} + +func wrapPermissionError(err error) error { + if !errors.Is(err, syscall.EPERM) && !errors.Is(err, syscall.EACCES) { + return err + } + switch runtime.GOOS { + case "linux": + return fmt.Errorf("%w\n Hint: run as root, or grant capabilities:\n sudo setcap cap_net_raw,cap_net_admin+ep /path/to/xray", err) + case "darwin": + return fmt.Errorf("%w\n Hint: rawpacket requires root on macOS. Run with: sudo ./xray", err) + case "freebsd": + return fmt.Errorf("%w\n Hint: rawpacket requires root on FreeBSD. Run with: sudo ./xray", err) + default: + return err + } +} diff --git a/transport/internet/finalmask/rawpacket/conn_test.go b/transport/internet/finalmask/rawpacket/conn_test.go new file mode 100644 index 000000000000..ab77ae584e26 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/conn_test.go @@ -0,0 +1,46 @@ +package rawpacket + +import ( + "testing" +) + +func TestParseMethod(t *testing.T) { + tests := []struct { + input string + expected Method + hasErr bool + }{ + {"", MethodWrongSequence, false}, + {"wrong-sequence", MethodWrongSequence, false}, + {"wrong-checksum", MethodWrongChecksum, false}, + {"wrong-ack", MethodWrongAcknowledgment, false}, + {"wrong-md5", MethodWrongMD5Sig, false}, + {"wrong-timestamp", MethodWrongTimestamp, false}, + {"invalid", 0, true}, + } + for _, tt := range tests { + m, err := ParseMethod(tt.input) + if tt.hasErr { + if err == nil { + t.Errorf("ParseMethod(%q): expected error, got nil", tt.input) + } + continue + } + if err != nil { + t.Errorf("ParseMethod(%q): unexpected error: %v", tt.input, err) + continue + } + if m != tt.expected { + t.Errorf("ParseMethod(%q) = %v, want %v", tt.input, m, tt.expected) + } + } +} + +func TestMethodString(t *testing.T) { + if MethodWrongSequence.String() != "wrong-sequence" { + t.Fatalf("unexpected method string: %s", MethodWrongSequence.String()) + } + if MethodWrongChecksum.String() != "wrong-checksum" { + t.Fatalf("unexpected method string: %s", MethodWrongChecksum.String()) + } +} From 480c2c7c8e553f36854ef82c629ad77b104494f4 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Sun, 10 May 2026 09:15:00 +0600 Subject: [PATCH 23/42] TLS: Remove spoof, spoof_method and spoof_count options --- transport/internet/tls/config.pb.go | 39 +- transport/internet/tls/config.proto | 7 - transport/internet/tls/tls.go | 33 - .../internet/tls/tlsspoof/client_hello.go | 75 -- transport/internet/tls/tlsspoof/endpoints.go | 27 - transport/internet/tls/tlsspoof/packet.go | 163 --- transport/internet/tls/tlsspoof/raw_darwin.go | 198 --- .../internet/tls/tlsspoof/raw_freebsd.go | 172 --- transport/internet/tls/tlsspoof/raw_linux.go | 166 --- transport/internet/tls/tlsspoof/raw_stub.go | 15 - transport/internet/tls/tlsspoof/raw_unix.go | 25 - .../internet/tls/tlsspoof/raw_windows.go | 234 ---- transport/internet/tls/tlsspoof/spoof.go | 182 --- .../tls/tlsspoof/spoof_freebsd_test.go | 82 -- transport/internet/tls/tlsspoof/spoof_test.go | 111 -- transport/internet/tls/tlsspoof/tcpip.go | 155 --- .../tls/tlsspoof/windivert/assets/LICENSE.txt | 1191 ----------------- .../tlsspoof/windivert/assets/WinDivert32.sys | Bin 79792 -> 0 bytes .../tlsspoof/windivert/assets/WinDivert64.sys | Bin 94144 -> 0 bytes .../tls/tlsspoof/windivert/assets_386.go | 14 - .../tls/tlsspoof/windivert/assets_amd64.go | 14 - .../tlsspoof/windivert/assets_unsupported.go | 7 - .../tls/tlsspoof/windivert/driver_windows.go | 211 --- .../internet/tls/tlsspoof/windivert/filter.go | 181 --- .../tls/tlsspoof/windivert/handle_windows.go | 323 ----- .../tls/tlsspoof/windivert/windivert.go | 78 -- 26 files changed, 5 insertions(+), 3698 deletions(-) delete mode 100644 transport/internet/tls/tlsspoof/client_hello.go delete mode 100644 transport/internet/tls/tlsspoof/endpoints.go delete mode 100644 transport/internet/tls/tlsspoof/packet.go delete mode 100644 transport/internet/tls/tlsspoof/raw_darwin.go delete mode 100644 transport/internet/tls/tlsspoof/raw_freebsd.go delete mode 100644 transport/internet/tls/tlsspoof/raw_linux.go delete mode 100644 transport/internet/tls/tlsspoof/raw_stub.go delete mode 100644 transport/internet/tls/tlsspoof/raw_unix.go delete mode 100644 transport/internet/tls/tlsspoof/raw_windows.go delete mode 100644 transport/internet/tls/tlsspoof/spoof.go delete mode 100644 transport/internet/tls/tlsspoof/spoof_freebsd_test.go delete mode 100644 transport/internet/tls/tlsspoof/spoof_test.go delete mode 100644 transport/internet/tls/tlsspoof/tcpip.go delete mode 100644 transport/internet/tls/tlsspoof/windivert/assets/LICENSE.txt delete mode 100644 transport/internet/tls/tlsspoof/windivert/assets/WinDivert32.sys delete mode 100644 transport/internet/tls/tlsspoof/windivert/assets/WinDivert64.sys delete mode 100644 transport/internet/tls/tlsspoof/windivert/assets_386.go delete mode 100644 transport/internet/tls/tlsspoof/windivert/assets_amd64.go delete mode 100644 transport/internet/tls/tlsspoof/windivert/assets_unsupported.go delete mode 100644 transport/internet/tls/tlsspoof/windivert/driver_windows.go delete mode 100644 transport/internet/tls/tlsspoof/windivert/filter.go delete mode 100644 transport/internet/tls/tlsspoof/windivert/handle_windows.go delete mode 100644 transport/internet/tls/tlsspoof/windivert/windivert.go diff --git a/transport/internet/tls/config.pb.go b/transport/internet/tls/config.pb.go index 700c70883ab1..5f7688a5c512 100644 --- a/transport/internet/tls/config.pb.go +++ b/transport/internet/tls/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v6.33.5 +// protoc v7.34.1 // source: transport/internet/tls/config.proto package tls @@ -209,12 +209,8 @@ type Config struct { EchForceQuery string `protobuf:"bytes,20,opt,name=ech_force_query,json=echForceQuery,proto3" json:"ech_force_query,omitempty"` EchSocketSettings *internet.SocketConfig `protobuf:"bytes,21,opt,name=ech_socket_settings,json=echSocketSettings,proto3" json:"ech_socket_settings,omitempty"` PinnedPeerCertSha256 [][]byte `protobuf:"bytes,22,rep,name=pinned_peer_cert_sha256,json=pinnedPeerCertSha256,proto3" json:"pinned_peer_cert_sha256,omitempty"` - Spoof string `protobuf:"bytes,23,opt,name=spoof,proto3" json:"spoof,omitempty"` - SpoofMethod string `protobuf:"bytes,24,opt,name=spoof_method,json=spoofMethod,proto3" json:"spoof_method,omitempty"` - // Number of times to inject the fake ClientHello (0 or 1 = single-shot). - SpoofCount int32 `protobuf:"varint,25,opt,name=spoof_count,json=spoofCount,proto3" json:"spoof_count,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Config) Reset() { @@ -380,27 +376,6 @@ func (x *Config) GetPinnedPeerCertSha256() [][]byte { return nil } -func (x *Config) GetSpoof() string { - if x != nil { - return x.Spoof - } - return "" -} - -func (x *Config) GetSpoofMethod() string { - if x != nil { - return x.SpoofMethod - } - return "" -} - -func (x *Config) GetSpoofCount() int32 { - if x != nil { - return x.SpoofCount - } - return 0 -} - var File_transport_internet_tls_config_proto protoreflect.FileDescriptor const file_transport_internet_tls_config_proto_rawDesc = "" + @@ -419,7 +394,7 @@ const file_transport_internet_tls_config_proto_rawDesc = "" + "\x05Usage\x12\x10\n" + "\fENCIPHERMENT\x10\x00\x12\x14\n" + "\x10AUTHORITY_VERIFY\x10\x01\x12\x13\n" + - "\x0fAUTHORITY_ISSUE\x10\x02\"\xcf\a\n" + + "\x0fAUTHORITY_ISSUE\x10\x02\"\xf5\x06\n" + "\x06Config\x12%\n" + "\x0eallow_insecure\x18\x01 \x01(\bR\rallowInsecure\x12J\n" + "\vcertificate\x18\x02 \x03(\v2(.xray.transport.internet.tls.CertificateR\vcertificate\x12\x1f\n" + @@ -442,11 +417,7 @@ const file_transport_internet_tls_config_proto_rawDesc = "" + "\x0fech_config_list\x18\x13 \x01(\tR\rechConfigList\x12&\n" + "\x0fech_force_query\x18\x14 \x01(\tR\rechForceQuery\x12U\n" + "\x13ech_socket_settings\x18\x15 \x01(\v2%.xray.transport.internet.SocketConfigR\x11echSocketSettings\x125\n" + - "\x17pinned_peer_cert_sha256\x18\x16 \x03(\fR\x14pinnedPeerCertSha256\x12\x14\n" + - "\x05spoof\x18\x17 \x01(\tR\x05spoof\x12!\n" + - "\fspoof_method\x18\x18 \x01(\tR\vspoofMethod\x12\x1f\n" + - "\vspoof_count\x18\x19 \x01(\x05R\n" + - "spoofCountBs\n" + + "\x17pinned_peer_cert_sha256\x18\x16 \x03(\fR\x14pinnedPeerCertSha256Bs\n" + "\x1fcom.xray.transport.internet.tlsP\x01Z0github.com/xtls/xray-core/transport/internet/tls\xaa\x02\x1bXray.Transport.Internet.Tlsb\x06proto3" var ( diff --git a/transport/internet/tls/config.proto b/transport/internet/tls/config.proto index 0039d0901a7d..4592822649c3 100644 --- a/transport/internet/tls/config.proto +++ b/transport/internet/tls/config.proto @@ -87,11 +87,4 @@ message Config { SocketConfig ech_socket_settings = 21; repeated bytes pinned_peer_cert_sha256 = 22; - - string spoof = 23; - - string spoof_method = 24; - - // Number of times to inject the fake ClientHello (0 or 1 = single-shot). - int32 spoof_count = 25; } diff --git a/transport/internet/tls/tls.go b/transport/internet/tls/tls.go index b8bc4102a31f..7fa3c25be55d 100644 --- a/transport/internet/tls/tls.go +++ b/transport/internet/tls/tls.go @@ -5,17 +5,13 @@ import ( "crypto/rand" "crypto/tls" "math/big" - gonet "net" "slices" - "strings" "time" utls "github.com/refraction-networking/utls" "github.com/xtls/xray-core/common/buf" - "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/utils" - "github.com/xtls/xray-core/transport/internet/tls/tlsspoof" ) type Interface interface { @@ -68,35 +64,6 @@ func Client(c net.Conn, config *tls.Config) net.Conn { return &Conn{Conn: tlsConn} } -// WrapWithSpoof wraps a connection with TLS spoofing if the config has -// spoof settings. The spoofed ClientHello is injected via raw sockets -// before the real TLS handshake, causing DPI middleboxes to see the -// forged SNI while the actual connection proceeds normally. -// spoofCount controls how many Write() calls trigger injection (0 = single-shot). -func WrapWithSpoof(c net.Conn, spoofSNI string, spoofMethodStr string, spoofCount int32, serverName string) (net.Conn, error) { - spoofSNI, method, err := tlsspoof.ParseOptions(spoofSNI, spoofMethodStr) - if err != nil { - return nil, errors.New("tls_spoof: invalid options").Base(err) - } - if spoofSNI == "" { - return c, nil - } - if serverName == "" { - return nil, errors.New("tls_spoof: requires a TLS server name (SNI)") - } - if gonet.ParseIP(serverName) != nil { - return nil, errors.New("tls_spoof: cannot spoof when server name is an IP literal") - } - if strings.EqualFold(spoofSNI, serverName) { - return nil, errors.New("tls_spoof: spoof must differ from server_name") - } - wrapped, err := tlsspoof.NewConn(c, method, spoofSNI, int(spoofCount)) - if err != nil { - return nil, errors.New("tls_spoof: failed to create spoof conn").Base(err) - } - return wrapped, nil -} - // Server initiates a TLS server handshake on the given connection. func Server(c net.Conn, config *tls.Config) net.Conn { tlsConn := tls.Server(c, config) diff --git a/transport/internet/tls/tlsspoof/client_hello.go b/transport/internet/tls/tlsspoof/client_hello.go deleted file mode 100644 index b078697c97cc..000000000000 --- a/transport/internet/tls/tlsspoof/client_hello.go +++ /dev/null @@ -1,75 +0,0 @@ -package tlsspoof - -import ( - "bytes" - "context" - "crypto/tls" - - "errors" - "net" - "time" -) - -type writeOnlyConn struct { - net.Conn - w *bytes.Buffer -} - -func (c *writeOnlyConn) Write(b []byte) (int, error) { - return c.w.Write(b) -} - -func (c *writeOnlyConn) Read(b []byte) (int, error) { - return 0, errors.New("read from write-only conn") -} - -func (c *writeOnlyConn) Close() error { - return nil -} - -func (c *writeOnlyConn) LocalAddr() net.Addr { - return nil -} - -func (c *writeOnlyConn) RemoteAddr() net.Addr { - return nil -} - -func (c *writeOnlyConn) SetDeadline(t time.Time) error { - return nil -} - -func (c *writeOnlyConn) SetReadDeadline(t time.Time) error { - return nil -} - -func (c *writeOnlyConn) SetWriteDeadline(t time.Time) error { - return nil -} - -// buildFakeClientHello drives crypto/tls against a write-only in-memory conn -// to capture a generated ClientHello. CurvePreferences pins classical groups -// to suppress Go's default X25519MLKEM768 hybrid key share; without this the -// post-quantum public key alone (~1184 bytes) pushes the record past one MSS, -// and middleboxes do not reassemble fragmented ClientHellos. The handshake -// error is discarded because the stub conn's Read returns immediately. -func buildFakeClientHello(sni string) ([]byte, error) { - if sni == "" { - return nil, errors.New("empty sni") - } - var buf bytes.Buffer - tlsConn := tls.Client(&writeOnlyConn{w: &buf}, &tls.Config{ - ServerName: sni, - // Order matches what browsers advertised before post-quantum. - CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP384}, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS13, - NextProtos: []string{"h2", "http/1.1"}, - InsecureSkipVerify: true, - }) - _ = tlsConn.HandshakeContext(context.Background()) - if buf.Len() == 0 { - return nil, errors.New("tls ClientHello not produced") - } - return buf.Bytes(), nil -} diff --git a/transport/internet/tls/tlsspoof/endpoints.go b/transport/internet/tls/tlsspoof/endpoints.go deleted file mode 100644 index ac0c30484226..000000000000 --- a/transport/internet/tls/tlsspoof/endpoints.go +++ /dev/null @@ -1,27 +0,0 @@ -package tlsspoof - -import ( - "net" - "net/netip" - - "errors" -) - -// The returned addresses are v4-unmapped and share the same family. -func tcpEndpoints(conn net.Conn) (*net.TCPConn, netip.AddrPort, netip.AddrPort, error) { - tcpConn, isTCP := conn.(*net.TCPConn) - if !isTCP { - return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("tls_spoof: underlying conn is not *net.TCPConn") - } - local := tcpConn.LocalAddr().(*net.TCPAddr).AddrPort() - remote := tcpConn.RemoteAddr().(*net.TCPAddr).AddrPort() - if !local.IsValid() || !remote.IsValid() { - return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("tls_spoof: invalid conn address") - } - local = netip.AddrPortFrom(local.Addr().Unmap(), local.Port()) - remote = netip.AddrPortFrom(remote.Addr().Unmap(), remote.Port()) - if local.Addr().Is4() != remote.Addr().Is4() { - return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("tls_spoof: local/remote address family mismatch") - } - return tcpConn, local, remote, nil -} diff --git a/transport/internet/tls/tlsspoof/packet.go b/transport/internet/tls/tlsspoof/packet.go deleted file mode 100644 index 5c23c0631ab8..000000000000 --- a/transport/internet/tls/tlsspoof/packet.go +++ /dev/null @@ -1,163 +0,0 @@ -package tlsspoof - -import ( - "encoding/binary" - "net/netip" - - "fmt" -) - -const ( - defaultTTL uint8 = 64 - defaultWindowSize uint16 = 0xFFFF - tcpHeaderLen = TCPMinimumSize - - tcpOptionMD5Signature = 19 - tcpOptionMD5SignatureLength = 18 - tcpTimestampBackdate = 3600000 -) - -type spoofPacketInfo struct { - seqNum uint32 - ackNum uint32 - corrupt bool - options []byte -} - -func buildTCPSegment( - src netip.AddrPort, - dst netip.AddrPort, - packetInfo spoofPacketInfo, - payload []byte, -) []byte { - if src.Addr().Is4() != dst.Addr().Is4() { - panic("tlsspoof: mixed IPv4/IPv6 address family") - } - var ( - frame []byte - ipHeaderLen int - ) - ipPayloadLen := tcpHeaderLen + len(packetInfo.options) + len(payload) - if src.Addr().Is4() { - ipHeaderLen = IPv4MinimumSize - frame = make([]byte, ipHeaderLen+ipPayloadLen) - ip := IPv4(frame[:ipHeaderLen]) - ip.Encode(uint16(len(frame)), 0, defaultTTL, TCPProtocolNumber, src.Addr(), dst.Addr()) - } else { - ipHeaderLen = IPv6MinimumSize - frame = make([]byte, ipHeaderLen+ipPayloadLen) - ip := IPv6(frame[:ipHeaderLen]) - ip.Encode(uint16(ipPayloadLen), TCPProtocolNumber, defaultTTL, src.Addr(), dst.Addr()) - } - encodeTCP(frame, ipHeaderLen, src, dst, packetInfo, payload) - return frame -} - -func encodeTCP(frame []byte, ipHeaderLen int, src, dst netip.AddrPort, packetInfo spoofPacketInfo, payload []byte) { - tcp := TCP(frame[ipHeaderLen:]) - copy(frame[ipHeaderLen+tcpHeaderLen:], packetInfo.options) - optionsLen := len(packetInfo.options) - copy(frame[ipHeaderLen+tcpHeaderLen+optionsLen:], payload) - tcp.Encode(src.Port(), dst.Port(), packetInfo.seqNum, packetInfo.ackNum, uint8(tcpHeaderLen+optionsLen), TCPFlagAck|TCPFlagPsh, defaultWindowSize) - applyTCPChecksum(tcp, src.Addr(), dst.Addr(), payload, packetInfo.corrupt) -} - -func buildSpoofFrame(method Method, src, dst netip.AddrPort, sendNext, receiveNext, timestamp uint32, tcpOptions, payload []byte) ([]byte, error) { - packetInfo, err := resolveSpoofPacketInfo(method, sendNext, receiveNext, timestamp, tcpOptions, payload) - if err != nil { - return nil, err - } - return buildTCPSegment(src, dst, packetInfo, payload), nil -} - -// buildSpoofTCPSegment returns a TCP segment without an IP header, for -// platforms where the kernel synthesises the IP header (darwin IPv6). -func buildSpoofTCPSegment(method Method, src, dst netip.AddrPort, sendNext, receiveNext, timestamp uint32, payload []byte) ([]byte, error) { - packetInfo, err := resolveSpoofPacketInfo(method, sendNext, receiveNext, timestamp, nil, payload) - if err != nil { - return nil, err - } - segment := make([]byte, tcpHeaderLen+len(packetInfo.options)+len(payload)) - encodeTCP(segment, 0, src, dst, packetInfo, payload) - return segment, nil -} - -func resolveSpoofPacketInfo(method Method, sendNext, receiveNext, timestamp uint32, tcpOptions, payload []byte) (spoofPacketInfo, error) { - packetInfo := spoofPacketInfo{seqNum: sendNext, ackNum: receiveNext} - switch method { - case MethodWrongSequence: - packetInfo.seqNum = sendNext - uint32(len(payload)) - case MethodWrongChecksum: - packetInfo.corrupt = true - case MethodWrongAcknowledgment: - packetInfo.ackNum = receiveNext - uint32(defaultWindowSize/2) - case MethodWrongMD5Sig: - packetInfo.options = buildMD5SignatureOptions() - case MethodWrongTimestamp: - packetInfo.options = buildWrongTimestampOptions(timestamp, tcpOptions) - default: - return packetInfo, fmt.Errorf("tls_spoof: unknown method %v", method) - } - return packetInfo, nil -} - -func buildMD5SignatureOptions() []byte { - options := make([]byte, tcpOptionMD5SignatureLength+2) - options[0] = tcpOptionMD5Signature - options[1] = tcpOptionMD5SignatureLength - return options -} - -func buildWrongTimestampOptions(timestamp uint32, tcpOptions []byte) []byte { - spoofedTimestamp := timestamp - if spoofedTimestamp > tcpTimestampBackdate { - spoofedTimestamp -= tcpTimestampBackdate - } else { - spoofedTimestamp = 0 - } - if rewriteTCPOptionTimestamp(tcpOptions, spoofedTimestamp) { - return tcpOptions - } - options := make([]byte, TCPOptionTSLength+2) - EncodeTSOption(spoofedTimestamp, 0, options) - return options -} - -// rewriteTCPOptionTimestamp finds the TS option in tcpOptions and writes -// timestamp into its TSVal field in place. The caller must own tcpOptions -// (parseTCPPacket already returns a private copy on Windows). -func rewriteTCPOptionTimestamp(tcpOptions []byte, timestamp uint32) bool { - for i := 0; i < len(tcpOptions); { - switch tcpOptions[i] { - case TCPOptionEOL: - return false - case TCPOptionNOP: - i++ - continue - } - if i+1 >= len(tcpOptions) { - return false - } - optionLen := int(tcpOptions[i+1]) - if optionLen < 2 || i+optionLen > len(tcpOptions) { - return false - } - if tcpOptions[i] == TCPOptionTS && optionLen == TCPOptionTSLength { - binary.BigEndian.PutUint32(tcpOptions[i+2:], timestamp) - return true - } - i += optionLen - } - return false -} - -func applyTCPChecksum(tcp TCP, srcAddr, dstAddr netip.Addr, payload []byte, corrupt bool) { - tcpLen := int(tcp.DataOffset()) + len(payload) - pseudo := PseudoHeaderChecksum(TCPProtocolNumber, srcAddr.AsSlice(), dstAddr.AsSlice(), uint16(tcpLen)) - payloadChecksum := Checksum(payload, 0) - tcpChecksum := ^tcp.CalculateChecksum(CombineChecksum(pseudo, payloadChecksum)) - if corrupt { - tcpChecksum ^= 0xFFFF - } - tcp.SetChecksum(tcpChecksum) -} diff --git a/transport/internet/tls/tlsspoof/raw_darwin.go b/transport/internet/tls/tlsspoof/raw_darwin.go deleted file mode 100644 index 3b45d17023be..000000000000 --- a/transport/internet/tls/tlsspoof/raw_darwin.go +++ /dev/null @@ -1,198 +0,0 @@ -package tlsspoof - -import ( - "encoding/binary" - "net" - "net/netip" - "strconv" - "strings" - "sync" - "syscall" - - "errors" - "fmt" - - "golang.org/x/sys/unix" -) - -const PlatformSupported = true - -// Offsets into xinpcb_n within each net.inet.tcp.pcblist_n record, identical -// to the values used by common/process/searcher_darwin_shared.go. -const ( - darwinXinpgenSize = 24 - darwinXsocketOffset = 104 - darwinXinpcbForeignPort = 16 - darwinXinpcbLocalPort = 18 - darwinXinpcbVFlag = 44 - darwinXinpcbForeignAddr = 48 - darwinXinpcbLocalAddr = 64 - darwinXinpcbIPv4Offset = 12 - - darwinTCPExtraSize = 208 - - darwinXtcpcbSndNxtOffset = 56 - darwinXtcpcbRcvNxtOffset = 80 -) - -// darwinStructSize returns the size of xinpcb_n for the running Darwin kernel. -// Darwin 22 (macOS 13 Ventura) grew the struct from 384 to 408 bytes; there is -// no ABI-stable way to read it, so we key off the kernel version. -var darwinStructSize = sync.OnceValues(func() (int, error) { - value, err := syscall.Sysctl("kern.osrelease") - if err != nil { - return 0, func(err error, m string) error { return err }(err, "sysctl kern.osrelease") - } - major, _, ok := strings.Cut(value, ".") - if !ok { - return 0, fmt.Errorf("unexpected kern.osrelease format: %s", value) - } - n, err := strconv.ParseInt(major, 10, 64) - if err != nil { - return 0, func(err error, m string) error { return err }(err, "parse kern.osrelease major version: ") - } - if n >= 22 { - return 408, nil - } - return 384, nil -}) - -type darwinSpoofer struct { - method Method - src netip.AddrPort - dst netip.AddrPort - rawFD int - rawSockAddr unix.Sockaddr - sendNext uint32 - receiveNext uint32 -} - -func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { - if method == MethodWrongTimestamp { - return nil, errors.New("tls_spoof: wrong-timestamp is not supported on macOS") - } - _, src, dst, err := tcpEndpoints(conn) - if err != nil { - return nil, err - } - fd, sockaddr, err := openDarwinRawSocket(src, dst) - if err != nil { - return nil, err - } - sendNext, receiveNext, err := readDarwinTCPSequence(src, dst) - if err != nil { - unix.Close(fd) - return nil, err - } - return &darwinSpoofer{ - method: method, - src: src, - dst: dst, - rawFD: fd, - rawSockAddr: sockaddr, - sendNext: sendNext, - receiveNext: receiveNext, - }, nil -} - -// readDarwinTCPSequence scans net.inet.tcp.pcblist_n for the PCB that matches -// src -> dst and returns (snd_nxt, rcv_nxt). These live in xtcpcb_n at the end -// of each record; see darwin-xnu bsd/netinet/in_pcblist.c:get_pcblist_n. -func readDarwinTCPSequence(src, dst netip.AddrPort) (uint32, uint32, error) { - buffer, err := unix.SysctlRaw("net.inet.tcp.pcblist_n") - if err != nil { - return 0, 0, func(err error, m string) error { return err }(err, "sysctl net.inet.tcp.pcblist_n") - } - structSize, err := darwinStructSize() - if err != nil { - return 0, 0, err - } - itemSize := structSize + darwinTCPExtraSize - for i := darwinXinpgenSize; i+itemSize <= len(buffer); i += itemSize { - inpcb := buffer[i : i+darwinXsocketOffset] - xtcpcb := buffer[i+structSize : i+itemSize] - localPort := binary.BigEndian.Uint16(inpcb[darwinXinpcbLocalPort : darwinXinpcbLocalPort+2]) - remotePort := binary.BigEndian.Uint16(inpcb[darwinXinpcbForeignPort : darwinXinpcbForeignPort+2]) - if localPort != src.Port() || remotePort != dst.Port() { - continue - } - versionFlag := inpcb[darwinXinpcbVFlag] - var localAddr, remoteAddr netip.Addr - switch { - case versionFlag&0x1 != 0: - localAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset : darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset+4])) - remoteAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset : darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset+4])) - case versionFlag&0x2 != 0: - localAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbLocalAddr : darwinXinpcbLocalAddr+16])) - remoteAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbForeignAddr : darwinXinpcbForeignAddr+16])) - default: - continue - } - if localAddr.Unmap() != src.Addr() || remoteAddr.Unmap() != dst.Addr() { - continue - } - sendNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbSndNxtOffset : darwinXtcpcbSndNxtOffset+4]) - receiveNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbRcvNxtOffset : darwinXtcpcbRcvNxtOffset+4]) - return sendNext, receiveNext, nil - } - return 0, 0, fmt.Errorf("tls_spoof: connection %v->%v not found in pcblist_n", src, dst) -} - -func openDarwinRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) { - if dst.Addr().Is4() { - return openIPv4RawSocket(dst) - } - // macOS does not accept IPV6_HDRINCL on AF_INET6 SOCK_RAW IPPROTO_TCP - // sockets, so the kernel builds the IPv6 header itself. Bind to the real - // connection's source address so in6_selectsrc returns it, and rely on - // in6p_cksum defaulting to -1 so the user-supplied TCP checksum is - // preserved (including deliberately corrupted ones). - fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) - if err != nil { - return -1, nil, func(err error, m string) error { return err }(err, "open AF_INET6 SOCK_RAW") - } - err = unix.Bind(fd, &unix.SockaddrInet6{Addr: src.Addr().As16()}) - if err != nil { - unix.Close(fd) - return -1, nil, func(err error, m string) error { return err }(err, "bind AF_INET6 SOCK_RAW") - } - sockaddr := &unix.SockaddrInet6{Port: int(dst.Port()), Addr: dst.Addr().As16()} - return fd, sockaddr, nil -} - -func (s *darwinSpoofer) Inject(payload []byte) error { - if !s.src.Addr().Is4() { - segment, err := buildSpoofTCPSegment(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, payload) - if err != nil { - return err - } - err = unix.Sendto(s.rawFD, segment, 0, s.rawSockAddr) - if err != nil { - return func(err error, m string) error { return err }(err, "sendto raw socket") - } - return nil - } - frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, nil, payload) - if err != nil { - return err - } - // Darwin inherits the historical BSD quirk: with IP_HDRINCL the kernel - // expects ip_len and ip_off in host byte order, not network byte order. - ip := IPv4(frame) - binary.NativeEndian.PutUint16(ip[2:4], ip.TotalLength()) - binary.NativeEndian.PutUint16(ip[6:8], uint16(ip.Flags())<<13|ip.FragmentOffset()) - err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) - if err != nil { - return func(err error, m string) error { return err }(err, "sendto raw socket") - } - return nil -} - -func (s *darwinSpoofer) Close() error { - if s.rawFD < 0 { - return nil - } - err := unix.Close(s.rawFD) - s.rawFD = -1 - return err -} diff --git a/transport/internet/tls/tlsspoof/raw_freebsd.go b/transport/internet/tls/tlsspoof/raw_freebsd.go deleted file mode 100644 index c38a249bf721..000000000000 --- a/transport/internet/tls/tlsspoof/raw_freebsd.go +++ /dev/null @@ -1,172 +0,0 @@ -package tlsspoof - -import ( - "encoding/binary" - "errors" - "fmt" - "net" - "net/netip" - "syscall" - "unsafe" - - "golang.org/x/sys/unix" -) - -const PlatformSupported = true - -// FreeBSD tcp_info offsets for snd_nxt and rcv_nxt. -// Derived from FreeBSD sys/netinet/tcp.h struct tcp_info layout. -// -// struct tcp_info { -// u8 state, __ca, __retrans, __probes, __backoff, opts, wscale = 8 bytes (with pad) -// u32 rto, __ato, snd_mss, rcv_mss = 16 bytes (offset 8) -// u32 __unacked, __sacked, __lost, __retrans, __fackets = 20 bytes (offset 24) -// u32 __last_data_sent, __last_ack_sent, last_data_recv, __last_ack_recv = 16 bytes (offset 44) -// u32 __pmtu, __rcv_ssthresh, rtt, rttvar, snd_ssthresh, snd_cwnd, __advmss, __reordering = 32 bytes (offset 60) -// u32 __rcv_rtt, rcv_space = 8 bytes (offset 92) -// u32 snd_wnd, snd_bwnd = 8 bytes (offset 100) -// u32 snd_nxt, rcv_nxt = 8 bytes (offset 108) -// ... remaining fields -// } -const ( - freebsdTCPInfoSndNxtOffset = 108 - freebsdTCPInfoRcvNxtOffset = 112 - freebsdTCPInfoMinSize = 116 // must read at least through rcv_nxt -) - -type freebsdSpoofer struct { - method Method - src netip.AddrPort - dst netip.AddrPort - rawFD int - rawSockAddr unix.Sockaddr - sendNext uint32 - receiveNext uint32 -} - -func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { - if method == MethodWrongTimestamp { - return nil, errors.New("tls_spoof: wrong-timestamp is not supported on FreeBSD") - } - tcpConn, src, dst, err := tcpEndpoints(conn) - if err != nil { - return nil, err - } - fd, sockaddr, err := openFreeBSDRawSocket(src, dst) - if err != nil { - return nil, err - } - sendNext, receiveNext, err := readFreeBSDTCPSequence(tcpConn) - if err != nil { - unix.Close(fd) - return nil, err - } - return &freebsdSpoofer{ - method: method, - src: src, - dst: dst, - rawFD: fd, - rawSockAddr: sockaddr, - sendNext: sendNext, - receiveNext: receiveNext, - }, nil -} - -// readFreeBSDTCPSequence retrieves snd_nxt and rcv_nxt via TCP_INFO getsockopt. -func readFreeBSDTCPSequence(conn *net.TCPConn) (uint32, uint32, error) { - raw, err := conn.SyscallConn() - if err != nil { - return 0, 0, fmt.Errorf("tls_spoof: SyscallConn: %w", err) - } - var sendNext, receiveNext uint32 - var sockErr error - err = raw.Control(func(fd uintptr) { - buf := make([]byte, 256) // generous buffer for tcp_info - bufLen := uint32(len(buf)) - _, _, errno := syscall.Syscall6( - syscall.SYS_GETSOCKOPT, - fd, - uintptr(syscall.IPPROTO_TCP), - uintptr(0x20), // TCP_INFO = 0x20 - uintptr(unsafe.Pointer(&buf[0])), - uintptr(unsafe.Pointer(&bufLen)), - 0, - ) - if errno != 0 { - sockErr = fmt.Errorf("tls_spoof: getsockopt TCP_INFO: %w", errno) - return - } - if bufLen < freebsdTCPInfoMinSize { - sockErr = fmt.Errorf("tls_spoof: TCP_INFO too short: %d < %d", bufLen, freebsdTCPInfoMinSize) - return - } - sendNext = binary.NativeEndian.Uint32(buf[freebsdTCPInfoSndNxtOffset : freebsdTCPInfoSndNxtOffset+4]) - receiveNext = binary.NativeEndian.Uint32(buf[freebsdTCPInfoRcvNxtOffset : freebsdTCPInfoRcvNxtOffset+4]) - }) - if err != nil { - return 0, 0, err - } - if sockErr != nil { - return 0, 0, sockErr - } - return sendNext, receiveNext, nil -} - -func openFreeBSDRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) { - if dst.Addr().Is4() { - return openIPv4RawSocket(dst) - } - // FreeBSD, like macOS, does not support IPV6_HDRINCL on SOCK_RAW/IPPROTO_TCP. - // The kernel constructs the IPv6 header. Bind to the source address - // and let the kernel fill in the IPv6 header automatically. - fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) - if err != nil { - return -1, nil, fmt.Errorf("tls_spoof: open AF_INET6 SOCK_RAW: %w", err) - } - err = unix.Bind(fd, &unix.SockaddrInet6{Addr: src.Addr().As16()}) - if err != nil { - unix.Close(fd) - return -1, nil, fmt.Errorf("tls_spoof: bind AF_INET6 SOCK_RAW: %w", err) - } - sockaddr := &unix.SockaddrInet6{Port: int(dst.Port()), Addr: dst.Addr().As16()} - return fd, sockaddr, nil -} - -func (s *freebsdSpoofer) Inject(payload []byte) error { - if !s.src.Addr().Is4() { - // IPv6: kernel builds the IP header, we supply TCP segment only. - segment, err := buildSpoofTCPSegment(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, payload) - if err != nil { - return err - } - err = unix.Sendto(s.rawFD, segment, 0, s.rawSockAddr) - if err != nil { - return fmt.Errorf("tls_spoof: sendto raw socket: %w", err) - } - return nil - } - // IPv4: we build the full IP+TCP frame with IP_HDRINCL. - frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, nil, payload) - if err != nil { - return err - } - // FreeBSD inherits the historical BSD quirk: with IP_HDRINCL the kernel - // expects ip_len and ip_off in host byte order, not network byte order. - ip := IPv4(frame) - binary.NativeEndian.PutUint16(ip[2:4], ip.TotalLength()) - binary.NativeEndian.PutUint16(ip[6:8], uint16(ip.Flags())<<13|ip.FragmentOffset()) - err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) - if err != nil { - return fmt.Errorf("tls_spoof: sendto raw socket: %w", err) - } - return nil -} - -func (s *freebsdSpoofer) Close() error { - if s.rawFD < 0 { - return nil - } - err := unix.Close(s.rawFD) - s.rawFD = -1 - return err -} diff --git a/transport/internet/tls/tlsspoof/raw_linux.go b/transport/internet/tls/tlsspoof/raw_linux.go deleted file mode 100644 index dc5c7311869c..000000000000 --- a/transport/internet/tls/tlsspoof/raw_linux.go +++ /dev/null @@ -1,166 +0,0 @@ -package tlsspoof - -import ( - "fmt" - "net" - "net/netip" - - "golang.org/x/sys/unix" -) - -const PlatformSupported = true - -const ( - // Values of enum { TCP_NO_QUEUE, TCP_RECV_QUEUE, TCP_SEND_QUEUE } from - // include/net/tcp.h; not exported by golang.org/x/sys/unix. - tcpRecvQueue = 1 - tcpSendQueue = 2 -) - -type linuxSpoofer struct { - method Method - src netip.AddrPort - dst netip.AddrPort - rawFD int - rawSockAddr unix.Sockaddr - sendNext uint32 - receiveNext uint32 - timestamp uint32 -} - -func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { - tcpConn, src, dst, err := tcpEndpoints(conn) - if err != nil { - return nil, err - } - fd, sockaddr, err := openLinuxRawSocket(dst) - if err != nil { - return nil, err - } - spoofer := &linuxSpoofer{ - method: method, - src: src, - dst: dst, - rawFD: fd, - rawSockAddr: sockaddr, - } - err = spoofer.loadSequenceNumbers(tcpConn) - if err != nil { - unix.Close(fd) - return nil, err - } - return spoofer, nil -} - -func openLinuxRawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) { - if dst.Addr().Is4() { - return openIPv4RawSocket(dst) - } - fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) - if err != nil { - return -1, nil, func(err error, m string) error { return err }(err, "open AF_INET6 SOCK_RAW") - } - err = unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_HDRINCL, 1) - if err != nil { - unix.Close(fd) - return -1, nil, func(err error, m string) error { return err }(err, "set IPV6_HDRINCL") - } - // Linux raw IPv6 sockets interpret sin6_port as a nexthdr protocol number - // (see raw(7)); any value other than 0 or the socket's IPPROTO_TCP causes - // sendto to fail with EINVAL. The destination is already encoded in the - // user-supplied IPv6 header under IPV6_HDRINCL. - sockaddr := &unix.SockaddrInet6{Addr: dst.Addr().As16()} - return fd, sockaddr, nil -} - -// loadSequenceNumbers puts the socket briefly into TCP_REPAIR mode to read -// snd_nxt and rcv_nxt from the kernel. TCP_REPAIR requires CAP_NET_ADMIN; -// callers must run as root or grant both CAP_NET_RAW and CAP_NET_ADMIN. -// -// If the TCP_REPAIR_OFF revert fails, the socket would stay in TCP_REPAIR -// state and subsequent Write() calls would silently buffer instead of sending. -// Surface that error so callers can abort. -func (s *linuxSpoofer) loadSequenceNumbers(tcpConn *net.TCPConn) error { - rawConn, err := tcpConn.SyscallConn() - if err != nil { - return err - } - var ctrlErr error - err = rawConn.Control(func(raw uintptr) { - fd := int(raw) - - if s.method == MethodWrongTimestamp { - timestamp, tsErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_TIMESTAMP) - if tsErr != nil { - ctrlErr = fmt.Errorf("tls_spoof: read timestamp: %w", tsErr) - return - } - s.timestamp = uint32(timestamp) - } - - ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR, unix.TCP_REPAIR_ON) - if ctrlErr != nil { - ctrlErr = fmt.Errorf("tls_spoof: enter TCP_REPAIR (need CAP_NET_ADMIN): %w", ctrlErr) - return - } - defer func() { - offErr := unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR, unix.TCP_REPAIR_OFF) - if offErr != nil { - offErr = fmt.Errorf("tls_spoof: leave TCP_REPAIR: %w", offErr) - if ctrlErr == nil { - ctrlErr = offErr - } else { - ctrlErr = fmt.Errorf("%v; also %w", ctrlErr, offErr) - } - } - }() - - ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR_QUEUE, tcpSendQueue) - if ctrlErr != nil { - ctrlErr = fmt.Errorf("tls_spoof: select TCP_SEND_QUEUE: %w", ctrlErr) - return - } - sendSequence, seqErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_QUEUE_SEQ) - if seqErr != nil { - ctrlErr = fmt.Errorf("tls_spoof: read send queue sequence: %w", seqErr) - return - } - ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR_QUEUE, tcpRecvQueue) - if ctrlErr != nil { - ctrlErr = fmt.Errorf("tls_spoof: select TCP_RECV_QUEUE: %w", ctrlErr) - return - } - receiveSequence, seqErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_QUEUE_SEQ) - if seqErr != nil { - ctrlErr = fmt.Errorf("tls_spoof: read recv queue sequence: %w", seqErr) - return - } - s.sendNext = uint32(sendSequence) - s.receiveNext = uint32(receiveSequence) - }) - if err != nil { - return err - } - return ctrlErr -} - -func (s *linuxSpoofer) Inject(payload []byte) error { - frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, s.timestamp, nil, payload) - if err != nil { - return err - } - err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) - if err != nil { - return func(err error, m string) error { return err }(err, "sendto raw socket") - } - return nil -} - -func (s *linuxSpoofer) Close() error { - if s.rawFD < 0 { - return nil - } - err := unix.Close(s.rawFD) - s.rawFD = -1 - return err -} diff --git a/transport/internet/tls/tlsspoof/raw_stub.go b/transport/internet/tls/tlsspoof/raw_stub.go deleted file mode 100644 index 78be3c23391d..000000000000 --- a/transport/internet/tls/tlsspoof/raw_stub.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build !linux && !darwin && !freebsd && !(windows && (amd64 || 386)) - -package tlsspoof - -import ( - "net" - - "errors" -) - -const PlatformSupported = false - -func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { - return nil, errors.New("tls_spoof: unsupported platform") -} diff --git a/transport/internet/tls/tlsspoof/raw_unix.go b/transport/internet/tls/tlsspoof/raw_unix.go deleted file mode 100644 index ae6c8b9f8b04..000000000000 --- a/transport/internet/tls/tlsspoof/raw_unix.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build linux || darwin || freebsd - -package tlsspoof - -import ( - "net/netip" - - - "golang.org/x/sys/unix" -) - -func openIPv4RawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) { - fd, err := unix.Socket(unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_TCP) - if err != nil { - return -1, nil, func(err error, m string) error { return err }(err, "open AF_INET SOCK_RAW") - } - err = unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_HDRINCL, 1) - if err != nil { - unix.Close(fd) - return -1, nil, func(err error, m string) error { return err }(err, "set IP_HDRINCL") - } - sockaddr := &unix.SockaddrInet4{Port: int(dst.Port())} - sockaddr.Addr = dst.Addr().As4() - return fd, sockaddr, nil -} diff --git a/transport/internet/tls/tlsspoof/raw_windows.go b/transport/internet/tls/tlsspoof/raw_windows.go deleted file mode 100644 index 17878ffce3dd..000000000000 --- a/transport/internet/tls/tlsspoof/raw_windows.go +++ /dev/null @@ -1,234 +0,0 @@ -//go:build windows && (amd64 || 386) - -package tlsspoof - -import ( - "errors" - "net" - "net/netip" - "slices" - "sync" - "sync/atomic" - "time" - - "github.com/xtls/xray-core/transport/internet/tls/tlsspoof/windivert" - "golang.org/x/sys/windows" -) - -const PlatformSupported = true - -// closeGracePeriod caps how long Close() waits for the divert goroutine to -// observe the kernel-emitted real ClientHello and perform the reorder -// (fake → real). In practice this completes in microseconds; the cap -// bounds the pathological case where the kernel buffers the packet. -const closeGracePeriod = 2 * time.Second - -// windowsSpoofer uses a single WinDivert handle for both capture and -// injection. Sequential Send() calls on one handle traverse one driver queue, -// so the fake provably precedes the released real on the wire — a guarantee -// two separate handles cannot make because cross-handle order depends on the -// scheduler. -type windowsSpoofer struct { - method Method - src, dst netip.AddrPort - divertH *windivert.Handle - - fakeReady chan []byte // buffered(1): staged by Inject - done chan struct{} // closed by run() on exit - closeOnce sync.Once - runErr atomic.Pointer[error] -} - -func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { - _, src, dst, err := tcpEndpoints(conn) - if err != nil { - return nil, err - } - filter, err := windivert.OutboundTCP(src, dst) - if err != nil { - return nil, err - } - divertH, err := windivert.Open(filter, windivert.LayerNetwork, 0, 0) - if err != nil { - return nil, err - } - s := &windowsSpoofer{ - method: method, - src: src, - dst: dst, - divertH: divertH, - fakeReady: make(chan []byte, 1), - done: make(chan struct{}), - } - go s.run() - return s, nil -} - -func (s *windowsSpoofer) Inject(payload []byte) error { - select { - case s.fakeReady <- payload: - return nil - case <-s.done: - if p := s.runErr.Load(); p != nil { - return *p - } - return errors.New("tls_spoof: spoofer closed before Inject") - } -} - -func (s *windowsSpoofer) Close() error { - s.closeOnce.Do(func() { - // Give run() a grace window to finish handling the real packet. - select { - case <-s.done: - case <-time.After(closeGracePeriod): - // Force Recv() to return by closing the divert handle. - s.divertH.Close() - <-s.done - } - }) - if p := s.runErr.Load(); p != nil { - return *p - } - return nil -} - -func (s *windowsSpoofer) recordErr(err error) { s.runErr.Store(&err) } - -func (s *windowsSpoofer) run() { - defer close(s.done) - defer s.divertH.Close() - - buf := make([]byte, windivert.MTUMax) - for { - n, addr, err := s.divertH.Recv(buf) - if err != nil { - if errors.Is(err, windows.ERROR_OPERATION_ABORTED) || - errors.Is(err, windows.ERROR_NO_DATA) { - return - } - s.recordErr(err) - return - } - pkt := buf[:n] - seq, ack, tcpOptions, payloadLen, ok := parseTCPPacket(pkt, addr.IPv6()) - if !ok { - // Our filter is OutboundTCP(src, dst); a non-TCP or truncated - // match means driver state is suspect. Re-inject so the kernel - // still sees the byte stream, then abort — continuing would risk - // reordering against an unknown reference point. - _, sendErr := s.divertH.Send(pkt, &addr) - if sendErr != nil { - s.recordErr(sendErr) - return - } - s.recordErr(errors.New("windivert received malformed packet matching spoof filter")) - return - } - if payloadLen == 0 { - // Handshake ACK, keepalive, FIN — pass through unchanged. - _, err := s.divertH.Send(pkt, &addr) - if err != nil { - s.recordErr(err) - return - } - continue - } - - // Non-empty outbound TCP payload = the real ClientHello. - var fake []byte - select { - case fake = <-s.fakeReady: - default: - // Inject() not yet called — pass through and keep observing. - _, err := s.divertH.Send(pkt, &addr) - if err != nil { - s.recordErr(err) - return - } - continue - } - - var timestamp uint32 - if tsVal, hasTS := ParseTCPOptions(tcpOptions); hasTS { - timestamp = tsVal - } - frame, err := buildSpoofFrame(s.method, s.src, s.dst, seq, ack, timestamp, tcpOptions, fake) - if err != nil { - s.recordErr(err) - return - } - fakeAddr := addr // inherit Outbound, IfIdx - // buildSpoofFrame emits ready-to-wire bytes. The driver recomputes - // checksums on Send when TCPChecksum/IPChecksum are 0 — which would - // overwrite the intentionally corrupt checksum in WrongChecksum mode. - // Force both to 1 to keep our bytes intact. - fakeAddr.SetIPChecksum(true) - fakeAddr.SetTCPChecksum(true) - _, err = s.divertH.Send(frame, &fakeAddr) - if err != nil { - s.recordErr(err) - return - } - _, err = s.divertH.Send(pkt, &addr) - if err != nil { - s.recordErr(err) - return - } - return // single-shot reorder complete - } -} - -func parseTCPPacket(pkt []byte, isV6 bool) (seq, ack uint32, options []byte, payloadLen int, ok bool) { - if isV6 { - if len(pkt) < IPv6MinimumSize+TCPMinimumSize { - return 0, 0, nil, 0, false - } - ip := IPv6(pkt) - if ip.TransportProtocol() != TCPProtocolNumber { - return 0, 0, nil, 0, false - } - tcp := TCP(pkt[IPv6MinimumSize:]) - tcpHdr := int(tcp.DataOffset()) - if tcpHdr < TCPMinimumSize || IPv6MinimumSize+tcpHdr > len(pkt) { - return 0, 0, nil, 0, false - } - total := IPv6MinimumSize + int(ip.PayloadLength()) - if total == IPv6MinimumSize || total > len(pkt) { - total = len(pkt) - } - if total < IPv6MinimumSize+tcpHdr { - return 0, 0, nil, 0, false - } - return tcp.SequenceNumber(), tcp.AckNumber(), slices.Clone(tcp.Options()), - total - IPv6MinimumSize - tcpHdr, true - } - if len(pkt) < IPv4MinimumSize+TCPMinimumSize { - return 0, 0, nil, 0, false - } - ip := IPv4(pkt) - if ip.Protocol() != TCPProtocolNumber { - return 0, 0, nil, 0, false - } - ihl := int(ip.HeaderLength()) - // ihl+TCPMinimumSize guards the TCP-header field reads below; without - // this, an IPv4 packet with options (ihl>20) against a 40-byte buffer - // reads past the TCP slice when calling DataOffset. - if ihl < IPv4MinimumSize || ihl+TCPMinimumSize > len(pkt) { - return 0, 0, nil, 0, false - } - tcp := TCP(pkt[ihl:]) - tcpHdr := int(tcp.DataOffset()) - if tcpHdr < TCPMinimumSize || ihl+tcpHdr > len(pkt) { - return 0, 0, nil, 0, false - } - total := int(ip.TotalLength()) - if total == 0 || total > len(pkt) { - total = len(pkt) - } - if total < ihl+tcpHdr { - return 0, 0, nil, 0, false - } - return tcp.SequenceNumber(), tcp.AckNumber(), slices.Clone(tcp.Options()), - total - ihl - tcpHdr, true -} diff --git a/transport/internet/tls/tlsspoof/spoof.go b/transport/internet/tls/tlsspoof/spoof.go deleted file mode 100644 index 6a9eae93a45b..000000000000 --- a/transport/internet/tls/tlsspoof/spoof.go +++ /dev/null @@ -1,182 +0,0 @@ -package tlsspoof - -import ( - "errors" - "fmt" - "net" - "runtime" - "syscall" -) - -type Method int - -const ( - MethodWrongSequence Method = iota - MethodWrongChecksum - MethodWrongAcknowledgment - MethodWrongMD5Sig - MethodWrongTimestamp -) - -const ( - MethodNameWrongSequence = "wrong-sequence" - MethodNameWrongChecksum = "wrong-checksum" - MethodNameWrongAcknowledgment = "wrong-ack" - MethodNameWrongMD5Sig = "wrong-md5" - MethodNameWrongTimestamp = "wrong-timestamp" -) - -func ParseOptions(spoof, method string) (string, Method, error) { - if spoof == "" { - if method != "" { - return "", 0, errors.New("spoof_method requires spoof") - } - return "", 0, nil - } - if net.ParseIP(spoof) != nil { - return "", 0, errors.New("tls_spoof: IP-literal server names are not allowed") - } - if !PlatformSupported { - return "", 0, errors.New("tls_spoof is not supported on this platform") - } - parsedMethod, err := ParseMethod(method) - if err != nil { - return "", 0, err - } - return spoof, parsedMethod, nil -} - -func ParseMethod(s string) (Method, error) { - switch s { - case "", MethodNameWrongSequence: - return MethodWrongSequence, nil - case MethodNameWrongChecksum: - return MethodWrongChecksum, nil - case MethodNameWrongAcknowledgment: - return MethodWrongAcknowledgment, nil - case MethodNameWrongMD5Sig: - return MethodWrongMD5Sig, nil - case MethodNameWrongTimestamp: - return MethodWrongTimestamp, nil - default: - return 0, fmt.Errorf("tls_spoof: unknown method: %s", s) - } -} - -func (m Method) String() string { - switch m { - case MethodWrongSequence: - return MethodNameWrongSequence - case MethodWrongChecksum: - return MethodNameWrongChecksum - case MethodWrongAcknowledgment: - return MethodNameWrongAcknowledgment - case MethodWrongMD5Sig: - return MethodNameWrongMD5Sig - case MethodWrongTimestamp: - return MethodNameWrongTimestamp - default: - return "unknown" - } -} - -type rawSpoofer interface { - Inject(payload []byte) error - Close() error -} - -type Conn struct { - net.Conn - spoofer rawSpoofer - fakeHello []byte - injectionCount int - maxInjections int // how many times to inject; default 1 -} - -// NewConn wraps a connection with TLS spoofing. maxInjections controls how -// many Write() calls will trigger a fake ClientHello injection (0 or 1 = single-shot). -func NewConn(conn net.Conn, method Method, fakeSNI string, maxInjections int) (*Conn, error) { - spoofer, err := newRawSpoofer(conn, method) - if err != nil { - return nil, wrapPermissionError(err) - } - result, err := newConn(conn, spoofer, fakeSNI, maxInjections) - if err != nil { - spoofer.Close() - return nil, err - } - return result, nil -} - -func newConn(conn net.Conn, spoofer rawSpoofer, fakeSNI string, maxInjections int) (*Conn, error) { - fakeHello, err := buildFakeClientHello(fakeSNI) - if err != nil { - return nil, func(err error, m string) error { return err }(err, "tls_spoof: build fake ClientHello") - } - if maxInjections <= 0 { - maxInjections = 1 - } - return &Conn{ - Conn: conn, - spoofer: spoofer, - fakeHello: fakeHello, - maxInjections: maxInjections, - }, nil -} - -func (c *Conn) Write(b []byte) (n int, err error) { - if c.injectionCount >= c.maxInjections { - return c.Conn.Write(b) - } - err = c.spoofer.Inject(c.fakeHello) - if err != nil { - return 0, func(err error, m string) error { return err }(err, "tls_spoof: inject") - } - c.injectionCount++ - if c.injectionCount >= c.maxInjections { - closeErr := c.spoofer.Close() - if closeErr != nil { - return 0, func(err error, m string) error { return err }(closeErr, "tls_spoof: close spoofer") - } - } - return c.Conn.Write(b) -} - -func (c *Conn) Close() error { - return func(e1, e2 error) error { - if e1 != nil { - return e1 - } - return e2 - }(c.Conn.Close(), c.spoofer.Close()) -} - -func (c *Conn) ReaderReplaceable() bool { - return true -} - -func (c *Conn) WriterReplaceable() bool { - return c.injectionCount >= c.maxInjections -} - -func (c *Conn) Upstream() any { - return c.Conn -} - -// wrapPermissionError adds platform-specific hints when the spoofer fails -// due to insufficient privileges. -func wrapPermissionError(err error) error { - if !errors.Is(err, syscall.EPERM) && !errors.Is(err, syscall.EACCES) { - return err - } - switch runtime.GOOS { - case "linux": - return fmt.Errorf("%w\n Hint: run as root, or grant capabilities:\n sudo setcap cap_net_raw,cap_net_admin+ep /path/to/xray", err) - case "darwin": - return fmt.Errorf("%w\n Hint: TLS spoofing requires root on macOS. Run with: sudo ./xray", err) - case "freebsd": - return fmt.Errorf("%w\n Hint: TLS spoofing requires root on FreeBSD. Run with: sudo ./xray", err) - default: - return err - } -} diff --git a/transport/internet/tls/tlsspoof/spoof_freebsd_test.go b/transport/internet/tls/tlsspoof/spoof_freebsd_test.go deleted file mode 100644 index a8ab2ccae823..000000000000 --- a/transport/internet/tls/tlsspoof/spoof_freebsd_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package tlsspoof - -import ( - "net" - "net/netip" - "os/user" - "testing" - - "golang.org/x/sys/unix" -) - -func TestFreeBSDTCPSequence(t *testing.T) { - u, err := user.Current() - if err == nil && u.Uid != "0" { - t.Skip("skipping test; must be root to use raw sockets / TCP_INFO on FreeBSD") - } - - ln, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("Listen failed: %v", err) - } - defer ln.Close() - - serverDone := make(chan struct{}) - go func() { - conn, err := ln.Accept() - if err == nil { - conn.Write([]byte("hello")) - conn.Close() - } - close(serverDone) - }() - - client, err := net.Dial("tcp", ln.Addr().String()) - if err != nil { - t.Fatalf("Dial failed: %v", err) - } - defer client.Close() - - tcpConn, ok := client.(*net.TCPConn) - if !ok { - t.Fatalf("expected *net.TCPConn, got %T", client) - } - - sndNxt, rcvNxt, err := readFreeBSDTCPSequence(tcpConn) - if err != nil { - t.Fatalf("readFreeBSDTCPSequence failed: %v", err) - } - - if sndNxt == 0 && rcvNxt == 0 { - t.Errorf("expected non-zero sequence numbers, got sndNxt=%d rcvNxt=%d", sndNxt, rcvNxt) - } - t.Logf("FreeBSD TCP sequence retrieved: snd_nxt=%d, rcv_nxt=%d", sndNxt, rcvNxt) - <-serverDone -} - -func TestFreeBSDRawSocket(t *testing.T) { - u, err := user.Current() - if err == nil && u.Uid != "0" { - t.Skip("skipping test; must be root to open raw sockets") - } - - dst := netip.MustParseAddrPort("8.8.8.8:443") - src := netip.MustParseAddrPort("127.0.0.1:12345") - - fd, sockaddr, err := openFreeBSDRawSocket(src, dst) - if err != nil { - t.Fatalf("openFreeBSDRawSocket failed: %v", err) - } - defer func() { - if fd >= 0 { - unix.Close(fd) - } - }() - - if fd < 0 { - t.Errorf("expected valid fd, got %d", fd) - } - if sockaddr == nil { - t.Error("expected valid sockaddr, got nil") - } -} diff --git a/transport/internet/tls/tlsspoof/spoof_test.go b/transport/internet/tls/tlsspoof/spoof_test.go deleted file mode 100644 index c51e4fddceb0..000000000000 --- a/transport/internet/tls/tlsspoof/spoof_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package tlsspoof - -import ( - "testing" -) - -func TestBuildFakeClientHello(t *testing.T) { - hello, err := buildFakeClientHello("www.example.com") - if err != nil { - t.Fatal("buildFakeClientHello returned error:", err) - } - if len(hello) == 0 { - t.Fatal("buildFakeClientHello returned empty payload") - } - // TLS record header: content type 0x16 (handshake) - if hello[0] != 0x16 { - t.Fatalf("expected TLS handshake record type 0x16, got 0x%02x", hello[0]) - } - // TLS version: 0x0301 (TLS 1.0 record layer) - if hello[1] != 0x03 || hello[2] != 0x01 { - t.Fatalf("unexpected TLS record version: 0x%02x%02x", hello[1], hello[2]) - } - t.Logf("ClientHello payload length: %d bytes", len(hello)) -} - -func TestBuildFakeClientHelloEmptySNI(t *testing.T) { - _, err := buildFakeClientHello("") - if err == nil { - t.Fatal("expected error for empty SNI") - } -} - -func TestParseMethod(t *testing.T) { - tests := []struct { - input string - expected Method - hasErr bool - }{ - {"", MethodWrongSequence, false}, - {"wrong-sequence", MethodWrongSequence, false}, - {"wrong-checksum", MethodWrongChecksum, false}, - {"wrong-ack", MethodWrongAcknowledgment, false}, - {"wrong-md5", MethodWrongMD5Sig, false}, - {"wrong-timestamp", MethodWrongTimestamp, false}, - {"invalid", 0, true}, - } - for _, tt := range tests { - m, err := ParseMethod(tt.input) - if tt.hasErr { - if err == nil { - t.Errorf("ParseMethod(%q): expected error, got nil", tt.input) - } - continue - } - if err != nil { - t.Errorf("ParseMethod(%q): unexpected error: %v", tt.input, err) - continue - } - if m != tt.expected { - t.Errorf("ParseMethod(%q) = %v, want %v", tt.input, m, tt.expected) - } - } -} - -func TestParseOptions(t *testing.T) { - // Empty spoof should be a no-op - sni, _, err := ParseOptions("", "") - if err != nil { - t.Fatal("ParseOptions(\"\", \"\"): unexpected error:", err) - } - if sni != "" { - t.Fatalf("expected empty SNI, got %q", sni) - } - - // spoof_method without spoof should error - _, _, err = ParseOptions("", "wrong-checksum") - if err == nil { - t.Fatal("expected error when spoof_method set without spoof") - } - - // Valid combo - sni, method, err := ParseOptions("fake.example.com", "wrong-checksum") - if err != nil { - t.Fatal("ParseOptions: unexpected error:", err) - } - if sni != "fake.example.com" { - t.Fatalf("expected SNI 'fake.example.com', got %q", sni) - } - if method != MethodWrongChecksum { - t.Fatalf("expected MethodWrongChecksum, got %v", method) - } - - // IP-literal should be rejected - _, _, err = ParseOptions("1.2.3.4", "wrong-checksum") - if err == nil { - t.Fatal("expected error for IP-literal spoof") - } - _, _, err = ParseOptions("::1", "wrong-checksum") - if err == nil { - t.Fatal("expected error for IP-literal spoof") - } -} - -func TestMethodString(t *testing.T) { - if MethodWrongSequence.String() != "wrong-sequence" { - t.Fatalf("unexpected method string: %s", MethodWrongSequence.String()) - } - if MethodWrongChecksum.String() != "wrong-checksum" { - t.Fatalf("unexpected method string: %s", MethodWrongChecksum.String()) - } -} diff --git a/transport/internet/tls/tlsspoof/tcpip.go b/transport/internet/tls/tlsspoof/tcpip.go deleted file mode 100644 index 62657ccefd68..000000000000 --- a/transport/internet/tls/tlsspoof/tcpip.go +++ /dev/null @@ -1,155 +0,0 @@ -package tlsspoof - -import ( - "encoding/binary" - "net/netip" -) - -const ( - IPv4MinimumSize = 20 - IPv6MinimumSize = 40 - TCPMinimumSize = 20 - TCPProtocolNumber = 6 - - TCPOptionEOL = 0 - TCPOptionNOP = 1 - TCPOptionTS = 8 - TCPOptionTSLength = 10 - - TCPFlagFin = 0x01 - TCPFlagSyn = 0x02 - TCPFlagRst = 0x04 - TCPFlagPsh = 0x08 - TCPFlagAck = 0x10 -) - -func Checksum(data []byte, initial uint16) uint16 { - var csum uint32 = uint32(initial) - for i := 0; i < len(data)-1; i += 2 { - csum += uint32(binary.BigEndian.Uint16(data[i:])) - } - if len(data)%2 == 1 { - csum += uint32(data[len(data)-1]) << 8 - } - for csum > 0xffff { - csum = (csum >> 16) + (csum & 0xffff) - } - return uint16(csum) -} - -func PseudoHeaderChecksum(protocol uint8, srcAddr, dstAddr []byte, totalLen uint16) uint16 { - var csum uint32 - for i := 0; i < len(srcAddr); i += 2 { - csum += uint32(binary.BigEndian.Uint16(srcAddr[i:])) - } - for i := 0; i < len(dstAddr); i += 2 { - csum += uint32(binary.BigEndian.Uint16(dstAddr[i:])) - } - csum += uint32(protocol) - csum += uint32(totalLen) - for csum > 0xffff { - csum = (csum >> 16) + (csum & 0xffff) - } - return uint16(csum) -} - -func CombineChecksum(c1, c2 uint16) uint16 { - csum := uint32(c1) + uint32(c2) - for csum > 0xffff { - csum = (csum >> 16) + (csum & 0xffff) - } - return uint16(csum) -} - -func EncodeTSOption(val uint32, ecr uint32, b []byte) { - b[0] = TCPOptionTS - b[1] = TCPOptionTSLength - binary.BigEndian.PutUint32(b[2:], val) - binary.BigEndian.PutUint32(b[6:], ecr) -} - -func ParseTCPOptions(b []byte) (tsVal uint32, hasTS bool) { - for i := 0; i < len(b); { - if b[i] == TCPOptionEOL { - break - } - if b[i] == TCPOptionNOP { - i++ - continue - } - if i+1 >= len(b) { - break - } - optLen := int(b[i+1]) - if optLen < 2 || i+optLen > len(b) { - break - } - if b[i] == TCPOptionTS && optLen == TCPOptionTSLength { - return binary.BigEndian.Uint32(b[i+2:]), true - } - i += optLen - } - return 0, false -} - -// IPv4 header representation -type IPv4 []byte - -func (b IPv4) TotalLength() uint16 { return binary.BigEndian.Uint16(b[2:]) } -func (b IPv4) Flags() uint8 { return uint8(binary.BigEndian.Uint16(b[6:]) >> 13) } -func (b IPv4) FragmentOffset() uint16 { return binary.BigEndian.Uint16(b[6:]) & 0x1fff } -func (b IPv4) Protocol() uint8 { return b[9] } -func (b IPv4) HeaderLength() uint8 { return (b[0] & 0x0f) * 4 } - -func (b IPv4) Encode(totalLength uint16, id uint16, ttl uint8, protocol uint8, src, dst netip.Addr) { - b[0] = (4 << 4) | 5 // IPv4, Header Length = 20 - b[1] = 0 // TOS - binary.BigEndian.PutUint16(b[2:], totalLength) - binary.BigEndian.PutUint16(b[4:], id) - binary.BigEndian.PutUint16(b[6:], 0) // Flags and Fragment Offset - b[8] = ttl - b[9] = protocol - b[10] = 0 // Checksum (0 for calculation) - copy(b[12:16], src.AsSlice()) - copy(b[16:20], dst.AsSlice()) - csum := Checksum(b[:20], 0) - binary.BigEndian.PutUint16(b[10:], ^csum) -} - -type IPv6 []byte - -func (b IPv6) PayloadLength() uint16 { return binary.BigEndian.Uint16(b[4:]) } -func (b IPv6) TransportProtocol() uint8 { return b[6] } - -func (b IPv6) Encode(payloadLength uint16, transportProtocol uint8, hopLimit uint8, src, dst netip.Addr) { - binary.BigEndian.PutUint32(b[0:], 6<<28) // Version 6, Traffic Class 0, Flow Label 0 - binary.BigEndian.PutUint16(b[4:], payloadLength) - b[6] = transportProtocol - b[7] = hopLimit - copy(b[8:24], src.AsSlice()) - copy(b[24:40], dst.AsSlice()) -} - -type TCP []byte - -func (b TCP) DataOffset() uint8 { return (b[12] >> 4) * 4 } -func (b TCP) SequenceNumber() uint32 { return binary.BigEndian.Uint32(b[4:]) } -func (b TCP) AckNumber() uint32 { return binary.BigEndian.Uint32(b[8:]) } -func (b TCP) Options() []byte { return b[TCPMinimumSize:b.DataOffset()] } -func (b TCP) SetChecksum(csum uint16) { binary.BigEndian.PutUint16(b[16:], csum) } - -func (b TCP) Encode(srcPort, dstPort uint16, seqNum, ackNum uint32, dataOffset uint8, flags uint8, windowSize uint16) { - binary.BigEndian.PutUint16(b[0:], srcPort) - binary.BigEndian.PutUint16(b[2:], dstPort) - binary.BigEndian.PutUint32(b[4:], seqNum) - binary.BigEndian.PutUint32(b[8:], ackNum) - b[12] = (dataOffset / 4) << 4 - b[13] = flags - binary.BigEndian.PutUint16(b[14:], windowSize) - b[16] = 0 // Checksum - binary.BigEndian.PutUint16(b[18:], 0) // Urgent pointer -} - -func (b TCP) CalculateChecksum(initial uint16) uint16 { - return Checksum(b, initial) -} diff --git a/transport/internet/tls/tlsspoof/windivert/assets/LICENSE.txt b/transport/internet/tls/tlsspoof/windivert/assets/LICENSE.txt deleted file mode 100644 index 8489a8e773c3..000000000000 --- a/transport/internet/tls/tlsspoof/windivert/assets/LICENSE.txt +++ /dev/null @@ -1,1191 +0,0 @@ -WinDivert is dual-licensed under your choice of the GNU Lesser General Public -License (LGPL) Version 3 or the GNU General Public License (GPL) Version 2. -Copies of the LGPLv3, GPLv3 and GPLv2 are provided below. - -============================================================================== - - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. - -============================================================================== - - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. - -============================================================================== - - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. - diff --git a/transport/internet/tls/tlsspoof/windivert/assets/WinDivert32.sys b/transport/internet/tls/tlsspoof/windivert/assets/WinDivert32.sys deleted file mode 100644 index d06738cbb78351cc57754fd484b77fac0df52cea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79792 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%0DB0KXuLbP(03=gskd! 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�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%0A~=3F;g8l!;0U`QAv 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 E0GFf)F8}}l diff --git a/transport/internet/tls/tlsspoof/windivert/assets/WinDivert64.sys b/transport/internet/tls/tlsspoof/windivert/assets/WinDivert64.sys deleted file mode 100644 index 218ccaf423ef0a67696226f9ef3a09149e4441d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 94144 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 Mok_m={2+h)7u^}>0RR91 diff --git a/transport/internet/tls/tlsspoof/windivert/assets_386.go b/transport/internet/tls/tlsspoof/windivert/assets_386.go deleted file mode 100644 index 0cbf35ed5cbf..000000000000 --- a/transport/internet/tls/tlsspoof/windivert/assets_386.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build windows && 386 - -package windivert - -import _ "embed" - -//go:embed assets/WinDivert32.sys -var sysBytes []byte - -func assetFiles() []assetFile { - return []assetFile{{"WinDivert32.sys", sysBytes}} -} - -func driverSysName() string { return "WinDivert32.sys" } diff --git a/transport/internet/tls/tlsspoof/windivert/assets_amd64.go b/transport/internet/tls/tlsspoof/windivert/assets_amd64.go deleted file mode 100644 index 2c9fb6c6ad19..000000000000 --- a/transport/internet/tls/tlsspoof/windivert/assets_amd64.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build windows && amd64 - -package windivert - -import _ "embed" - -//go:embed assets/WinDivert64.sys -var sysBytes []byte - -func assetFiles() []assetFile { - return []assetFile{{"WinDivert64.sys", sysBytes}} -} - -func driverSysName() string { return "WinDivert64.sys" } diff --git a/transport/internet/tls/tlsspoof/windivert/assets_unsupported.go b/transport/internet/tls/tlsspoof/windivert/assets_unsupported.go deleted file mode 100644 index 04698953fa6b..000000000000 --- a/transport/internet/tls/tlsspoof/windivert/assets_unsupported.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build windows && !amd64 && !386 - -package windivert - -func assetFiles() []assetFile { return nil } - -func driverSysName() string { return "" } diff --git a/transport/internet/tls/tlsspoof/windivert/driver_windows.go b/transport/internet/tls/tlsspoof/windivert/driver_windows.go deleted file mode 100644 index 50e94c578422..000000000000 --- a/transport/internet/tls/tlsspoof/windivert/driver_windows.go +++ /dev/null @@ -1,211 +0,0 @@ -//go:build windows - -package windivert - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "runtime" - "strconv" - "sync" - - "golang.org/x/sys/windows" -) - -const ( - driverServiceName = "WinDivert" - driverDeviceName = `\\.\WinDivert` -) - -var ( - driverOnce sync.Once - driverErr error - // driverDevName is ASCII-safe and must be available before ensureDriver - // so Open can try CreateFile first and only install on FILE_NOT_FOUND. - driverDevName, _ = windows.UTF16PtrFromString(driverDeviceName) -) - -// Requires SeLoadDriverPrivilege (Administrator). Running the 386 build -// under WOW64 on a 64-bit kernel is rejected — use the amd64 build. -func ensureDriver() error { - driverOnce.Do(func() { - driverErr = installDriver() - }) - return driverErr -} - -func installDriver() error { - if runtime.GOARCH == "386" { - var isWow64 bool - err := windows.IsWow64Process(windows.CurrentProcess(), &isWow64) - if err == nil && isWow64 { - return errors.New("windivert: 386 build detected running under WOW64 on a 64-bit kernel; use the amd64 build") - } - } - - dir, err := ensureExtracted() - if err != nil { - return err - } - sysPath := filepath.Join(dir, driverSysName()) - sysPathW, err := windows.UTF16PtrFromString(sysPath) - if err != nil { - return fmt.Errorf("windivert: utf16 driver path: %w", err) - } - - // Serialize driver install across concurrent processes. - mutexName, _ := windows.UTF16PtrFromString("WinDivertDriverInstallMutex") - mutex, err := windows.CreateMutex(nil, false, mutexName) - if err != nil { - return fmt.Errorf("windivert: create install mutex: %w", err) - } - defer windows.CloseHandle(mutex) - _, err = windows.WaitForSingleObject(mutex, windows.INFINITE) - if err != nil { - return fmt.Errorf("windivert: wait install mutex: %w", err) - } - defer windows.ReleaseMutex(mutex) - - manager, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_ALL_ACCESS) - if err != nil { - return fmt.Errorf("windivert: open SCM: %w", err) - } - defer windows.CloseServiceHandle(manager) - - serviceNameW, _ := windows.UTF16PtrFromString(driverServiceName) - service, err := windows.OpenService(manager, serviceNameW, windows.SERVICE_ALL_ACCESS) - if err != nil { - service, err = windows.CreateService( - manager, - serviceNameW, - serviceNameW, - windows.SERVICE_ALL_ACCESS, - windows.SERVICE_KERNEL_DRIVER, - windows.SERVICE_DEMAND_START, - windows.SERVICE_ERROR_NORMAL, - sysPathW, - nil, nil, nil, nil, nil, - ) - if err != nil { - if errors.Is(err, windows.ERROR_SERVICE_EXISTS) { - service, err = windows.OpenService(manager, serviceNameW, windows.SERVICE_ALL_ACCESS) - } - if err != nil { - return wrapDriverInstallError(err) - } - } - } - defer windows.CloseServiceHandle(service) - - err = windows.StartService(service, 0, nil) - if err != nil && errors.Is(err, windows.ERROR_SERVICE_DISABLED) { - // A prior process called DeleteService on a still-running kernel - // driver: SCM marks the record for deletion and flips START_TYPE - // to DISABLED until the last handle closes. Re-enable so we can - // start it instead of waiting for a reboot. - err = windows.ChangeServiceConfig( - service, - windows.SERVICE_NO_CHANGE, - windows.SERVICE_DEMAND_START, - windows.SERVICE_NO_CHANGE, - nil, nil, nil, nil, nil, nil, nil, - ) - if err != nil { - return fmt.Errorf("windivert: re-enable disabled service: %w", err) - } - err = windows.StartService(service, 0, nil) - } - if err == nil { - // Mark for deletion so the driver unregisters when the last handle - // closes or on next reboot. Matches the upstream DLL's behavior: - // only the process that actually started the service takes on the - // cleanup responsibility. If another process already started it, - // we leave DeleteService to them. - _ = windows.DeleteService(service) - } else if !errors.Is(err, windows.ERROR_SERVICE_ALREADY_RUNNING) { - return fmt.Errorf("windivert: start service: %w", err) - } - return nil -} - -func wrapDriverInstallError(err error) error { - if errors.Is(err, windows.ERROR_ACCESS_DENIED) { - return fmt.Errorf("windivert: installing the kernel driver requires Administrator privileges: %w", err) - } - return fmt.Errorf("windivert: create service: %w", err) -} - -type assetFile struct { - name string - data []byte -} - -var ( - extractOnce sync.Once - extractErr error - extractDir string -) - -// The on-disk copy is protected by Windows Authenticode signature -// enforcement, which rejects any tampered .sys at StartService time. -func ensureExtracted() (string, error) { - extractOnce.Do(func() { - extractDir, extractErr = extractImpl() - }) - return extractDir, extractErr -} - -func extractImpl() (string, error) { - files := assetFiles() - if len(files) == 0 { - return "", fmt.Errorf("windivert: unsupported architecture %s", runtime.GOARCH) - } - - base, err := os.UserCacheDir() - if err != nil { - return "", fmt.Errorf("windivert: locate user cache dir: %w", err) - } - dir := filepath.Join(base, "xray-core", "windivert", "v"+AssetVersion) - err = os.MkdirAll(dir, 0o755) - if err != nil { - return "", fmt.Errorf("windivert: mkdir %s: %w", dir, err) - } - - for _, asset := range files { - err = ensureAsset(dir, asset) - if err != nil { - return "", err - } - } - return dir, nil -} - -// Concurrent sing-box processes race on os.Rename (atomic on NTFS); -// whichever wins creates the final file. Writers that lose the race -// silently discard their temp copy. -func ensureAsset(dir string, asset assetFile) error { - target := filepath.Join(dir, asset.name) - _, err := os.Stat(target) - if err == nil { - return nil - } - if !os.IsNotExist(err) { - return fmt.Errorf("windivert: stat %s: %w", asset.name, err) - } - tmp := target + ".tmp-" + strconv.Itoa(os.Getpid()) - err = os.WriteFile(tmp, asset.data, 0o644) - if err != nil { - return fmt.Errorf("windivert: write %s: %w", asset.name, err) - } - err = os.Rename(tmp, target) - if err != nil { - os.Remove(tmp) - if _, statErr := os.Stat(target); statErr == nil { - return nil - } - return fmt.Errorf("windivert: rename %s: %w", asset.name, err) - } - return nil -} diff --git a/transport/internet/tls/tlsspoof/windivert/filter.go b/transport/internet/tls/tlsspoof/windivert/filter.go deleted file mode 100644 index d63adae2b630..000000000000 --- a/transport/internet/tls/tlsspoof/windivert/filter.go +++ /dev/null @@ -1,181 +0,0 @@ -package windivert - -import ( - "encoding/binary" - "errors" - "net/netip" -) - -// WINDIVERT_FILTER VM instruction layout (24 bytes, #pragma pack(1)): -// -// word 0 (LE): field:11 | test:5 | success:16 -// word 1 (LE): failure:16 | neg:1 | reserved:15 -// words 2..5: arg[4] (native-endian uint32 each) -// -// The driver walks this as a decision tree: evaluate the test at inst i; -// on success jump to success; on failure jump to failure. Continuations -// 0x7FFE and 0x7FFF are ACCEPT and REJECT terminals. -const ( - filterInstBytes = 24 - filterMaxInsts = 256 - - fieldZero = 0 - fieldOutbound = 2 - fieldIP = 5 - fieldIPv6 = 6 - fieldTCP = 8 - fieldIPSrcAddr = 21 - fieldIPDstAddr = 22 - fieldIPv6SrcAddr = 28 - fieldIPv6DstAddr = 29 - fieldTCPSrcPort = 38 - fieldTCPDstPort = 39 - - testEQ = 0 - - resultAccept uint16 = 0x7FFE - resultReject uint16 = 0x7FFF -) - -// Filter flags passed to IOCTL_WINDIVERT_STARTUP alongside the compiled -// filter. These tell the driver what *kinds* of packets the filter might -// match, used as a kernel-side fast-reject. -const ( - filterFlagOutbound uint64 = 0x0020 - filterFlagIP uint64 = 0x0040 - filterFlagIPv6 uint64 = 0x0080 -) - -type filterInst struct { - field uint16 // 11 bits used - test uint8 // 5 bits used - success uint16 - failure uint16 - neg bool - arg [4]uint32 -} - -// Filter is a typed specification of packets to capture. It replaces -// WinDivert's filter string language. -// -// Zero value = "reject all" (match nothing), suitable for send-only handles. -type Filter struct { - insts []filterInst - flags uint64 // filter flags for STARTUP ioctl -} - -// reject returns a filter that matches no packet. The empty insts slice -// is encoded as a single rejecting instruction by encode(). -func reject() *Filter { - return &Filter{} -} - -// OutboundTCP returns a filter matching outbound TCP packets on the given -// 5-tuple. Both addresses must share an address family (IPv4 or IPv6). -func OutboundTCP(src, dst netip.AddrPort) (*Filter, error) { - if !src.IsValid() || !dst.IsValid() { - return nil, errors.New("windivert: filter: invalid address port") - } - if src.Addr().Is4() != dst.Addr().Is4() { - return nil, errors.New("windivert: filter: mixed IPv4/IPv6") - } - f := &Filter{ - flags: filterFlagOutbound, - } - // Insts chain as AND: each test's failure = REJECT, success = next inst. - // The final inst's success = ACCEPT. - f.add(fieldOutbound, testEQ, argUint32(1)) - if src.Addr().Is4() { - f.flags |= filterFlagIP - f.add(fieldIP, testEQ, argUint32(1)) - f.add(fieldTCP, testEQ, argUint32(1)) - f.add(fieldIPSrcAddr, testEQ, argIPv4(src.Addr())) - f.add(fieldIPDstAddr, testEQ, argIPv4(dst.Addr())) - } else { - f.flags |= filterFlagIPv6 - f.add(fieldIPv6, testEQ, argUint32(1)) - f.add(fieldTCP, testEQ, argUint32(1)) - f.add(fieldIPv6SrcAddr, testEQ, argIPv6(src.Addr())) - f.add(fieldIPv6DstAddr, testEQ, argIPv6(dst.Addr())) - } - f.add(fieldTCPSrcPort, testEQ, argUint32(uint32(src.Port()))) - f.add(fieldTCPDstPort, testEQ, argUint32(uint32(dst.Port()))) - return f, nil -} - -func (f *Filter) add(field uint16, test uint8, arg [4]uint32) { - f.insts = append(f.insts, filterInst{field: field, test: test, arg: arg}) -} - -func argUint32(v uint32) [4]uint32 { return [4]uint32{v, 0, 0, 0} } - -// argIPv4 encodes an IPv4 address for IP_SRCADDR/IP_DSTADDR. The driver -// compares against an IPv4-mapped-IPv6 form: {host_order_u32, 0x0000FFFF, -// 0, 0} (see sys/windivert.c windivert_get_ipv4_addr and the IPv4_SRCADDR -// val-word construction). Omitting the 0x0000FFFF marker causes the EQ -// test to fail for every packet. -func argIPv4(addr netip.Addr) [4]uint32 { - b := addr.As4() - return [4]uint32{binary.BigEndian.Uint32(b[:]), 0x0000FFFF, 0, 0} -} - -// argIPv6 encodes an IPv6 address for IPV6_SRCADDR/IPV6_DSTADDR. The -// driver stores the address as four host-order uint32s in REVERSED word -// order: val[0]=low (bytes 12..15), val[3]=high (bytes 0..3). See -// sys/windivert.c windivert_outbound_network_v6_classify val-word -// construction. -func argIPv6(addr netip.Addr) [4]uint32 { - b := addr.As16() - return [4]uint32{ - binary.BigEndian.Uint32(b[12:16]), - binary.BigEndian.Uint32(b[8:12]), - binary.BigEndian.Uint32(b[4:8]), - binary.BigEndian.Uint32(b[0:4]), - } -} - -// encode serializes the Filter to the on-wire WINDIVERT_FILTER[] format -// plus the filter_flags for STARTUP ioctl. -func (f *Filter) encode() ([]byte, uint64, error) { - if len(f.insts) == 0 { - // "Reject all" — one instruction, ZERO == 0 is always true, but we - // invert by setting both success and failure to REJECT. - return encodeInst(filterInst{ - field: fieldZero, - test: testEQ, - success: resultReject, - failure: resultReject, - }), 0, nil - } - if len(f.insts) > filterMaxInsts-1 { - return nil, 0, errors.New("windivert: filter too long") - } - buf := make([]byte, 0, filterInstBytes*len(f.insts)) - for i, inst := range f.insts { - if i == len(f.insts)-1 { - inst.success = resultAccept - } else { - inst.success = uint16(i + 1) - } - inst.failure = resultReject - buf = append(buf, encodeInst(inst)...) - } - return buf, f.flags, nil -} - -func encodeInst(inst filterInst) []byte { - out := make([]byte, filterInstBytes) - word0 := uint32(inst.field&0x7FF) | uint32(inst.test&0x1F)<<11 | - uint32(inst.success)<<16 - word1 := uint32(inst.failure) - if inst.neg { - word1 |= 1 << 16 - } - binary.LittleEndian.PutUint32(out[0:4], word0) - binary.LittleEndian.PutUint32(out[4:8], word1) - binary.LittleEndian.PutUint32(out[8:12], inst.arg[0]) - binary.LittleEndian.PutUint32(out[12:16], inst.arg[1]) - binary.LittleEndian.PutUint32(out[16:20], inst.arg[2]) - binary.LittleEndian.PutUint32(out[20:24], inst.arg[3]) - return out -} diff --git a/transport/internet/tls/tlsspoof/windivert/handle_windows.go b/transport/internet/tls/tlsspoof/windivert/handle_windows.go deleted file mode 100644 index c48e6214c11b..000000000000 --- a/transport/internet/tls/tlsspoof/windivert/handle_windows.go +++ /dev/null @@ -1,323 +0,0 @@ -//go:build windows - -package windivert - -import ( - "encoding/binary" - "errors" - "fmt" - "runtime" - "sync" - "unsafe" - - "golang.org/x/sys/windows" -) - -// Handle owns a WinDivert kernel device handle plus a private event for -// overlapped I/O. Methods on *Handle are not safe for concurrent use -// across goroutines (there is a single shared event per Handle). -// -// addr is a per-Handle Address buffer the IOCTL struct embeds a pointer -// to. It lives on the heap (as a field of a heap-allocated Handle) so -// the pointer value stored as bytes in the ioctl buffer remains valid -// across stack growth between buildIoctl* and the DeviceIoControl -// syscall — stack-local Address values are not safe for this pattern -// because Go's escape analysis does not see the pointer through the -// unsafe.Pointer → uintptr → bytes conversion. -type Handle struct { - device windows.Handle - event windows.Handle - closing sync.Once - closeErr error - addr Address -} - -// Filter may be nil for "reject all", suitable for send-only handles. -// Requires Administrator on first call per process (installs the kernel -// driver via SCM); subsequent calls reuse the running driver. -func Open(filter *Filter, layer Layer, priority int16, flags Flag) (*Handle, error) { - err := validateOpenArgs(layer, priority, flags) - if err != nil { - return nil, err - } - if filter == nil { - filter = reject() - } - filterBin, filterFlags, err := filter.encode() - if err != nil { - return nil, err - } - device, err := openDevice() - if err != nil { - if !errors.Is(err, windows.ERROR_FILE_NOT_FOUND) && - !errors.Is(err, windows.ERROR_PATH_NOT_FOUND) { - if errors.Is(err, windows.ERROR_ACCESS_DENIED) { - return nil, fmt.Errorf("windivert: open device (administrator required): %w", err) - } - return nil, fmt.Errorf("windivert: open device: %w", err) - } - // Device node missing: kernel driver not loaded. Install + retry. - // Matches WinDivertOpen's lazy-install path; avoids racing StartService - // against a still-loaded driver whose SCM record is marked for deletion. - err = ensureDriver() - if err != nil { - return nil, err - } - device, err = openDevice() - if err != nil { - if errors.Is(err, windows.ERROR_ACCESS_DENIED) { - return nil, fmt.Errorf("windivert: open device (administrator required): %w", err) - } - return nil, fmt.Errorf("windivert: open device: %w", err) - } - } - event, err := windows.CreateEvent(nil, 1, 0, nil) // manual reset, unsignaled - if err != nil { - windows.CloseHandle(device) - return nil, fmt.Errorf("windivert: create event: %w", err) - } - h := &Handle{device: device, event: event} - - err = h.initialize(layer, priority, flags) - if err != nil { - h.Close() - return nil, err - } - err = h.startup(filterBin, filterFlags) - if err != nil { - h.Close() - return nil, err - } - return h, nil -} - -func openDevice() (windows.Handle, error) { - return windows.CreateFile( - driverDevName, - windows.GENERIC_READ|windows.GENERIC_WRITE, - 0, nil, - windows.OPEN_EXISTING, - windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_OVERLAPPED, - 0, - ) -} - -func validateOpenArgs(layer Layer, priority int16, flags Flag) error { - if layer != LayerNetwork { - return fmt.Errorf("windivert: invalid layer %d", uint32(layer)) - } - if priority < PriorityLowest || priority > PriorityHighest { - return errors.New("windivert: priority out of range") - } - const supportedFlags = FlagSniff | FlagSendOnly - if flags&^supportedFlags != 0 { - return errors.New("windivert: unknown flag bits") - } - if flags&FlagSniff != 0 && flags&FlagSendOnly != 0 { - return errors.New("windivert: FlagSniff and FlagSendOnly are mutually exclusive") - } - return nil -} - -func (h *Handle) initialize(layer Layer, priority int16, flags Flag) error { - in := buildIoctlInitialize(layer, priority, flags) - // WINDIVERT_VERSION is a 64-byte packed struct; only the first 20 - // bytes (magic, major, minor, bits) carry data, the rest is reserved. - var outBuf [versionStructSize]byte - binary.LittleEndian.PutUint64(outBuf[0:8], magicDLL) - binary.LittleEndian.PutUint32(outBuf[8:12], versionMajor) - binary.LittleEndian.PutUint32(outBuf[12:16], versionMinor) - binary.LittleEndian.PutUint32(outBuf[16:20], uint32(unsafe.Sizeof(uintptr(0))*8)) - _, err := doIoctl(h.device, ioctlInitialize, in[:], outBuf[:], h.event) - if err != nil { - return fmt.Errorf("windivert: initialize ioctl: %w", err) - } - gotMagic := binary.LittleEndian.Uint64(outBuf[0:8]) - if gotMagic != magicSYS { - return fmt.Errorf("windivert: driver magic mismatch (got %d)", gotMagic) - } - gotMajor := binary.LittleEndian.Uint32(outBuf[8:12]) - if gotMajor < versionMajor { - gotMinor := binary.LittleEndian.Uint32(outBuf[12:16]) - return fmt.Errorf("windivert: driver version too old: %d.%d", gotMajor, gotMinor) - } - return nil -} - -func (h *Handle) startup(filterBin []byte, filterFlags uint64) error { - in := buildIoctlStartup(filterFlags) - _, err := doIoctl(h.device, ioctlStartup, in[:], filterBin, h.event) - if err != nil { - return fmt.Errorf("windivert: startup ioctl: %w", err) - } - return nil -} - -// If the handle is closed mid-Recv the error wraps ERROR_OPERATION_ABORTED. -func (h *Handle) Recv(buf []byte) (int, Address, error) { - if len(buf) == 0 { - return 0, Address{}, errors.New("windivert: recv: zero-length buffer") - } - h.addr = Address{} - in := buildIoctlRecv(&h.addr) - n, err := doIoctl(h.device, ioctlRecv, in[:], buf, h.event) - runtime.KeepAlive(h) - if err != nil { - return 0, Address{}, err - } - return int(n), h.addr, nil -} - -// The address's Outbound flag controls whether the packet is sent toward -// the wire (outbound=true) or delivered up the stack (outbound=false). -// IfIdx and SubIfIdx can stay zero — the driver uses the routing table -// when IfIdx=0. -func (h *Handle) Send(packet []byte, addr *Address) (int, error) { - if len(packet) == 0 { - return 0, errors.New("windivert: send: empty packet") - } - if addr == nil { - return 0, errors.New("windivert: send: nil address") - } - h.addr = *addr - in := buildIoctlSend(&h.addr) - n, err := doIoctl(h.device, ioctlSend, in[:], packet, h.event) - runtime.KeepAlive(h) - if err != nil { - return 0, err - } - return int(n), nil -} - -// Idempotent. Aborts any in-flight I/O on the handle. -func (h *Handle) Close() error { - h.closing.Do(func() { - var errs []error - if h.device != 0 { - err := windows.CloseHandle(h.device) - if err != nil { - errs = append(errs, err) - } - h.device = 0 - } - if h.event != 0 { - err := windows.CloseHandle(h.event) - if err != nil { - errs = append(errs, err) - } - h.event = 0 - } - h.closeErr = errors.Join(errs...) - }) - return h.closeErr -} - -// IOCTL codes from windivert_device.h. CTL_CODE macro layout: -// -// (DeviceType << 16) | (Access << 14) | (Function << 2) | Method -const ( - fileDeviceNetwork uint32 = 0x12 - accessReadWrite uint32 = 3 // FILE_READ_DATA | FILE_WRITE_DATA - accessRead uint32 = 1 - - methodInDirect uint32 = 1 - methodOutDirect uint32 = 2 -) - -func ctlCode(deviceType, access, function, method uint32) uint32 { - return (deviceType << 16) | (access << 14) | (function << 2) | method -} - -var ( - ioctlInitialize = ctlCode(fileDeviceNetwork, accessReadWrite, 0x921, methodOutDirect) - ioctlStartup = ctlCode(fileDeviceNetwork, accessReadWrite, 0x922, methodInDirect) - ioctlRecv = ctlCode(fileDeviceNetwork, accessRead, 0x923, methodOutDirect) - ioctlSend = ctlCode(fileDeviceNetwork, accessReadWrite, 0x924, methodInDirect) -) - -// Magic numbers exchanged during INITIALIZE. DLL sends magicDLL in the -// version struct; driver returns magicSYS on success. -const ( - magicDLL uint64 = 0x4C4C447669645724 // "$WdivDLL" in LE bytes - magicSYS uint64 = 0x5359537669645723 // "#WdivSYS" in LE bytes -) - -const ( - versionMajor uint32 = 2 - versionMinor uint32 = 2 -) - -// Size of the WINDIVERT_IOCTL union on wire (packed). -const ioctlSize = 16 - -// Size of WINDIVERT_VERSION on wire (packed). Only the first 20 bytes -// carry data; the rest is reserved zero padding. -const versionStructSize = 64 - -// doIoctl performs a single synchronous (blocking) overlapped -// DeviceIoControl. The handle is opened with FILE_FLAG_OVERLAPPED so -// DeviceIoControl returns ERROR_IO_PENDING; we then wait for completion -// via GetOverlappedResult. Event is passed in so callers can reuse it -// across calls on the same handle (avoids per-call CreateEvent). -func doIoctl(handle windows.Handle, code uint32, in []byte, out []byte, event windows.Handle) (uint32, error) { - var overlapped windows.Overlapped - overlapped.HEvent = event - _ = windows.ResetEvent(event) - - var inPtr *byte - var inLen uint32 - if len(in) > 0 { - inPtr = &in[0] - inLen = uint32(len(in)) - } - var outPtr *byte - var outLen uint32 - if len(out) > 0 { - outPtr = &out[0] - outLen = uint32(len(out)) - } - var returned uint32 - err := windows.DeviceIoControl(handle, code, inPtr, inLen, outPtr, outLen, &returned, &overlapped) - if err != nil && !errors.Is(err, windows.ERROR_IO_PENDING) { - return 0, err - } - err = windows.GetOverlappedResult(handle, &overlapped, &returned, true) - if err != nil { - return 0, err - } - return returned, nil -} - -func buildIoctlInitialize(layer Layer, priority int16, flags Flag) [ioctlSize]byte { - var buf [ioctlSize]byte - binary.LittleEndian.PutUint32(buf[0:4], uint32(layer)) - // The driver expects priority + WINDIVERT_PRIORITY_HIGHEST (30000) so - // the low range maps to non-negative integers. - binary.LittleEndian.PutUint32(buf[4:8], uint32(int32(priority)+int32(PriorityHighest))) - binary.LittleEndian.PutUint64(buf[8:16], uint64(flags)) - return buf -} - -func buildIoctlStartup(filterFlags uint64) [ioctlSize]byte { - var buf [ioctlSize]byte - binary.LittleEndian.PutUint64(buf[0:8], filterFlags) - return buf -} - -// buildIoctlRecv packs a user-space pointer to a WINDIVERT_ADDRESS into -// the ioctl struct. The driver dereferences it to write the address for -// the received packet. Caller must keep the Address alive via -// runtime.KeepAlive. -func buildIoctlRecv(addr *Address) [ioctlSize]byte { - var buf [ioctlSize]byte - binary.LittleEndian.PutUint64(buf[0:8], uint64(uintptr(unsafe.Pointer(addr)))) - binary.LittleEndian.PutUint64(buf[8:16], 0) - return buf -} - -func buildIoctlSend(addr *Address) [ioctlSize]byte { - var buf [ioctlSize]byte - binary.LittleEndian.PutUint64(buf[0:8], uint64(uintptr(unsafe.Pointer(addr)))) - binary.LittleEndian.PutUint64(buf[8:16], uint64(unsafe.Sizeof(Address{}))) - return buf -} diff --git a/transport/internet/tls/tlsspoof/windivert/windivert.go b/transport/internet/tls/tlsspoof/windivert/windivert.go deleted file mode 100644 index 9d309886cbe3..000000000000 --- a/transport/internet/tls/tlsspoof/windivert/windivert.go +++ /dev/null @@ -1,78 +0,0 @@ -// Package windivert provides a pure-Go binding to the WinDivert kernel -// driver on Windows (amd64 and 386). User-mode WinDivert calls are -// reimplemented in Go; only the signed kernel driver is embedded as an -// asset, since SCM-installed drivers must live on disk and their -// Authenticode signature forbids modification. -// -// Administrator is required for the first Open in a process so SCM can -// load the driver. Upstream: https://github.com/basil00/WinDivert v2.2.2, -// redistributed under its LGPL v3 option; see assets/LICENSE.txt. -package windivert - -import "unsafe" - -const AssetVersion = "2.2.2" - -// MTUMax is WINDIVERT_MTU_MAX from windivert.h (40 + 0xFFFF). Suitable as -// a single-packet receive buffer size. -const MTUMax = 40 + 0xFFFF - -type Layer uint32 - -const LayerNetwork Layer = 0 - -type Flag uint64 - -const ( - // FlagSniff opens a passive observer: the driver copies matching packets - // to userspace without removing them from the network stack. Send is not - // required (and not allowed) on a sniffing handle. - FlagSniff Flag = 0x0001 - // FlagSendOnly opens a write-only injection handle; Recv is not allowed. - FlagSendOnly Flag = 0x0008 -) - -const ( - PriorityHighest int16 = 30000 - PriorityLowest int16 = -30000 -) - -// Address mirrors WINDIVERT_ADDRESS from windivert.h (80 bytes, -// little-endian on both amd64 and 386): -// -// 0: INT64 Timestamp -// 8: UINT32 bitfield: Layer:8 | Event:8 | flags | Reserved1:8 -// 12: UINT32 Reserved2 -// 16: 64 bytes union (WINDIVERT_DATA_NETWORK / FLOW / SOCKET / REFLECT) -type Address struct { - Timestamp int64 - bits uint32 - Reserved2 uint32 - union [64]byte -} - -var _ [80]byte = [unsafe.Sizeof(Address{})]byte{} - -// Bit positions inside the Address's packed flags word. -const ( - addrBitIPv6 = 20 - addrBitIPChecksum = 21 - addrBitTCPChecksum = 22 -) - -func getFlagBit(bits uint32, pos uint) bool { return bits&(1< Date: Sun, 10 May 2026 10:30:00 +0600 Subject: [PATCH 24/42] Transport: Remove TLS spoof integration from dialers --- transport/internet/grpc/dial.go | 6 ------ transport/internet/httpupgrade/dialer.go | 6 ------ transport/internet/kcp/dialer.go | 6 ------ transport/internet/splithttp/dialer.go | 6 ------ transport/internet/tcp/dialer.go | 10 ---------- transport/internet/websocket/dialer.go | 8 -------- 6 files changed, 42 deletions(-) diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index b17caa9730fc..c8b8423c6579 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -140,12 +140,6 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in if config.ServerName == "" && address.Family().IsDomain() { config.ServerName = address.Domain() } - if spoofConn, err := tls.WrapWithSpoof(c, tlsConfig.Spoof, tlsConfig.SpoofMethod, tlsConfig.SpoofCount, config.ServerName); err != nil { - c.Close() - return nil, err - } else { - c = spoofConn - } if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil { return tls.UClient(c, config, fingerprint), nil } else { // Fallback to normal gRPC TLS diff --git a/transport/internet/httpupgrade/dialer.go b/transport/internet/httpupgrade/dialer.go index bb9df1c912fb..571797f6172d 100644 --- a/transport/internet/httpupgrade/dialer.go +++ b/transport/internet/httpupgrade/dialer.go @@ -66,12 +66,6 @@ func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings * tConfig := tls.ConfigFromStreamSettings(streamSettings) if tConfig != nil { tlsConfig := tConfig.GetTLSConfig(tls.WithDestination(dest), tls.WithNextProto("http/1.1")) - if spoofConn, err := tls.WrapWithSpoof(pconn, tConfig.Spoof, tConfig.SpoofMethod, tConfig.SpoofCount, tlsConfig.ServerName); err != nil { - pconn.Close() - return nil, err - } else { - pconn = spoofConn - } if fingerprint := tls.GetFingerprint(tConfig.Fingerprint); fingerprint != nil { conn = tls.UClient(pconn, tlsConfig, fingerprint) if err := conn.(*tls.UConn).WebsocketHandshakeContext(ctx); err != nil { diff --git a/transport/internet/kcp/dialer.go b/transport/internet/kcp/dialer.go index e3ff0bdc9a19..a0e9c8aae25a 100644 --- a/transport/internet/kcp/dialer.go +++ b/transport/internet/kcp/dialer.go @@ -98,12 +98,6 @@ func DialKCP(ctx context.Context, dest net.Destination, streamSettings *internet if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { tlsConfig := config.GetTLSConfig(tls.WithDestination(dest)) - if spoofConn, err := tls.WrapWithSpoof(iConn, config.Spoof, config.SpoofMethod, config.SpoofCount, tlsConfig.ServerName); err != nil { - iConn.Close() - return nil, err - } else { - iConn = spoofConn.(stat.Connection) - } iConn = tls.Client(iConn, tlsConfig) } diff --git a/transport/internet/splithttp/dialer.go b/transport/internet/splithttp/dialer.go index 1b8c00d0a2bd..1329713c5f81 100644 --- a/transport/internet/splithttp/dialer.go +++ b/transport/internet/splithttp/dialer.go @@ -138,12 +138,6 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea } if gotlsConfig != nil { - if spoofConn, err := tls.WrapWithSpoof(conn, tlsConfig.Spoof, tlsConfig.SpoofMethod, tlsConfig.SpoofCount, gotlsConfig.ServerName); err != nil { - conn.Close() - return nil, err - } else { - conn = spoofConn - } if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil { conn = tls.UClient(conn, gotlsConfig, fingerprint) if err := conn.(*tls.UConn).HandshakeContext(ctxInner); err != nil { diff --git a/transport/internet/tcp/dialer.go b/transport/internet/tcp/dialer.go index e226a5657cb3..92fa7557f13a 100644 --- a/transport/internet/tcp/dialer.go +++ b/transport/internet/tcp/dialer.go @@ -74,11 +74,6 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me } } if fingerprint := tls.GetFingerprint(config.Fingerprint); fingerprint != nil { - if spoofConn, err := tls.WrapWithSpoof(conn, config.Spoof, config.SpoofMethod, config.SpoofCount, tlsConfig.ServerName); err != nil { - return nil, err - } else { - conn = spoofConn - } conn = tls.UClient(conn, tlsConfig, fingerprint) if len(tlsConfig.NextProtos) == 1 && tlsConfig.NextProtos[0] == "http/1.1" { // allow manually specify err = conn.(*tls.UConn).WebsocketHandshakeContext(ctx) @@ -86,11 +81,6 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me err = conn.(*tls.UConn).HandshakeContext(ctx) } } else { - if spoofConn, err := tls.WrapWithSpoof(conn, config.Spoof, config.SpoofMethod, config.SpoofCount, tlsConfig.ServerName); err != nil { - return nil, err - } else { - conn = spoofConn - } conn = tls.Client(conn, tlsConfig) err = conn.(*tls.Conn).HandshakeContext(ctx) } diff --git a/transport/internet/websocket/dialer.go b/transport/internet/websocket/dialer.go index f6eb73e1edae..8e295da062e8 100644 --- a/transport/internet/websocket/dialer.go +++ b/transport/internet/websocket/dialer.go @@ -94,14 +94,6 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in pconn = newConn } - // Wrap with TLS spoofing if configured - if spoofConn, err := tls.WrapWithSpoof(pconn, tConfig.Spoof, tConfig.SpoofMethod, tConfig.SpoofCount, tlsConfig.ServerName); err != nil { - pconn.Close() - return nil, err - } else { - pconn = spoofConn - } - // TLS and apply the handshake cn := tls.UClient(pconn, tlsConfig, fingerprint).(*tls.UConn) if err := cn.WebsocketHandshakeContext(ctx); err != nil { From 3e45126ec0f6e4ed68aa729b776b2c7e058db5f1 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Sun, 10 May 2026 11:00:00 +0600 Subject: [PATCH 25/42] Config: Add rawpacket tcpmask support --- infra/conf/transport_internet.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 0fd1c767380a..4ea9aa97c7bb 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -23,6 +23,7 @@ import ( "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/finalmask/fragment" "github.com/xtls/xray-core/transport/internet/finalmask/header/custom" + "github.com/xtls/xray-core/transport/internet/finalmask/rawpacket" "github.com/xtls/xray-core/transport/internet/finalmask/header/dns" "github.com/xtls/xray-core/transport/internet/finalmask/header/dtls" "github.com/xtls/xray-core/transport/internet/finalmask/header/srtp" @@ -1237,6 +1238,7 @@ var ( tcpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{ "header-custom": func() interface{} { return new(HeaderCustomTCP) }, "fragment": func() interface{} { return new(FragmentMask) }, + "rawpacket": func() interface{} { return new(RawpacketMask) }, "sudoku": func() interface{} { return new(Sudoku) }, }, "type", "settings") @@ -1447,6 +1449,23 @@ func (c *FragmentMask) Build() (proto.Message, error) { return config, nil } +type RawpacketMask struct { + Payload string `json:"payload"` + Method string `json:"method"` + TTL int32 `json:"ttl"` + Count int32 `json:"count"` +} + +func (c *RawpacketMask) Build() (proto.Message, error) { + config := &rawpacket.Config{ + Payload: c.Payload, + Method: c.Method, + Ttl: uint32(c.TTL), + Count: c.Count, + } + return config, nil +} + type NoiseItem struct { Rand Int32Range `json:"rand"` RandRange *Int32Range `json:"randRange"` From 7c83ef50d720c605ce4a8eb0f69efa1d1652bb20 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Sun, 10 May 2026 06:52:41 +0000 Subject: [PATCH 26/42] Fix protoc version header in generated proto files --- transport/internet/finalmask/rawpacket/config.pb.go | 2 +- transport/internet/kcp/dialer.go | 3 +-- transport/internet/tls/config.pb.go | 11 +++++------ 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/transport/internet/finalmask/rawpacket/config.pb.go b/transport/internet/finalmask/rawpacket/config.pb.go index f1e3982bb2d4..26f8b63d26ca 100644 --- a/transport/internet/finalmask/rawpacket/config.pb.go +++ b/transport/internet/finalmask/rawpacket/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v7.34.1 +// protoc v6.33.5 // source: transport/internet/finalmask/rawpacket/config.proto package rawpacket diff --git a/transport/internet/kcp/dialer.go b/transport/internet/kcp/dialer.go index a0e9c8aae25a..175998ec7dd3 100644 --- a/transport/internet/kcp/dialer.go +++ b/transport/internet/kcp/dialer.go @@ -97,8 +97,7 @@ func DialKCP(ctx context.Context, dest net.Destination, streamSettings *internet var iConn stat.Connection = session if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { - tlsConfig := config.GetTLSConfig(tls.WithDestination(dest)) - iConn = tls.Client(iConn, tlsConfig) + iConn = tls.Client(iConn, config.GetTLSConfig(tls.WithDestination(dest))) } return iConn, nil diff --git a/transport/internet/tls/config.pb.go b/transport/internet/tls/config.pb.go index 5f7688a5c512..37628755eb4f 100644 --- a/transport/internet/tls/config.pb.go +++ b/transport/internet/tls/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v7.34.1 +// protoc v6.33.5 // source: transport/internet/tls/config.proto package tls @@ -201,11 +201,10 @@ type Config struct { RejectUnknownSni bool `protobuf:"varint,12,opt,name=reject_unknown_sni,json=rejectUnknownSni,proto3" json:"reject_unknown_sni,omitempty"` MasterKeyLog string `protobuf:"bytes,15,opt,name=master_key_log,json=masterKeyLog,proto3" json:"master_key_log,omitempty"` // Lists of string as CurvePreferences values. - CurvePreferences []string `protobuf:"bytes,16,rep,name=curve_preferences,json=curvePreferences,proto3" json:"curve_preferences,omitempty"` - VerifyPeerCertByName []string `protobuf:"bytes,17,rep,name=verify_peer_cert_by_name,json=verifyPeerCertByName,proto3" json:"verify_peer_cert_by_name,omitempty"` - EchServerKeys []byte `protobuf:"bytes,18,opt,name=ech_server_keys,json=echServerKeys,proto3" json:"ech_server_keys,omitempty"` - EchConfigList string `protobuf:"bytes,19,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"` - // Deprecated + CurvePreferences []string `protobuf:"bytes,16,rep,name=curve_preferences,json=curvePreferences,proto3" json:"curve_preferences,omitempty"` + VerifyPeerCertByName []string `protobuf:"bytes,17,rep,name=verify_peer_cert_by_name,json=verifyPeerCertByName,proto3" json:"verify_peer_cert_by_name,omitempty"` + EchServerKeys []byte `protobuf:"bytes,18,opt,name=ech_server_keys,json=echServerKeys,proto3" json:"ech_server_keys,omitempty"` + EchConfigList string `protobuf:"bytes,19,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"` EchForceQuery string `protobuf:"bytes,20,opt,name=ech_force_query,json=echForceQuery,proto3" json:"ech_force_query,omitempty"` EchSocketSettings *internet.SocketConfig `protobuf:"bytes,21,opt,name=ech_socket_settings,json=echSocketSettings,proto3" json:"ech_socket_settings,omitempty"` PinnedPeerCertSha256 [][]byte `protobuf:"bytes,22,rep,name=pinned_peer_cert_sha256,json=pinnedPeerCertSha256,proto3" json:"pinned_peer_cert_sha256,omitempty"` From 7eb384847add07c8771a7e5aa3cf8d17096dafb9 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Fri, 29 May 2026 04:38:53 +0600 Subject: [PATCH 27/42] Rawpacket: Fix injection Write/Close order;; add sni field and fake-hello CLI --- infra/conf/transport_internet.go | 2 + main/commands/all/tls/fakehello.go | 48 +++++ main/commands/all/tls/tls.go | 1 + .../finalmask/rawpacket/client_hello.go | 51 +++++ .../internet/finalmask/rawpacket/config.pb.go | 17 +- .../internet/finalmask/rawpacket/config.proto | 4 + .../internet/finalmask/rawpacket/conn.go | 41 ++-- .../internet/finalmask/rawpacket/conn_test.go | 181 ++++++++++++++---- 8 files changed, 292 insertions(+), 53 deletions(-) create mode 100644 main/commands/all/tls/fakehello.go create mode 100644 transport/internet/finalmask/rawpacket/client_hello.go diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 4ea9aa97c7bb..0001234ade4d 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -1451,6 +1451,7 @@ func (c *FragmentMask) Build() (proto.Message, error) { type RawpacketMask struct { Payload string `json:"payload"` + Sni string `json:"sni"` Method string `json:"method"` TTL int32 `json:"ttl"` Count int32 `json:"count"` @@ -1459,6 +1460,7 @@ type RawpacketMask struct { func (c *RawpacketMask) Build() (proto.Message, error) { config := &rawpacket.Config{ Payload: c.Payload, + Sni: c.Sni, Method: c.Method, Ttl: uint32(c.TTL), Count: c.Count, diff --git a/main/commands/all/tls/fakehello.go b/main/commands/all/tls/fakehello.go new file mode 100644 index 000000000000..d7d84bca6041 --- /dev/null +++ b/main/commands/all/tls/fakehello.go @@ -0,0 +1,48 @@ +package tls + +import ( + "encoding/base64" + "fmt" + "os" + + "github.com/xtls/xray-core/main/commands/base" + "github.com/xtls/xray-core/transport/internet/finalmask/rawpacket" +) + +var cmdFakeHello = &base.Command{ + UsageLine: "{{.Exec}} tls fake-hello [-hex] ", + Short: "Generate a fake TLS ClientHello payload for rawpacket", + Long: ` +Generate a fake TLS ClientHello for use in finalmask rawpacket settings. + +Arguments: + + -base64 + Output base64-encoded payload (default). + -hex + Output hex-encoded payload instead of base64. +`, +} + +func init() { + cmdFakeHello.Run = executeFakeHello +} + +var fakeHelloHex = cmdFakeHello.Flag.Bool("hex", false, "") + +func executeFakeHello(cmd *base.Command, args []string) { + if cmdFakeHello.Flag.NArg() < 1 { + base.Fatalf("sni not specified") + } + sni := cmdFakeHello.Flag.Arg(0) + payload, err := rawpacket.BuildFakeClientHello(sni) + if err != nil { + base.Fatalf("failed to build ClientHello: %s", err) + } + switch { + case *fakeHelloHex: + fmt.Fprintf(os.Stdout, "%x\n", payload) + default: + fmt.Fprintln(os.Stdout, base64.StdEncoding.EncodeToString(payload)) + } +} diff --git a/main/commands/all/tls/tls.go b/main/commands/all/tls/tls.go index 17a9465a7851..27bc4e8c373c 100644 --- a/main/commands/all/tls/tls.go +++ b/main/commands/all/tls/tls.go @@ -15,5 +15,6 @@ var CmdTLS = &base.Command{ cmdPing, cmdHash, cmdECH, + cmdFakeHello, }, } diff --git a/transport/internet/finalmask/rawpacket/client_hello.go b/transport/internet/finalmask/rawpacket/client_hello.go new file mode 100644 index 000000000000..04eec64584da --- /dev/null +++ b/transport/internet/finalmask/rawpacket/client_hello.go @@ -0,0 +1,51 @@ +package rawpacket + +import ( + "bytes" + "context" + "crypto/tls" + "errors" + "io" + "net" + "time" +) + +// BuildFakeClientHello drives crypto/tls against a write-only in-memory conn +// to capture a generated ClientHello. CurvePreferences pins classical groups +// to suppress Go's default X25519MLKEM768 hybrid key share; without this the +// post-quantum public key alone (~1184 bytes) pushes the record past one MSS, +// and middleboxes do not reassemble fragmented ClientHellos. The handshake +// error is discarded because the stub conn's Read returns immediately. +func BuildFakeClientHello(sni string) ([]byte, error) { + if sni == "" { + return nil, errors.New("empty sni") + } + var buf bytes.Buffer + tlsConn := tls.Client(&writeOnlyConn{w: &buf}, &tls.Config{ + ServerName: sni, + // Order matches what browsers advertised before post-quantum. + CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP384}, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS13, + NextProtos: []string{"h2", "http/1.1"}, + InsecureSkipVerify: true, + }) + _ = tlsConn.HandshakeContext(context.Background()) + if buf.Len() == 0 { + return nil, errors.New("tls ClientHello not produced") + } + return buf.Bytes(), nil +} + +type writeOnlyConn struct { + w io.Writer +} + +func (c *writeOnlyConn) Read([]byte) (int, error) { return 0, io.EOF } +func (c *writeOnlyConn) Write(p []byte) (int, error) { return c.w.Write(p) } +func (c *writeOnlyConn) Close() error { return nil } +func (c *writeOnlyConn) LocalAddr() net.Addr { return nil } +func (c *writeOnlyConn) RemoteAddr() net.Addr { return nil } +func (c *writeOnlyConn) SetDeadline(time.Time) error { return nil } +func (c *writeOnlyConn) SetReadDeadline(time.Time) error { return nil } +func (c *writeOnlyConn) SetWriteDeadline(time.Time) error { return nil } diff --git a/transport/internet/finalmask/rawpacket/config.pb.go b/transport/internet/finalmask/rawpacket/config.pb.go index 26f8b63d26ca..9845957d9d85 100644 --- a/transport/internet/finalmask/rawpacket/config.pb.go +++ b/transport/internet/finalmask/rawpacket/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v6.33.5 +// protoc v7.34.1 // source: transport/internet/finalmask/rawpacket/config.proto package rawpacket @@ -24,7 +24,10 @@ const ( type Config struct { state protoimpl.MessageState `protogen:"open.v1"` // Base64-encoded fake payload bytes to inject before the real traffic. + // When empty, sni is used to auto-generate a TLS ClientHello. Payload string `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + // Fake SNI hostname for auto-generated ClientHello when payload is empty. + Sni string `protobuf:"bytes,5,opt,name=sni,proto3" json:"sni,omitempty"` // Corruption method to make the fake packet dropped by the server. // Available: wrong-sequence, wrong-checksum, wrong-ack, wrong-md5, wrong-timestamp. Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"` @@ -74,6 +77,13 @@ func (x *Config) GetPayload() string { return "" } +func (x *Config) GetSni() string { + if x != nil { + return x.Sni + } + return "" +} + func (x *Config) GetMethod() string { if x != nil { return x.Method @@ -99,9 +109,10 @@ var File_transport_internet_finalmask_rawpacket_config_proto protoreflect.FileDe const file_transport_internet_finalmask_rawpacket_config_proto_rawDesc = "" + "\n" + - "3transport/internet/finalmask/rawpacket/config.proto\x12+xray.transport.internet.finalmask.rawpacket\"b\n" + + "3transport/internet/finalmask/rawpacket/config.proto\x12+xray.transport.internet.finalmask.rawpacket\"t\n" + "\x06Config\x12\x18\n" + - "\apayload\x18\x01 \x01(\tR\apayload\x12\x16\n" + + "\apayload\x18\x01 \x01(\tR\apayload\x12\x10\n" + + "\x03sni\x18\x05 \x01(\tR\x03sni\x12\x16\n" + "\x06method\x18\x02 \x01(\tR\x06method\x12\x10\n" + "\x03ttl\x18\x03 \x01(\rR\x03ttl\x12\x14\n" + "\x05count\x18\x04 \x01(\x05R\x05countB\xa3\x01\n" + diff --git a/transport/internet/finalmask/rawpacket/config.proto b/transport/internet/finalmask/rawpacket/config.proto index f6b3e8dcb10e..8a25852468b9 100644 --- a/transport/internet/finalmask/rawpacket/config.proto +++ b/transport/internet/finalmask/rawpacket/config.proto @@ -8,8 +8,12 @@ option java_multiple_files = true; message Config { // Base64-encoded fake payload bytes to inject before the real traffic. + // When empty, sni is used to auto-generate a TLS ClientHello. string payload = 1; + // Fake SNI hostname for auto-generated ClientHello when payload is empty. + string sni = 5; + // Corruption method to make the fake packet dropped by the server. // Available: wrong-sequence, wrong-checksum, wrong-ack, wrong-md5, wrong-timestamp. string method = 2; diff --git a/transport/internet/finalmask/rawpacket/conn.go b/transport/internet/finalmask/rawpacket/conn.go index 188b145ea2f7..cc618cff462a 100644 --- a/transport/internet/finalmask/rawpacket/conn.go +++ b/transport/internet/finalmask/rawpacket/conn.go @@ -75,18 +75,27 @@ type Conn struct { } func NewConnClient(cfg *Config, conn net.Conn) (net.Conn, error) { - if cfg.Payload == "" { + if cfg.Payload == "" && cfg.Sni == "" { return conn, nil } if !PlatformSupported { return nil, errors.New("rawpacket is not supported on this platform") } - payload, err := base64.StdEncoding.DecodeString(cfg.Payload) - if err != nil { - return nil, fmt.Errorf("rawpacket: invalid base64 payload: %w", err) - } - if len(payload) == 0 { - return nil, errors.New("rawpacket: payload is empty") + var payload []byte + var err error + if cfg.Payload != "" { + payload, err = base64.StdEncoding.DecodeString(cfg.Payload) + if err != nil { + return nil, fmt.Errorf("rawpacket: invalid base64 payload: %w", err) + } + if len(payload) == 0 { + return nil, errors.New("rawpacket: payload is empty") + } + } else { + payload, err = BuildFakeClientHello(cfg.Sni) + if err != nil { + return nil, fmt.Errorf("rawpacket: build fake ClientHello: %w", err) + } } method, err := ParseMethod(cfg.Method) if err != nil { @@ -120,18 +129,24 @@ func (c *Conn) Write(b []byte) (n int, err error) { if c.injectionCount >= c.maxInjections { return c.Conn.Write(b) } + closeSpoofer := false + defer func() { + if closeSpoofer { + if closeErr := c.spoofer.Close(); closeErr != nil && err == nil { + err = fmt.Errorf("rawpacket: close spoofer: %w", closeErr) + } + } + }() err = c.spoofer.Inject(c.fakePayload) if err != nil { return 0, fmt.Errorf("rawpacket: inject: %w", err) } c.injectionCount++ if c.injectionCount >= c.maxInjections { - closeErr := c.spoofer.Close() - if closeErr != nil { - return 0, fmt.Errorf("rawpacket: close spoofer: %w", closeErr) - } + closeSpoofer = true } - return c.Conn.Write(b) + n, err = c.Conn.Write(b) + return n, err } func (c *Conn) Close() error { @@ -164,6 +179,8 @@ func wrapPermissionError(err error) error { return fmt.Errorf("%w\n Hint: rawpacket requires root on macOS. Run with: sudo ./xray", err) case "freebsd": return fmt.Errorf("%w\n Hint: rawpacket requires root on FreeBSD. Run with: sudo ./xray", err) + case "windows": + return fmt.Errorf("%w\n Hint: rawpacket requires Administrator on Windows (WinDivert driver)", err) default: return err } diff --git a/transport/internet/finalmask/rawpacket/conn_test.go b/transport/internet/finalmask/rawpacket/conn_test.go index ab77ae584e26..242d00a41a56 100644 --- a/transport/internet/finalmask/rawpacket/conn_test.go +++ b/transport/internet/finalmask/rawpacket/conn_test.go @@ -1,46 +1,151 @@ package rawpacket import ( + "errors" + "io" + "net" + "sync" "testing" + "time" ) -func TestParseMethod(t *testing.T) { - tests := []struct { - input string - expected Method - hasErr bool - }{ - {"", MethodWrongSequence, false}, - {"wrong-sequence", MethodWrongSequence, false}, - {"wrong-checksum", MethodWrongChecksum, false}, - {"wrong-ack", MethodWrongAcknowledgment, false}, - {"wrong-md5", MethodWrongMD5Sig, false}, - {"wrong-timestamp", MethodWrongTimestamp, false}, - {"invalid", 0, true}, - } - for _, tt := range tests { - m, err := ParseMethod(tt.input) - if tt.hasErr { - if err == nil { - t.Errorf("ParseMethod(%q): expected error, got nil", tt.input) - } - continue - } - if err != nil { - t.Errorf("ParseMethod(%q): unexpected error: %v", tt.input, err) - continue - } - if m != tt.expected { - t.Errorf("ParseMethod(%q) = %v, want %v", tt.input, m, tt.expected) - } - } -} - -func TestMethodString(t *testing.T) { - if MethodWrongSequence.String() != "wrong-sequence" { - t.Fatalf("unexpected method string: %s", MethodWrongSequence.String()) - } - if MethodWrongChecksum.String() != "wrong-checksum" { - t.Fatalf("unexpected method string: %s", MethodWrongChecksum.String()) +type mockSpoofer struct { + mu sync.Mutex + calls []string + injectErr error + closeErr error +} + +func (m *mockSpoofer) Inject([]byte) error { + m.mu.Lock() + defer m.mu.Unlock() + m.calls = append(m.calls, "inject") + return m.injectErr +} + +func (m *mockSpoofer) Close() error { + m.mu.Lock() + defer m.mu.Unlock() + m.calls = append(m.calls, "close") + return m.closeErr +} + +func (m *mockSpoofer) callOrder() []string { + m.mu.Lock() + defer m.mu.Unlock() + out := make([]string, len(m.calls)) + copy(out, m.calls) + return out +} + +type recordingConn struct { + mu sync.Mutex + writes [][]byte +} + +func (c *recordingConn) Read([]byte) (int, error) { return 0, io.EOF } +func (c *recordingConn) Write(b []byte) (int, error) { + c.mu.Lock() + defer c.mu.Unlock() + dup := make([]byte, len(b)) + copy(dup, b) + c.writes = append(c.writes, dup) + return len(b), nil +} +func (c *recordingConn) Close() error { return nil } +func (c *recordingConn) LocalAddr() net.Addr { return nil } +func (c *recordingConn) RemoteAddr() net.Addr { return nil } +func (c *recordingConn) SetDeadline(time.Time) error { return nil } +func (c *recordingConn) SetReadDeadline(time.Time) error { return nil } +func (c *recordingConn) SetWriteDeadline(time.Time) error { return nil } + +func (c *recordingConn) wrotePayloads() [][]byte { + c.mu.Lock() + defer c.mu.Unlock() + out := make([][]byte, len(c.writes)) + for i, w := range c.writes { + dup := make([]byte, len(w)) + copy(dup, w) + out[i] = dup + } + return out +} + +func TestWriteCallOrder(t *testing.T) { + spoofer := &mockSpoofer{} + client, server := net.Pipe() + defer client.Close() + defer server.Close() + + go func() { + _, _ = io.ReadAll(server) + }() + + conn := &Conn{ + Conn: client, + spoofer: spoofer, + fakePayload: []byte("fake"), + maxInjections: 1, + } + + if _, err := conn.Write([]byte("real")); err != nil { + t.Fatalf("Write: %v", err) + } + + order := spoofer.callOrder() + if len(order) != 2 || order[0] != "inject" || order[1] != "close" { + t.Fatalf("call order = %v, want [inject close]", order) + } +} + +func TestWriteCloseAfterUnderlyingWrite(t *testing.T) { + spoofer := &mockSpoofer{} + rec := &recordingConn{} + conn := &Conn{ + Conn: rec, + spoofer: spoofer, + fakePayload: []byte("fake"), + maxInjections: 1, + } + + if _, err := conn.Write([]byte("real")); err != nil { + t.Fatalf("Write: %v", err) + } + if len(rec.wrotePayloads()) != 1 { + t.Fatalf("expected one underlying write, got %d", len(rec.wrotePayloads())) + } + if order := spoofer.callOrder(); len(order) != 2 || order[1] != "close" { + t.Fatalf("close not last: %v", order) } } + +func TestBuildFakeClientHello(t *testing.T) { + hello, err := BuildFakeClientHello("hcaptcha.com") + if err != nil { + t.Fatalf("buildFakeClientHello: %v", err) + } + if len(hello) == 0 { + t.Fatal("empty ClientHello") + } + if hello[0] != 0x16 { + t.Fatalf("expected TLS handshake record (0x16), got 0x%x", hello[0]) + } +} + +func TestBuildFakeClientHelloEmptySNI(t *testing.T) { + _, err := BuildFakeClientHello("") + if err == nil { + t.Fatal("expected error for empty sni") + } +} + +type discardConn struct{} + +func (discardConn) Read([]byte) (int, error) { return 0, io.EOF } +func (discardConn) Write([]byte) (int, error) { return 0, errors.New("unexpected write") } +func (discardConn) Close() error { return nil } +func (discardConn) LocalAddr() net.Addr { return nil } +func (discardConn) RemoteAddr() net.Addr { return nil } +func (discardConn) SetDeadline(time.Time) error { return nil } +func (discardConn) SetReadDeadline(time.Time) error { return nil } +func (discardConn) SetWriteDeadline(time.Time) error { return nil } From a6e8fae27bc68764935a2ac50cdbbc63d0e772b2 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Fri, 29 May 2026 04:41:00 +0600 Subject: [PATCH 28/42] Update config.pb.go --- transport/internet/finalmask/rawpacket/config.pb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transport/internet/finalmask/rawpacket/config.pb.go b/transport/internet/finalmask/rawpacket/config.pb.go index 9845957d9d85..2ba1457af33c 100644 --- a/transport/internet/finalmask/rawpacket/config.pb.go +++ b/transport/internet/finalmask/rawpacket/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v7.34.1 +// protoc v6.33.5 // source: transport/internet/finalmask/rawpacket/config.proto package rawpacket From 945350608a7cbffe864f37da9749df4e6e704dec Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Wed, 3 Jun 2026 19:00:15 +0600 Subject: [PATCH 29/42] Remove stale imports for removed header sub-packages (dns/dtls/srtp/utp/wechat/wireguard) --- infra/conf/transport_internet.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index fed1225765bb..f3c6de6e3ca7 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -24,12 +24,6 @@ import ( "github.com/xtls/xray-core/transport/internet/finalmask/fragment" "github.com/xtls/xray-core/transport/internet/finalmask/header/custom" "github.com/xtls/xray-core/transport/internet/finalmask/rawpacket" - "github.com/xtls/xray-core/transport/internet/finalmask/header/dns" - "github.com/xtls/xray-core/transport/internet/finalmask/header/dtls" - "github.com/xtls/xray-core/transport/internet/finalmask/header/srtp" - "github.com/xtls/xray-core/transport/internet/finalmask/header/utp" - "github.com/xtls/xray-core/transport/internet/finalmask/header/wechat" - "github.com/xtls/xray-core/transport/internet/finalmask/header/wireguard" "github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm" "github.com/xtls/xray-core/transport/internet/finalmask/mkcp/header" "github.com/xtls/xray-core/transport/internet/finalmask/mkcp/original" From cbede9a9ac93b81f7a5abd3485a9cd89eed7cf74 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Wed, 3 Jun 2026 20:27:06 +0600 Subject: [PATCH 30/42] Fix gofumpt formatting issues in rawpacket files --- infra/conf/transport_internet.go | 2 +- .../internet/finalmask/rawpacket/conn_test.go | 18 +++++------ .../internet/finalmask/rawpacket/endpoints.go | 3 +- .../internet/finalmask/rawpacket/packet.go | 3 +- .../finalmask/rawpacket/raw_darwin.go | 5 ++-- .../finalmask/rawpacket/raw_freebsd.go | 22 +++++++------- .../internet/finalmask/rawpacket/raw_stub.go | 3 +- .../internet/finalmask/rawpacket/tcpip.go | 30 +++++++++---------- 8 files changed, 41 insertions(+), 45 deletions(-) diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index f3c6de6e3ca7..21704d28eef2 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -23,11 +23,11 @@ import ( "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/finalmask/fragment" "github.com/xtls/xray-core/transport/internet/finalmask/header/custom" - "github.com/xtls/xray-core/transport/internet/finalmask/rawpacket" "github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm" "github.com/xtls/xray-core/transport/internet/finalmask/mkcp/header" "github.com/xtls/xray-core/transport/internet/finalmask/mkcp/original" "github.com/xtls/xray-core/transport/internet/finalmask/noise" + "github.com/xtls/xray-core/transport/internet/finalmask/rawpacket" "github.com/xtls/xray-core/transport/internet/finalmask/realm" "github.com/xtls/xray-core/transport/internet/finalmask/salamander" finalsudoku "github.com/xtls/xray-core/transport/internet/finalmask/sudoku" diff --git a/transport/internet/finalmask/rawpacket/conn_test.go b/transport/internet/finalmask/rawpacket/conn_test.go index 242d00a41a56..64166f5440dc 100644 --- a/transport/internet/finalmask/rawpacket/conn_test.go +++ b/transport/internet/finalmask/rawpacket/conn_test.go @@ -43,7 +43,7 @@ type recordingConn struct { writes [][]byte } -func (c *recordingConn) Read([]byte) (int, error) { return 0, io.EOF } +func (c *recordingConn) Read([]byte) (int, error) { return 0, io.EOF } func (c *recordingConn) Write(b []byte) (int, error) { c.mu.Lock() defer c.mu.Unlock() @@ -141,11 +141,11 @@ func TestBuildFakeClientHelloEmptySNI(t *testing.T) { type discardConn struct{} -func (discardConn) Read([]byte) (int, error) { return 0, io.EOF } -func (discardConn) Write([]byte) (int, error) { return 0, errors.New("unexpected write") } -func (discardConn) Close() error { return nil } -func (discardConn) LocalAddr() net.Addr { return nil } -func (discardConn) RemoteAddr() net.Addr { return nil } -func (discardConn) SetDeadline(time.Time) error { return nil } -func (discardConn) SetReadDeadline(time.Time) error { return nil } -func (discardConn) SetWriteDeadline(time.Time) error { return nil } +func (discardConn) Read([]byte) (int, error) { return 0, io.EOF } +func (discardConn) Write([]byte) (int, error) { return 0, errors.New("unexpected write") } +func (discardConn) Close() error { return nil } +func (discardConn) LocalAddr() net.Addr { return nil } +func (discardConn) RemoteAddr() net.Addr { return nil } +func (discardConn) SetDeadline(time.Time) error { return nil } +func (discardConn) SetReadDeadline(time.Time) error { return nil } +func (discardConn) SetWriteDeadline(time.Time) error { return nil } diff --git a/transport/internet/finalmask/rawpacket/endpoints.go b/transport/internet/finalmask/rawpacket/endpoints.go index 6c7107eb3987..cc37c3223c9f 100644 --- a/transport/internet/finalmask/rawpacket/endpoints.go +++ b/transport/internet/finalmask/rawpacket/endpoints.go @@ -1,10 +1,9 @@ package rawpacket import ( + "errors" "net" "net/netip" - - "errors" ) // The returned addresses are v4-unmapped and share the same family. diff --git a/transport/internet/finalmask/rawpacket/packet.go b/transport/internet/finalmask/rawpacket/packet.go index 914dc04760b8..de821c269f2f 100644 --- a/transport/internet/finalmask/rawpacket/packet.go +++ b/transport/internet/finalmask/rawpacket/packet.go @@ -2,9 +2,8 @@ package rawpacket import ( "encoding/binary" - "net/netip" - "fmt" + "net/netip" ) const ( diff --git a/transport/internet/finalmask/rawpacket/raw_darwin.go b/transport/internet/finalmask/rawpacket/raw_darwin.go index 1b2335565ae2..10c483c1b72a 100644 --- a/transport/internet/finalmask/rawpacket/raw_darwin.go +++ b/transport/internet/finalmask/rawpacket/raw_darwin.go @@ -2,6 +2,8 @@ package rawpacket import ( "encoding/binary" + "errors" + "fmt" "net" "net/netip" "strconv" @@ -9,9 +11,6 @@ import ( "sync" "syscall" - "errors" - "fmt" - "golang.org/x/sys/unix" ) diff --git a/transport/internet/finalmask/rawpacket/raw_freebsd.go b/transport/internet/finalmask/rawpacket/raw_freebsd.go index b3d2e13492a8..c664a2862028 100644 --- a/transport/internet/finalmask/rawpacket/raw_freebsd.go +++ b/transport/internet/finalmask/rawpacket/raw_freebsd.go @@ -17,17 +17,17 @@ const PlatformSupported = true // FreeBSD tcp_info offsets for snd_nxt and rcv_nxt. // Derived from FreeBSD sys/netinet/tcp.h struct tcp_info layout. // -// struct tcp_info { -// u8 state, __ca, __retrans, __probes, __backoff, opts, wscale = 8 bytes (with pad) -// u32 rto, __ato, snd_mss, rcv_mss = 16 bytes (offset 8) -// u32 __unacked, __sacked, __lost, __retrans, __fackets = 20 bytes (offset 24) -// u32 __last_data_sent, __last_ack_sent, last_data_recv, __last_ack_recv = 16 bytes (offset 44) -// u32 __pmtu, __rcv_ssthresh, rtt, rttvar, snd_ssthresh, snd_cwnd, __advmss, __reordering = 32 bytes (offset 60) -// u32 __rcv_rtt, rcv_space = 8 bytes (offset 92) -// u32 snd_wnd, snd_bwnd = 8 bytes (offset 100) -// u32 snd_nxt, rcv_nxt = 8 bytes (offset 108) -// ... remaining fields -// } +// struct tcp_info { +// u8 state, __ca, __retrans, __probes, __backoff, opts, wscale = 8 bytes (with pad) +// u32 rto, __ato, snd_mss, rcv_mss = 16 bytes (offset 8) +// u32 __unacked, __sacked, __lost, __retrans, __fackets = 20 bytes (offset 24) +// u32 __last_data_sent, __last_ack_sent, last_data_recv, __last_ack_recv = 16 bytes (offset 44) +// u32 __pmtu, __rcv_ssthresh, rtt, rttvar, snd_ssthresh, snd_cwnd, __advmss, __reordering = 32 bytes (offset 60) +// u32 __rcv_rtt, rcv_space = 8 bytes (offset 92) +// u32 snd_wnd, snd_bwnd = 8 bytes (offset 100) +// u32 snd_nxt, rcv_nxt = 8 bytes (offset 108) +// ... remaining fields +// } const ( freebsdTCPInfoSndNxtOffset = 108 freebsdTCPInfoRcvNxtOffset = 112 diff --git a/transport/internet/finalmask/rawpacket/raw_stub.go b/transport/internet/finalmask/rawpacket/raw_stub.go index c06a40f48bb2..596a9713a5f1 100644 --- a/transport/internet/finalmask/rawpacket/raw_stub.go +++ b/transport/internet/finalmask/rawpacket/raw_stub.go @@ -3,9 +3,8 @@ package rawpacket import ( - "net" - "errors" + "net" ) const PlatformSupported = false diff --git a/transport/internet/finalmask/rawpacket/tcpip.go b/transport/internet/finalmask/rawpacket/tcpip.go index 8814422e35ed..80ff010d0660 100644 --- a/transport/internet/finalmask/rawpacket/tcpip.go +++ b/transport/internet/finalmask/rawpacket/tcpip.go @@ -11,11 +11,11 @@ const ( TCPMinimumSize = 20 TCPProtocolNumber = 6 - TCPOptionEOL = 0 - TCPOptionNOP = 1 - TCPOptionTS = 8 + TCPOptionEOL = 0 + TCPOptionNOP = 1 + TCPOptionTS = 8 TCPOptionTSLength = 10 - + TCPFlagFin = 0x01 TCPFlagSyn = 0x02 TCPFlagRst = 0x04 @@ -95,15 +95,15 @@ func ParseTCPOptions(b []byte) (tsVal uint32, hasTS bool) { // IPv4 header representation type IPv4 []byte -func (b IPv4) TotalLength() uint16 { return binary.BigEndian.Uint16(b[2:]) } -func (b IPv4) Flags() uint8 { return uint8(binary.BigEndian.Uint16(b[6:]) >> 13) } +func (b IPv4) TotalLength() uint16 { return binary.BigEndian.Uint16(b[2:]) } +func (b IPv4) Flags() uint8 { return uint8(binary.BigEndian.Uint16(b[6:]) >> 13) } func (b IPv4) FragmentOffset() uint16 { return binary.BigEndian.Uint16(b[6:]) & 0x1fff } -func (b IPv4) Protocol() uint8 { return b[9] } -func (b IPv4) HeaderLength() uint8 { return (b[0] & 0x0f) * 4 } +func (b IPv4) Protocol() uint8 { return b[9] } +func (b IPv4) HeaderLength() uint8 { return (b[0] & 0x0f) * 4 } func (b IPv4) Encode(totalLength uint16, id uint16, ttl uint8, protocol uint8, src, dst netip.Addr) { b[0] = (4 << 4) | 5 // IPv4, Header Length = 20 - b[1] = 0 // TOS + b[1] = 0 // TOS binary.BigEndian.PutUint16(b[2:], totalLength) binary.BigEndian.PutUint16(b[4:], id) binary.BigEndian.PutUint16(b[6:], 0) // Flags and Fragment Offset @@ -118,7 +118,7 @@ func (b IPv4) Encode(totalLength uint16, id uint16, ttl uint8, protocol uint8, s type IPv6 []byte -func (b IPv6) PayloadLength() uint16 { return binary.BigEndian.Uint16(b[4:]) } +func (b IPv6) PayloadLength() uint16 { return binary.BigEndian.Uint16(b[4:]) } func (b IPv6) TransportProtocol() uint8 { return b[6] } func (b IPv6) Encode(payloadLength uint16, transportProtocol uint8, hopLimit uint8, src, dst netip.Addr) { @@ -132,10 +132,10 @@ func (b IPv6) Encode(payloadLength uint16, transportProtocol uint8, hopLimit uin type TCP []byte -func (b TCP) DataOffset() uint8 { return (b[12] >> 4) * 4 } -func (b TCP) SequenceNumber() uint32 { return binary.BigEndian.Uint32(b[4:]) } -func (b TCP) AckNumber() uint32 { return binary.BigEndian.Uint32(b[8:]) } -func (b TCP) Options() []byte { return b[TCPMinimumSize:b.DataOffset()] } +func (b TCP) DataOffset() uint8 { return (b[12] >> 4) * 4 } +func (b TCP) SequenceNumber() uint32 { return binary.BigEndian.Uint32(b[4:]) } +func (b TCP) AckNumber() uint32 { return binary.BigEndian.Uint32(b[8:]) } +func (b TCP) Options() []byte { return b[TCPMinimumSize:b.DataOffset()] } func (b TCP) SetChecksum(csum uint16) { binary.BigEndian.PutUint16(b[16:], csum) } func (b TCP) Encode(srcPort, dstPort uint16, seqNum, ackNum uint32, dataOffset uint8, flags uint8, windowSize uint16) { @@ -146,7 +146,7 @@ func (b TCP) Encode(srcPort, dstPort uint16, seqNum, ackNum uint32, dataOffset u b[12] = (dataOffset / 4) << 4 b[13] = flags binary.BigEndian.PutUint16(b[14:], windowSize) - b[16] = 0 // Checksum + b[16] = 0 // Checksum binary.BigEndian.PutUint16(b[18:], 0) // Urgent pointer } From 3f886a4a5c20344c6d6790f8fbfc5f0f4cd22074 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Thu, 4 Jun 2026 21:11:20 +0600 Subject: [PATCH 31/42] rawpacket: capture+modify spoofing with bidirectional WinDivert --- .../internet/finalmask/rawpacket/conn.go | 8 +- .../internet/finalmask/rawpacket/packet.go | 192 ++++++++++++++++-- .../finalmask/rawpacket/raw_darwin.go | 6 +- .../finalmask/rawpacket/raw_freebsd.go | 6 +- .../internet/finalmask/rawpacket/raw_linux.go | 27 +-- .../finalmask/rawpacket/raw_windows.go | 82 ++++++-- .../internet/finalmask/rawpacket/tcpip.go | 41 ++++ .../finalmask/rawpacket/windivert/filter.go | 19 +- 8 files changed, 321 insertions(+), 60 deletions(-) diff --git a/transport/internet/finalmask/rawpacket/conn.go b/transport/internet/finalmask/rawpacket/conn.go index cc618cff462a..4c762a3afc2a 100644 --- a/transport/internet/finalmask/rawpacket/conn.go +++ b/transport/internet/finalmask/rawpacket/conn.go @@ -150,12 +150,12 @@ func (c *Conn) Write(b []byte) (n int, err error) { } func (c *Conn) Close() error { - connErr := c.Conn.Close() spooferErr := c.spoofer.Close() - if connErr != nil { - return connErr + connErr := c.Conn.Close() + if spooferErr != nil { + return spooferErr } - return spooferErr + return connErr } func (c *Conn) TcpMaskConn() {} diff --git a/transport/internet/finalmask/rawpacket/packet.go b/transport/internet/finalmask/rawpacket/packet.go index de821c269f2f..0addd3239997 100644 --- a/transport/internet/finalmask/rawpacket/packet.go +++ b/transport/internet/finalmask/rawpacket/packet.go @@ -2,8 +2,10 @@ package rawpacket import ( "encoding/binary" + "errors" "fmt" "net/netip" + "time" ) const ( @@ -81,19 +83,51 @@ func buildSpoofTCPSegment(method Method, src, dst netip.AddrPort, sendNext, rece return segment, nil } +func buildTimestampOption(tsVal, tsEcr uint32) []byte { + b := make([]byte, TCPOptionTSLength+2) + EncodeTSOption(tsVal, tsEcr, b) + return b +} + func resolveSpoofPacketInfo(method Method, sendNext, receiveNext, timestamp uint32, tcpOptions, payload []byte) (spoofPacketInfo, error) { packetInfo := spoofPacketInfo{seqNum: sendNext, ackNum: receiveNext} + // Always include a valid TCP timestamp option in all methods. + // Modern TCP connections always carry timestamps. A segment without + // them is immediately flagged as anomalous by DPI equipment. + tsVal := timestamp + if tsVal == 0 { + tsVal = uint32(time.Now().UnixMilli()) + } + tsOpt := buildTimestampOption(tsVal, 0) switch method { case MethodWrongSequence: packetInfo.seqNum = sendNext - uint32(len(payload)) + packetInfo.options = tsOpt case MethodWrongChecksum: packetInfo.corrupt = true + packetInfo.options = tsOpt case MethodWrongAcknowledgment: packetInfo.ackNum = receiveNext - uint32(defaultWindowSize/2) + packetInfo.options = tsOpt case MethodWrongMD5Sig: - packetInfo.options = buildMD5SignatureOptions() + md5Opt := buildMD5SignatureOptions() + combined := make([]byte, 0, len(tsOpt)+2+len(md5Opt)) + combined = append(combined, tsOpt...) + combined = append(combined, TCPOptionNOP, TCPOptionNOP) + combined = append(combined, md5Opt...) + packetInfo.options = combined case MethodWrongTimestamp: - packetInfo.options = buildWrongTimestampOptions(timestamp, tcpOptions) + backdated := tsVal + if backdated > tcpTimestampBackdate { + backdated -= tcpTimestampBackdate + } else { + backdated = 0 + } + if rewriteTCPOptionTimestamp(tcpOptions, backdated) { + packetInfo.options = tcpOptions + } else { + packetInfo.options = buildTimestampOption(backdated, 0) + } default: return packetInfo, fmt.Errorf("rawpacket: unknown method %v", method) } @@ -107,21 +141,6 @@ func buildMD5SignatureOptions() []byte { return options } -func buildWrongTimestampOptions(timestamp uint32, tcpOptions []byte) []byte { - spoofedTimestamp := timestamp - if spoofedTimestamp > tcpTimestampBackdate { - spoofedTimestamp -= tcpTimestampBackdate - } else { - spoofedTimestamp = 0 - } - if rewriteTCPOptionTimestamp(tcpOptions, spoofedTimestamp) { - return tcpOptions - } - options := make([]byte, TCPOptionTSLength+2) - EncodeTSOption(spoofedTimestamp, 0, options) - return options -} - // rewriteTCPOptionTimestamp finds the TS option in tcpOptions and writes // timestamp into its TSVal field in place. The caller must own tcpOptions // (parseTCPPacket already returns a private copy on Windows). @@ -150,6 +169,145 @@ func rewriteTCPOptionTimestamp(tcpOptions []byte, timestamp uint32) bool { return false } +// buildSpoofFromCapturedPacket takes a captured IP+TCP packet and builds a +// spoofed version that preserves the real connection's TCP options, IP ID +// sequencing, and window size. +func buildSpoofFromCapturedPacket(captured []byte, isV6 bool, synSeq uint32, fakePayload []byte, method Method) ([]byte, error) { + var ipHdrLen int + var srcAddr, dstAddr netip.Addr + + if isV6 { + if len(captured) < IPv6MinimumSize+TCPMinimumSize { + return nil, errors.New("rawpacket: captured packet too short for IPv6") + } + ip := IPv6(captured) + if ip.TransportProtocol() != TCPProtocolNumber { + return nil, errors.New("rawpacket: captured packet is not TCP") + } + ipHdrLen = IPv6MinimumSize + srcAddr = ip.Src() + dstAddr = ip.Dst() + } else { + if len(captured) < IPv4MinimumSize+TCPMinimumSize { + return nil, errors.New("rawpacket: captured packet too short for IPv4") + } + ip := IPv4(captured) + if ip.Protocol() != TCPProtocolNumber { + return nil, errors.New("rawpacket: captured packet is not TCP") + } + ipHdrLen = int(ip.HeaderLength()) + if ipHdrLen < IPv4MinimumSize || ipHdrLen > len(captured) { + return nil, fmt.Errorf("rawpacket: invalid IPv4 header length %d", ipHdrLen) + } + srcAddr = ip.Src() + dstAddr = ip.Dst() + } + + if ipHdrLen+TCPMinimumSize > len(captured) { + return nil, errors.New("rawpacket: captured packet truncated") + } + + tcp := TCP(captured[ipHdrLen:]) + tcpHdrLen := int(tcp.DataOffset()) + if tcpHdrLen < TCPMinimumSize || ipHdrLen+tcpHdrLen > len(captured) { + return nil, fmt.Errorf("rawpacket: invalid TCP header length %d in captured packet", tcpHdrLen) + } + + capturedAck := tcp.AckNumber() + capturedFlags := tcp.Flags() + + // Preserve captured TCP options (timestamp, SACK, window scale, etc.) + // We work on a copy, not the original. + tcpOpts := make([]byte, tcpHdrLen-TCPMinimumSize) + copy(tcpOpts, tcp.Options()) + + // Determine captured total length + var totalLen int + if isV6 { + totalLen = ipHdrLen + int(IPv6(captured).PayloadLength()) + } else { + totalLen = int(IPv4(captured).TotalLength()) + } + if totalLen > len(captured) { + totalLen = len(captured) + } + originalPayloadLen := totalLen - ipHdrLen - tcpHdrLen + if originalPayloadLen < 0 { + originalPayloadLen = 0 + } + + // Allocate output: IP hdr + TCP hdr (with copied options) + fake payload + newTotalLen := ipHdrLen + tcpHdrLen + len(fakePayload) + out := make([]byte, newTotalLen) + + // Copy IP header + copy(out[:ipHdrLen], captured[:ipHdrLen]) + + // Copy TCP header + options (original payload is NOT copied) + copy(out[ipHdrLen:ipHdrLen+tcpHdrLen], captured[ipHdrLen:ipHdrLen+tcpHdrLen]) + + // Write fake payload + copy(out[ipHdrLen+tcpHdrLen:], fakePayload) + + // --- Modify IP header --- + if isV6 { + ip6 := IPv6(out) + ip6.SetPayloadLength(uint16(tcpHdrLen + len(fakePayload))) + } else { + ip4 := IPv4(out) + ip4.SetTotalLength(uint16(newTotalLen)) + // Increment IP ID by 1 to maintain sequential appearance + ip4.SetID(ip4.ID() + 1) + ip4.RecalcChecksum() + } + + // --- Modify TCP header --- + tcpOut := TCP(out[ipHdrLen:]) + + // Determine new seq/ack based on method + switch method { + case MethodWrongSequence: + // seq = synSeq + 1 - len(fake) places the spoofed packet before the window + newSeq := (synSeq + 1 - uint32(len(fakePayload))) & 0xffffffff + tcpOut.SetSequenceNumber(newSeq) + case MethodWrongAcknowledgment: + tcpOut.SetAckNumber(capturedAck - uint32(defaultWindowSize/2)) + } + + // Backdate timestamp for wrong-timestamp method + if method == MethodWrongTimestamp { + opts := tcpOut.Options() + tsSlice := make([]byte, len(opts)) + copy(tsSlice, opts) + if tsVal, hasTS := ParseTCPOptions(tsSlice); hasTS { + backdated := tsVal + if backdated > tcpTimestampBackdate { + backdated -= tcpTimestampBackdate + } else { + backdated = 0 + } + rewriteTCPOptionTimestamp(opts, backdated) + } + } + + // Set PSH flag since the spoofed packet is a data segment + tcpOut.SetFlags(capturedFlags | TCPFlagPsh) + + // Recalculate TCP checksum + tcpOut.SetChecksum(0) + tcpLen := tcpHdrLen + len(fakePayload) + pseudo := PseudoHeaderChecksum(TCPProtocolNumber, srcAddr.AsSlice(), dstAddr.AsSlice(), uint16(tcpLen)) + tcpChecksum := ^tcpOut.CalculateChecksum(pseudo) + + // Apply checksum corruption for wrong-checksum method + if method == MethodWrongChecksum { + tcpChecksum ^= 0xFFFF + } + tcpOut.SetChecksum(tcpChecksum) + + return out, nil +} + func applyTCPChecksum(tcp TCP, srcAddr, dstAddr netip.Addr, payload []byte, corrupt bool) { tcpLen := int(tcp.DataOffset()) + len(payload) pseudo := PseudoHeaderChecksum(TCPProtocolNumber, srcAddr.AsSlice(), dstAddr.AsSlice(), uint16(tcpLen)) diff --git a/transport/internet/finalmask/rawpacket/raw_darwin.go b/transport/internet/finalmask/rawpacket/raw_darwin.go index 10c483c1b72a..3d2af14afdd1 100644 --- a/transport/internet/finalmask/rawpacket/raw_darwin.go +++ b/transport/internet/finalmask/rawpacket/raw_darwin.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "errors" "fmt" + "math/rand" "net" "net/netip" "strconv" @@ -177,11 +178,14 @@ func (s *darwinSpoofer) Inject(payload []byte) error { if err != nil { return err } + ip := IPv4(frame) + // Non-zero IP ID avoids DPI flagging. + ip.SetID(uint16(rand.Uint32())) // Darwin inherits the historical BSD quirk: with IP_HDRINCL the kernel // expects ip_len and ip_off in host byte order, not network byte order. - ip := IPv4(frame) binary.NativeEndian.PutUint16(ip[2:4], ip.TotalLength()) binary.NativeEndian.PutUint16(ip[6:8], uint16(ip.Flags())<<13|ip.FragmentOffset()) + ip.RecalcChecksum() err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) if err != nil { return fmt.Errorf("sendto raw socket: %w", err) diff --git a/transport/internet/finalmask/rawpacket/raw_freebsd.go b/transport/internet/finalmask/rawpacket/raw_freebsd.go index c664a2862028..a8cd12db9ef0 100644 --- a/transport/internet/finalmask/rawpacket/raw_freebsd.go +++ b/transport/internet/finalmask/rawpacket/raw_freebsd.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "errors" "fmt" + "math/rand" "net" "net/netip" "syscall" @@ -152,11 +153,14 @@ func (s *freebsdSpoofer) Inject(payload []byte) error { if err != nil { return err } + ip := IPv4(frame) + // Non-zero IP ID avoids DPI flagging. + ip.SetID(uint16(rand.Uint32())) // FreeBSD inherits the historical BSD quirk: with IP_HDRINCL the kernel // expects ip_len and ip_off in host byte order, not network byte order. - ip := IPv4(frame) binary.NativeEndian.PutUint16(ip[2:4], ip.TotalLength()) binary.NativeEndian.PutUint16(ip[6:8], uint16(ip.Flags())<<13|ip.FragmentOffset()) + ip.RecalcChecksum() err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) if err != nil { return fmt.Errorf("rawpacket: sendto raw socket: %w", err) diff --git a/transport/internet/finalmask/rawpacket/raw_linux.go b/transport/internet/finalmask/rawpacket/raw_linux.go index 1de3ff862839..0786ca9851be 100644 --- a/transport/internet/finalmask/rawpacket/raw_linux.go +++ b/transport/internet/finalmask/rawpacket/raw_linux.go @@ -2,6 +2,7 @@ package rawpacket import ( "fmt" + "math/rand" "net" "net/netip" @@ -75,13 +76,9 @@ func openLinuxRawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) { return fd, sockaddr, nil } -// loadSequenceNumbers puts the socket briefly into TCP_REPAIR mode to read -// snd_nxt and rcv_nxt from the kernel. TCP_REPAIR requires CAP_NET_ADMIN; -// callers must run as root or grant both CAP_NET_RAW and CAP_NET_ADMIN. -// -// If the TCP_REPAIR_OFF revert fails, the socket would stay in TCP_REPAIR -// state and subsequent Write() calls would silently buffer instead of sending. -// Surface that error so callers can abort. +// loadSequenceNumbers briefly enters TCP_REPAIR mode to read snd_nxt and +// rcv_nxt from the kernel, then immediately exits TCP_REPAIR. TCP_REPAIR +// requires CAP_NET_ADMIN. func (s *linuxSpoofer) loadSequenceNumbers(tcpConn *net.TCPConn) error { rawConn, err := tcpConn.SyscallConn() if err != nil { @@ -91,12 +88,8 @@ func (s *linuxSpoofer) loadSequenceNumbers(tcpConn *net.TCPConn) error { err = rawConn.Control(func(raw uintptr) { fd := int(raw) - if s.method == MethodWrongTimestamp { - timestamp, tsErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_TIMESTAMP) - if tsErr != nil { - ctrlErr = fmt.Errorf("rawpacket: read timestamp: %w", tsErr) - return - } + timestamp, tsErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_TIMESTAMP) + if tsErr == nil { s.timestamp = uint32(timestamp) } @@ -151,6 +144,14 @@ func (s *linuxSpoofer) Inject(payload []byte) error { if err != nil { return err } + // Use a non-zero IP ID. The buildSpoofFrame → buildTCPSegment path + // passes id=0 to IPv4.Encode; override it with a random value since + // IP ID 0 is a DPI red flag. + if s.src.Addr().Is4() && len(frame) >= IPv4MinimumSize { + ip := IPv4(frame) + ip.SetID(uint16(rand.Uint32())) + ip.RecalcChecksum() + } err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) if err != nil { return fmt.Errorf("sendto raw socket: %w", err) diff --git a/transport/internet/finalmask/rawpacket/raw_windows.go b/transport/internet/finalmask/rawpacket/raw_windows.go index acfed30ff0c8..9d1ef7ed5a45 100644 --- a/transport/internet/finalmask/rawpacket/raw_windows.go +++ b/transport/internet/finalmask/rawpacket/raw_windows.go @@ -3,6 +3,7 @@ package rawpacket import ( + "encoding/binary" "errors" "net" "net/netip" @@ -45,7 +46,7 @@ func newRawSpoofer(conn net.Conn, method Method, ttl uint8) (rawSpoofer, error) if err != nil { return nil, err } - filter, err := windivert.OutboundTCP(src, dst) + filter, err := windivert.BidirectionalTCP(src, dst) if err != nil { return nil, err } @@ -113,22 +114,64 @@ func (s *windowsSpoofer) run() { return } pkt := buf[:n] - seq, ack, tcpOptions, payloadLen, ok := parseTCPPacket(pkt, addr.IPv6()) + seq, _, _, payloadLen, ok := parseTCPPacket(pkt, addr.IPv6()) if !ok { - // Our filter is OutboundTCP(src, dst); a non-TCP or truncated - // match means driver state is suspect. Re-inject so the kernel - // still sees the byte stream, then abort — continuing would risk - // reordering against an unknown reference point. _, sendErr := s.divertH.Send(pkt, &addr) if sendErr != nil { s.recordErr(sendErr) return } - s.recordErr(errors.New("windivert received malformed packet matching spoof filter")) - return + continue + } + + // Check direction. s.src is the local (client) address. + var isOutbound bool + if addr.IPv6() { + if len(pkt) < IPv6MinimumSize+TCPMinimumSize { + _, _ = s.divertH.Send(pkt, &addr) + continue + } + ip6 := IPv6(pkt) + srcIP := ip6.Src() + srcPort := binary.BigEndian.Uint16(pkt[IPv6MinimumSize:]) + if srcIP == s.src.Addr() && srcPort == s.src.Port() { + isOutbound = true + } else if srcIP == s.dst.Addr() && srcPort == s.dst.Port() { + isOutbound = false + } else { + _, _ = s.divertH.Send(pkt, &addr) + continue + } + } else { + if len(pkt) < IPv4MinimumSize+TCPMinimumSize { + _, _ = s.divertH.Send(pkt, &addr) + continue + } + ip4 := IPv4(pkt) + srcIP := ip4.Src() + srcPort := binary.BigEndian.Uint16(pkt[IPv4MinimumSize:]) + if srcIP == s.src.Addr() && srcPort == s.src.Port() { + isOutbound = true + } else if srcIP == s.dst.Addr() && srcPort == s.dst.Port() { + isOutbound = false + } else { + _, _ = s.divertH.Send(pkt, &addr) + continue + } + } + + if !isOutbound { + // Inbound (server→client) — pass through unchanged. + _, err := s.divertH.Send(pkt, &addr) + if err != nil { + s.recordErr(err) + return + } + continue } + if payloadLen == 0 { - // Handshake ACK, keepalive, FIN — pass through unchanged. + // Outbound ACK, keepalive, FIN — pass through unchanged. _, err := s.divertH.Send(pkt, &addr) if err != nil { s.recordErr(err) @@ -137,7 +180,7 @@ func (s *windowsSpoofer) run() { continue } - // Non-empty outbound TCP payload = the real ClientHello. + // Outbound data packet — the real ClientHello. var fake []byte select { case fake = <-s.fakeReady: @@ -151,20 +194,21 @@ func (s *windowsSpoofer) run() { continue } - var timestamp uint32 - if tsVal, hasTS := ParseTCPOptions(tcpOptions); hasTS { - timestamp = tsVal - } - frame, err := buildSpoofFrame(s.method, s.src, s.dst, seq, ack, timestamp, tcpOptions, fake, s.ttl) + // Build the spoofed packet from the captured real packet template. + // This preserves all TCP options and IP ID sequencing from the real + // connection. synSeq is derived from the captured data seq (first + // data after handshake always has seq = synSeq + 1). + synSeq := seq - 1 + frame, err := buildSpoofFromCapturedPacket(pkt, addr.IPv6(), synSeq, fake, s.method) if err != nil { s.recordErr(err) return } fakeAddr := addr // inherit Outbound, IfIdx - // buildSpoofFrame emits ready-to-wire bytes. The driver recomputes - // checksums on Send when TCPChecksum/IPChecksum are 0 — which would - // overwrite the intentionally corrupt checksum in WrongChecksum mode. - // Force both to 1 to keep our bytes intact. + // buildSpoofFromCapturedPacket emits ready-to-wire bytes with + // correct checksums. The driver would recompute checksums on Send + // when TCPChecksum/IPChecksum are 0. Force both to 1 to preserve + // intentional corruption (wrong-checksum method) and keep our bytes. fakeAddr.SetIPChecksum(true) fakeAddr.SetTCPChecksum(true) _, err = s.divertH.Send(frame, &fakeAddr) diff --git a/transport/internet/finalmask/rawpacket/tcpip.go b/transport/internet/finalmask/rawpacket/tcpip.go index 80ff010d0660..0bb18cc0aea0 100644 --- a/transport/internet/finalmask/rawpacket/tcpip.go +++ b/transport/internet/finalmask/rawpacket/tcpip.go @@ -100,6 +100,23 @@ func (b IPv4) Flags() uint8 { return uint8(binary.BigEndian.Uint16(b[6 func (b IPv4) FragmentOffset() uint16 { return binary.BigEndian.Uint16(b[6:]) & 0x1fff } func (b IPv4) Protocol() uint8 { return b[9] } func (b IPv4) HeaderLength() uint8 { return (b[0] & 0x0f) * 4 } +func (b IPv4) ID() uint16 { return binary.BigEndian.Uint16(b[4:]) } +func (b IPv4) Src() netip.Addr { return netip.AddrFrom4([4]byte(b[12:16])) } +func (b IPv4) Dst() netip.Addr { return netip.AddrFrom4([4]byte(b[16:20])) } + +func (b IPv4) SetID(id uint16) { + binary.BigEndian.PutUint16(b[4:], id) +} + +func (b IPv4) SetTotalLength(n uint16) { + binary.BigEndian.PutUint16(b[2:], n) +} + +func (b IPv4) RecalcChecksum() { + binary.BigEndian.PutUint16(b[10:], 0) + csum := Checksum(b[:20], 0) + binary.BigEndian.PutUint16(b[10:], ^csum) +} func (b IPv4) Encode(totalLength uint16, id uint16, ttl uint8, protocol uint8, src, dst netip.Addr) { b[0] = (4 << 4) | 5 // IPv4, Header Length = 20 @@ -120,6 +137,12 @@ type IPv6 []byte func (b IPv6) PayloadLength() uint16 { return binary.BigEndian.Uint16(b[4:]) } func (b IPv6) TransportProtocol() uint8 { return b[6] } +func (b IPv6) Src() netip.Addr { return netip.AddrFrom16([16]byte(b[8:24])) } +func (b IPv6) Dst() netip.Addr { return netip.AddrFrom16([16]byte(b[24:40])) } + +func (b IPv6) SetPayloadLength(n uint16) { + binary.BigEndian.PutUint16(b[4:], n) +} func (b IPv6) Encode(payloadLength uint16, transportProtocol uint8, hopLimit uint8, src, dst netip.Addr) { binary.BigEndian.PutUint32(b[0:], 6<<28) // Version 6, Traffic Class 0, Flow Label 0 @@ -135,9 +158,27 @@ type TCP []byte func (b TCP) DataOffset() uint8 { return (b[12] >> 4) * 4 } func (b TCP) SequenceNumber() uint32 { return binary.BigEndian.Uint32(b[4:]) } func (b TCP) AckNumber() uint32 { return binary.BigEndian.Uint32(b[8:]) } +func (b TCP) Flags() uint8 { return b[13] } +func (b TCP) WindowSize() uint16 { return binary.BigEndian.Uint16(b[14:]) } func (b TCP) Options() []byte { return b[TCPMinimumSize:b.DataOffset()] } func (b TCP) SetChecksum(csum uint16) { binary.BigEndian.PutUint16(b[16:], csum) } +func (b TCP) SetSequenceNumber(seq uint32) { + binary.BigEndian.PutUint32(b[4:], seq) +} + +func (b TCP) SetAckNumber(ack uint32) { + binary.BigEndian.PutUint32(b[8:], ack) +} + +func (b TCP) SetFlags(flags uint8) { + b[13] = flags +} + +func (b TCP) SetWindowSize(wind uint16) { + binary.BigEndian.PutUint16(b[14:], wind) +} + func (b TCP) Encode(srcPort, dstPort uint16, seqNum, ackNum uint32, dataOffset uint8, flags uint8, windowSize uint16) { binary.BigEndian.PutUint16(b[0:], srcPort) binary.BigEndian.PutUint16(b[2:], dstPort) diff --git a/transport/internet/finalmask/rawpacket/windivert/filter.go b/transport/internet/finalmask/rawpacket/windivert/filter.go index d63adae2b630..6304de6c7f48 100644 --- a/transport/internet/finalmask/rawpacket/windivert/filter.go +++ b/transport/internet/finalmask/rawpacket/windivert/filter.go @@ -73,18 +73,27 @@ func reject() *Filter { // OutboundTCP returns a filter matching outbound TCP packets on the given // 5-tuple. Both addresses must share an address family (IPv4 or IPv6). func OutboundTCP(src, dst netip.AddrPort) (*Filter, error) { + return tcpFilter(src, dst, true) +} + +// BidirectionalTCP returns a filter matching TCP packets in either direction +// on the given 5-tuple. Both addresses must share an address family. +func BidirectionalTCP(src, dst netip.AddrPort) (*Filter, error) { + return tcpFilter(src, dst, false) +} + +func tcpFilter(src, dst netip.AddrPort, outboundOnly bool) (*Filter, error) { if !src.IsValid() || !dst.IsValid() { return nil, errors.New("windivert: filter: invalid address port") } if src.Addr().Is4() != dst.Addr().Is4() { return nil, errors.New("windivert: filter: mixed IPv4/IPv6") } - f := &Filter{ - flags: filterFlagOutbound, + f := &Filter{} + if outboundOnly { + f.flags = filterFlagOutbound + f.add(fieldOutbound, testEQ, argUint32(1)) } - // Insts chain as AND: each test's failure = REJECT, success = next inst. - // The final inst's success = ACCEPT. - f.add(fieldOutbound, testEQ, argUint32(1)) if src.Addr().Is4() { f.flags |= filterFlagIP f.add(fieldIP, testEQ, argUint32(1)) From 4bcb59aea8c3736a03365393a845710b221de524 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Thu, 4 Jun 2026 21:53:26 +0600 Subject: [PATCH 32/42] rawpacket: always use before-window seq for all methods --- transport/internet/finalmask/rawpacket/packet.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/transport/internet/finalmask/rawpacket/packet.go b/transport/internet/finalmask/rawpacket/packet.go index 0addd3239997..346a4e503645 100644 --- a/transport/internet/finalmask/rawpacket/packet.go +++ b/transport/internet/finalmask/rawpacket/packet.go @@ -98,10 +98,10 @@ func resolveSpoofPacketInfo(method Method, sendNext, receiveNext, timestamp uint if tsVal == 0 { tsVal = uint32(time.Now().UnixMilli()) } + packetInfo.seqNum = sendNext - uint32(len(payload)) tsOpt := buildTimestampOption(tsVal, 0) switch method { case MethodWrongSequence: - packetInfo.seqNum = sendNext - uint32(len(payload)) packetInfo.options = tsOpt case MethodWrongChecksum: packetInfo.corrupt = true @@ -264,13 +264,12 @@ func buildSpoofFromCapturedPacket(captured []byte, isV6 bool, synSeq uint32, fak // --- Modify TCP header --- tcpOut := TCP(out[ipHdrLen:]) - // Determine new seq/ack based on method - switch method { - case MethodWrongSequence: - // seq = synSeq + 1 - len(fake) places the spoofed packet before the window - newSeq := (synSeq + 1 - uint32(len(fakePayload))) & 0xffffffff - tcpOut.SetSequenceNumber(newSeq) - case MethodWrongAcknowledgment: + // Always use before-window seq so the server drops the fake packet. + // All methods apply this; method-specific corruption is applied on top. + newSeq := (synSeq + 1 - uint32(len(fakePayload))) & 0xffffffff + tcpOut.SetSequenceNumber(newSeq) + + if method == MethodWrongAcknowledgment { tcpOut.SetAckNumber(capturedAck - uint32(defaultWindowSize/2)) } From 1ed2e397a137192b0fa27b536589ac6e20035abe Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:03:30 +0600 Subject: [PATCH 33/42] rawpacket: use larger seq offset to ensure outside server window --- transport/internet/finalmask/rawpacket/packet.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/transport/internet/finalmask/rawpacket/packet.go b/transport/internet/finalmask/rawpacket/packet.go index 346a4e503645..f29be37f7138 100644 --- a/transport/internet/finalmask/rawpacket/packet.go +++ b/transport/internet/finalmask/rawpacket/packet.go @@ -98,7 +98,7 @@ func resolveSpoofPacketInfo(method Method, sendNext, receiveNext, timestamp uint if tsVal == 0 { tsVal = uint32(time.Now().UnixMilli()) } - packetInfo.seqNum = sendNext - uint32(len(payload)) + packetInfo.seqNum = sendNext - uint32(len(payload) + int(defaultWindowSize)) tsOpt := buildTimestampOption(tsVal, 0) switch method { case MethodWrongSequence: @@ -265,8 +265,9 @@ func buildSpoofFromCapturedPacket(captured []byte, isV6 bool, synSeq uint32, fak tcpOut := TCP(out[ipHdrLen:]) // Always use before-window seq so the server drops the fake packet. - // All methods apply this; method-specific corruption is applied on top. - newSeq := (synSeq + 1 - uint32(len(fakePayload))) & 0xffffffff + // The offset is large enough to place the fake clearly outside the + // server's receive window, preventing the server from accepting it. + newSeq := (synSeq + 1 - uint32(len(fakePayload)+int(defaultWindowSize))) & 0xffffffff tcpOut.SetSequenceNumber(newSeq) if method == MethodWrongAcknowledgment { From ecf602f7b92fec8dc99521c74afd25169452708a Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:18:36 +0600 Subject: [PATCH 34/42] rawpacket: add debug logging to Windows spoofer --- .../finalmask/rawpacket/raw_windows.go | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/transport/internet/finalmask/rawpacket/raw_windows.go b/transport/internet/finalmask/rawpacket/raw_windows.go index 9d1ef7ed5a45..8ea395a8bc28 100644 --- a/transport/internet/finalmask/rawpacket/raw_windows.go +++ b/transport/internet/finalmask/rawpacket/raw_windows.go @@ -5,6 +5,7 @@ package rawpacket import ( "encoding/binary" "errors" + "fmt" "net" "net/netip" "slices" @@ -12,6 +13,7 @@ import ( "sync/atomic" "time" + "github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/transport/internet/finalmask/rawpacket/windivert" "golang.org/x/sys/windows" ) @@ -50,10 +52,12 @@ func newRawSpoofer(conn net.Conn, method Method, ttl uint8) (rawSpoofer, error) if err != nil { return nil, err } + log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: opening WinDivert handle filter=%q src=%s dst=%s method=%s", filter, src, dst, method)}) divertH, err := windivert.Open(filter, windivert.LayerNetwork, 0, 0) if err != nil { return nil, err } + log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: WinDivert opened src=%s dst=%s method=%s", src, dst, method)}) s := &windowsSpoofer{ method: method, src: src, @@ -68,11 +72,14 @@ func newRawSpoofer(conn net.Conn, method Method, ttl uint8) (rawSpoofer, error) } func (s *windowsSpoofer) Inject(payload []byte) error { + log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: Inject called payload_len=%d", len(payload))}) select { case s.fakeReady <- payload: + log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: "rawpacket: injected payload onto fakeReady"}) return nil case <-s.done: if p := s.runErr.Load(); p != nil { + log.Record(&log.GeneralMessage{Severity: log.Severity_Error, Content: fmt.Sprintf("rawpacket: Inject failed spoofer closed err=%v", *p)}) return *p } return errors.New("rawpacket: spoofer closed before Inject") @@ -101,23 +108,31 @@ func (s *windowsSpoofer) recordErr(err error) { s.runErr.Store(&err) } func (s *windowsSpoofer) run() { defer close(s.done) defer s.divertH.Close() + defer log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: "rawpacket: run() exiting"}) + log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: "rawpacket: run() started"}) buf := make([]byte, windivert.MTUMax) + packetCount := 0 for { n, addr, err := s.divertH.Recv(buf) if err != nil { if errors.Is(err, windows.ERROR_OPERATION_ABORTED) || errors.Is(err, windows.ERROR_NO_DATA) { + log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: Recv returned expected err=%v", err)}) return } + log.Record(&log.GeneralMessage{Severity: log.Severity_Error, Content: fmt.Sprintf("rawpacket: Recv err=%v", err)}) s.recordErr(err) return } pkt := buf[:n] + packetCount++ seq, _, _, payloadLen, ok := parseTCPPacket(pkt, addr.IPv6()) if !ok { + log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: pkt#%d not TCP/passthrough len=%d", packetCount, n)}) _, sendErr := s.divertH.Send(pkt, &addr) if sendErr != nil { + log.Record(&log.GeneralMessage{Severity: log.Severity_Error, Content: fmt.Sprintf("rawpacket: Send err after parse fail=%v", sendErr)}) s.recordErr(sendErr) return } @@ -139,6 +154,7 @@ func (s *windowsSpoofer) run() { } else if srcIP == s.dst.Addr() && srcPort == s.dst.Port() { isOutbound = false } else { + log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: pkt#%d direction-unknown (neither side) passthrough", packetCount)}) _, _ = s.divertH.Send(pkt, &addr) continue } @@ -155,15 +171,17 @@ func (s *windowsSpoofer) run() { } else if srcIP == s.dst.Addr() && srcPort == s.dst.Port() { isOutbound = false } else { + log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: pkt#%d direction-unknown (neither side) passthrough", packetCount)}) _, _ = s.divertH.Send(pkt, &addr) continue } } if !isOutbound { - // Inbound (server→client) — pass through unchanged. + log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: pkt#%d inbound seq=%d payload=%d passthrough", packetCount, seq, payloadLen)}) _, err := s.divertH.Send(pkt, &addr) if err != nil { + log.Record(&log.GeneralMessage{Severity: log.Severity_Error, Content: fmt.Sprintf("rawpacket: Send err inbound=%v", err)}) s.recordErr(err) return } @@ -171,9 +189,10 @@ func (s *windowsSpoofer) run() { } if payloadLen == 0 { - // Outbound ACK, keepalive, FIN — pass through unchanged. + log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: pkt#%d outbound ack/ctrl passthrough", packetCount)}) _, err := s.divertH.Send(pkt, &addr) if err != nil { + log.Record(&log.GeneralMessage{Severity: log.Severity_Error, Content: fmt.Sprintf("rawpacket: Send err outbound-ctrl=%v", err)}) s.recordErr(err) return } @@ -181,13 +200,17 @@ func (s *windowsSpoofer) run() { } // Outbound data packet — the real ClientHello. + log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: pkt#%d outbound DATA seq=%d payload=%d", packetCount, seq, payloadLen)}) var fake []byte select { case fake = <-s.fakeReady: + log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: fakeReady consumed fake_len=%d", len(fake))}) default: // Inject() not yet called — pass through and keep observing. + log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: "rawpacket: fakeReady empty, pass-through until Inject called"}) _, err := s.divertH.Send(pkt, &addr) if err != nil { + log.Record(&log.GeneralMessage{Severity: log.Severity_Error, Content: fmt.Sprintf("rawpacket: Send err data-passthrough=%v", err)}) s.recordErr(err) return } @@ -199,8 +222,10 @@ func (s *windowsSpoofer) run() { // connection. synSeq is derived from the captured data seq (first // data after handshake always has seq = synSeq + 1). synSeq := seq - 1 + log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: building spoof seq=%d synSeq=%d fake=%d method=%s", seq, synSeq, len(fake), s.method)}) frame, err := buildSpoofFromCapturedPacket(pkt, addr.IPv6(), synSeq, fake, s.method) if err != nil { + log.Record(&log.GeneralMessage{Severity: log.Severity_Error, Content: fmt.Sprintf("rawpacket: buildSpoofFromCapturedPacket err=%v", err)}) s.recordErr(err) return } @@ -211,16 +236,21 @@ func (s *windowsSpoofer) run() { // intentional corruption (wrong-checksum method) and keep our bytes. fakeAddr.SetIPChecksum(true) fakeAddr.SetTCPChecksum(true) + log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: "rawpacket: sending fake frame"}) _, err = s.divertH.Send(frame, &fakeAddr) if err != nil { + log.Record(&log.GeneralMessage{Severity: log.Severity_Error, Content: fmt.Sprintf("rawpacket: Send fake err=%v", err)}) s.recordErr(err) return } + log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: "rawpacket: sending real frame"}) _, err = s.divertH.Send(pkt, &addr) if err != nil { + log.Record(&log.GeneralMessage{Severity: log.Severity_Error, Content: fmt.Sprintf("rawpacket: Send real err=%v", err)}) s.recordErr(err) return } + log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: "rawpacket: reorder complete"}) return // single-shot reorder complete } } From f25c14feb205a92ba4054964746cc2dcc78a4007 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:47:05 +0600 Subject: [PATCH 35/42] rawpacket: use highest WinDivert priority to beat other handles --- transport/internet/finalmask/rawpacket/raw_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transport/internet/finalmask/rawpacket/raw_windows.go b/transport/internet/finalmask/rawpacket/raw_windows.go index 8ea395a8bc28..31df91f266c9 100644 --- a/transport/internet/finalmask/rawpacket/raw_windows.go +++ b/transport/internet/finalmask/rawpacket/raw_windows.go @@ -53,7 +53,7 @@ func newRawSpoofer(conn net.Conn, method Method, ttl uint8) (rawSpoofer, error) return nil, err } log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: opening WinDivert handle filter=%q src=%s dst=%s method=%s", filter, src, dst, method)}) - divertH, err := windivert.Open(filter, windivert.LayerNetwork, 0, 0) + divertH, err := windivert.Open(filter, windivert.LayerNetwork, windivert.PriorityLowest, 0) if err != nil { return nil, err } From 882749873e5bd6c1a06cfb89998d4b8a81bd7b00 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Fri, 5 Jun 2026 20:49:11 +0600 Subject: [PATCH 36/42] rawpacket: rewrite as stateless IP spoof tunnel transport Replace TLS ClientHello spoofing with a stateless packet pipe transport. The new implementation builds spoofed IP packets directly (TCP SYN, UDP, ICMPv4 Echo, ICMPv6 Echo over IPv4) and supports configurable send/recv transports with round-robin source IP rotation. - SpoofSender/SpoofReceiver interfaces with TCP/UDP/ICMP/ICMPv6 impls - SpoofConn (net.Conn): Write sends spoofed packets, Read receives responses - Relay: receives spoofed packets, forwards to target, sends responses - Uses raw sockets (SOCK_RAW + IP_HDRINCL) on Unix, WinDivert on Windows - Platform build tags for darwin/freebsd/linux and Windows --- infra/conf/transport_internet.go | 57 +++- .../finalmask/rawpacket/client_hello.go | 51 --- .../internet/finalmask/rawpacket/config.go | 56 ++- .../internet/finalmask/rawpacket/config.pb.go | 180 ++++++++-- .../internet/finalmask/rawpacket/config.proto | 59 +++- .../finalmask/rawpacket/config_register.go | 74 ++++ .../internet/finalmask/rawpacket/conn.go | 286 ++++++++-------- .../internet/finalmask/rawpacket/conn_test.go | 151 --------- .../internet/finalmask/rawpacket/endpoints.go | 26 -- .../internet/finalmask/rawpacket/packet.go | 320 ------------------ .../internet/finalmask/rawpacket/platform.go | 5 + .../rawpacket/platform_unsupported.go | 5 + .../finalmask/rawpacket/raw_darwin.go | 203 ----------- .../finalmask/rawpacket/raw_freebsd.go | 178 ---------- .../internet/finalmask/rawpacket/raw_linux.go | 169 --------- .../internet/finalmask/rawpacket/raw_stub.go | 14 - .../internet/finalmask/rawpacket/raw_unix.go | 25 -- .../finalmask/rawpacket/raw_windows.go | 310 ----------------- .../finalmask/rawpacket/spoof_conn.go | 105 ++++++ .../finalmask/rawpacket/spoof_icmp_utils.go | 48 +++ .../internet/finalmask/rawpacket/spoof_ip.go | 69 ++++ .../finalmask/rawpacket/spoof_rawsend.go | 59 ++++ .../finalmask/rawpacket/spoof_rawsend_stub.go | 22 ++ .../rawpacket/spoof_rawsend_windows.go | 52 +++ .../finalmask/rawpacket/spoof_receiver.go | 193 +++++++++++ .../finalmask/rawpacket/spoof_relay.go | 239 +++++++++++++ .../finalmask/rawpacket/spoof_sender.go | 187 ++++++++++ .../finalmask/rawpacket/spoof_session.go | 99 ++++++ .../finalmask/rawpacket/spoof_source_ip.go | 33 ++ .../internet/finalmask/rawpacket/spoof_tcp.go | 224 ++++++++++++ .../finalmask/rawpacket/spoof_transport.go | 54 +++ 31 files changed, 1905 insertions(+), 1648 deletions(-) delete mode 100644 transport/internet/finalmask/rawpacket/client_hello.go create mode 100644 transport/internet/finalmask/rawpacket/config_register.go delete mode 100644 transport/internet/finalmask/rawpacket/conn_test.go delete mode 100644 transport/internet/finalmask/rawpacket/endpoints.go delete mode 100644 transport/internet/finalmask/rawpacket/packet.go create mode 100644 transport/internet/finalmask/rawpacket/platform.go create mode 100644 transport/internet/finalmask/rawpacket/platform_unsupported.go delete mode 100644 transport/internet/finalmask/rawpacket/raw_darwin.go delete mode 100644 transport/internet/finalmask/rawpacket/raw_freebsd.go delete mode 100644 transport/internet/finalmask/rawpacket/raw_linux.go delete mode 100644 transport/internet/finalmask/rawpacket/raw_stub.go delete mode 100644 transport/internet/finalmask/rawpacket/raw_unix.go delete mode 100644 transport/internet/finalmask/rawpacket/raw_windows.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_conn.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_icmp_utils.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_ip.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_rawsend.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_rawsend_stub.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_rawsend_windows.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_receiver.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_relay.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_sender.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_session.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_source_ip.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_tcp.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_transport.go diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 21704d28eef2..e2531df6688d 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -973,6 +973,8 @@ func (p TransportProtocol) Build() (string, error) { switch strings.ToLower(string(p)) { case "raw", "tcp": return "tcp", nil + case "rawpacket": + return "rawpacket", nil case "xhttp", "splithttp": return "splithttp", nil case "kcp", "mkcp": @@ -1420,20 +1422,44 @@ func (c *FragmentMask) Build() (proto.Message, error) { } type RawpacketMask struct { - Payload string `json:"payload"` - Sni string `json:"sni"` - Method string `json:"method"` - TTL int32 `json:"ttl"` - Count int32 `json:"count"` + Mode string `json:"mode"` + RemoteAddress string `json:"remoteAddress"` + RemotePort uint16 `json:"remotePort"` + RecvPort uint16 `json:"recvPort"` + SpoofIPs []string `json:"spoofIPs"` + Protocols []string `json:"protocols"` + MTU uint16 `json:"mtu"` + Target string `json:"target"` + TTL uint8 `json:"ttl"` + SendTransport string `json:"sendTransport"` + RecvTransport string `json:"recvTransport"` + RelayAddress string `json:"relayAddress"` + RelayPort uint16 `json:"relayPort"` + ClientIP string `json:"clientIP"` + ClientPort uint16 `json:"clientPort"` + PeerSpoofIP string `json:"peerSpoofIP"` + SpoofPort uint16 `json:"spoofPort"` } func (c *RawpacketMask) Build() (proto.Message, error) { config := &rawpacket.Config{ - Payload: c.Payload, - Sni: c.Sni, - Method: c.Method, - Ttl: uint32(c.TTL), - Count: c.Count, + Mode: c.Mode, + RemoteAddress: c.RemoteAddress, + RemotePort: uint32(c.RemotePort), + RecvPort: uint32(c.RecvPort), + SpoofIps: c.SpoofIPs, + Protocols: c.Protocols, + Mtu: uint32(c.MTU), + Target: c.Target, + Ttl: uint32(c.TTL), + SendTransport: c.SendTransport, + RecvTransport: c.RecvTransport, + RelayAddress: c.RelayAddress, + RelayPort: uint32(c.RelayPort), + ClientIp: c.ClientIP, + ClientPort: uint32(c.ClientPort), + PeerSpoofIp: c.PeerSpoofIP, + SpoofPort: uint32(c.SpoofPort), } return config, nil } @@ -2007,6 +2033,7 @@ type StreamConfig struct { Network *TransportProtocol `json:"network"` Security string `json:"security"` FinalMask *FinalMask `json:"finalmask"` + RawpacketSettings *RawpacketMask `json:"rawpacketSettings"` TLSSettings *TLSConfig `json:"tlsSettings"` REALITYSettings *REALITYConfig `json:"realitySettings"` RAWSettings *TCPConfig `json:"rawSettings"` @@ -2138,6 +2165,16 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) { Settings: serial.ToTypedMessage(hs), }) } + if c.RawpacketSettings != nil { + rs, err := c.RawpacketSettings.Build() + if err != nil { + return nil, errors.New("Failed to build rawpacket config.").Base(err) + } + config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ + ProtocolName: "rawpacket", + Settings: serial.ToTypedMessage(rs), + }) + } if c.HysteriaSettings != nil { hs, err := c.HysteriaSettings.Build() if err != nil { diff --git a/transport/internet/finalmask/rawpacket/client_hello.go b/transport/internet/finalmask/rawpacket/client_hello.go deleted file mode 100644 index 04eec64584da..000000000000 --- a/transport/internet/finalmask/rawpacket/client_hello.go +++ /dev/null @@ -1,51 +0,0 @@ -package rawpacket - -import ( - "bytes" - "context" - "crypto/tls" - "errors" - "io" - "net" - "time" -) - -// BuildFakeClientHello drives crypto/tls against a write-only in-memory conn -// to capture a generated ClientHello. CurvePreferences pins classical groups -// to suppress Go's default X25519MLKEM768 hybrid key share; without this the -// post-quantum public key alone (~1184 bytes) pushes the record past one MSS, -// and middleboxes do not reassemble fragmented ClientHellos. The handshake -// error is discarded because the stub conn's Read returns immediately. -func BuildFakeClientHello(sni string) ([]byte, error) { - if sni == "" { - return nil, errors.New("empty sni") - } - var buf bytes.Buffer - tlsConn := tls.Client(&writeOnlyConn{w: &buf}, &tls.Config{ - ServerName: sni, - // Order matches what browsers advertised before post-quantum. - CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP384}, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS13, - NextProtos: []string{"h2", "http/1.1"}, - InsecureSkipVerify: true, - }) - _ = tlsConn.HandshakeContext(context.Background()) - if buf.Len() == 0 { - return nil, errors.New("tls ClientHello not produced") - } - return buf.Bytes(), nil -} - -type writeOnlyConn struct { - w io.Writer -} - -func (c *writeOnlyConn) Read([]byte) (int, error) { return 0, io.EOF } -func (c *writeOnlyConn) Write(p []byte) (int, error) { return c.w.Write(p) } -func (c *writeOnlyConn) Close() error { return nil } -func (c *writeOnlyConn) LocalAddr() net.Addr { return nil } -func (c *writeOnlyConn) RemoteAddr() net.Addr { return nil } -func (c *writeOnlyConn) SetDeadline(time.Time) error { return nil } -func (c *writeOnlyConn) SetReadDeadline(time.Time) error { return nil } -func (c *writeOnlyConn) SetWriteDeadline(time.Time) error { return nil } diff --git a/transport/internet/finalmask/rawpacket/config.go b/transport/internet/finalmask/rawpacket/config.go index e4ee5d717d25..0db2e10d28ee 100644 --- a/transport/internet/finalmask/rawpacket/config.go +++ b/transport/internet/finalmask/rawpacket/config.go @@ -1,14 +1,56 @@ package rawpacket -import "net" +import ( + "fmt" + "net/netip" + "strings" +) -func (c *Config) TCP() {} +const ( + ProtocolTCP uint8 = 6 + ProtocolICMP uint8 = 1 + ProtocolICMPv6 uint8 = 58 + ProtocolUDP uint8 = 17 +) -func (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) { - return NewConnClient(c, raw) +func ParseProtocol(s string) (uint8, error) { + switch strings.ToLower(s) { + case "tcp": + return ProtocolTCP, nil + case "icmp": + return ProtocolICMP, nil + case "icmpv6": + return ProtocolICMPv6, nil + case "udp": + return ProtocolUDP, nil + default: + return 0, fmt.Errorf("rawpacket: unknown protocol: %s", s) + } } -func (c *Config) WrapConnServer(raw net.Conn) (net.Conn, error) { - // Raw packet injection is client-side only. - return raw, nil +func ParseIPs(ss []string) ([]netip.Addr, error) { + var out []netip.Addr + for _, s := range ss { + ip, err := netip.ParseAddr(s) + if err != nil { + return nil, fmt.Errorf("rawpacket: invalid spoof IP %q: %w", s, err) + } + out = append(out, ip.Unmap()) + } + return out, nil +} + +type RelayConfig struct { + ListenPort uint16 + ForwardAddr string + ForwardTransport string // "tcp" (Xray) or "udp" (reference, default) + ClientIP netip.Addr + ClientPort uint16 + SpoofIP netip.Addr // single fallback + SpoofIPs []string + SpoofPort uint16 + PeerSpoofIP netip.Addr + SendTransport string + RecvTransport string + icmpSuppressed bool } diff --git a/transport/internet/finalmask/rawpacket/config.pb.go b/transport/internet/finalmask/rawpacket/config.pb.go index 2ba1457af33c..cc7a895212ca 100644 --- a/transport/internet/finalmask/rawpacket/config.pb.go +++ b/transport/internet/finalmask/rawpacket/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v6.33.5 +// protoc v7.34.1 // source: transport/internet/finalmask/rawpacket/config.proto package rawpacket @@ -23,19 +23,40 @@ const ( type Config struct { state protoimpl.MessageState `protogen:"open.v1"` - // Base64-encoded fake payload bytes to inject before the real traffic. - // When empty, sni is used to auto-generate a TLS ClientHello. - Payload string `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - // Fake SNI hostname for auto-generated ClientHello when payload is empty. - Sni string `protobuf:"bytes,5,opt,name=sni,proto3" json:"sni,omitempty"` - // Corruption method to make the fake packet dropped by the server. - // Available: wrong-sequence, wrong-checksum, wrong-ack, wrong-md5, wrong-timestamp. - Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"` - // TTL of the fake packet. A low value (e.g. 3-5) ensures the packet - // is seen by middleboxes but does not reach the destination server. - Ttl uint32 `protobuf:"varint,3,opt,name=ttl,proto3" json:"ttl,omitempty"` - // How many Write() calls trigger injection. 0 or 1 = single-shot (default). - Count int32 `protobuf:"varint,4,opt,name=count,proto3" json:"count,omitempty"` + // Mode: "local" (client) or "remote" (server). + Mode string `protobuf:"bytes,6,opt,name=mode,proto3" json:"mode,omitempty"` + // Remote server address (local/client mode). + RemoteAddress string `protobuf:"bytes,7,opt,name=remote_address,json=remoteAddress,proto3" json:"remote_address,omitempty"` + // Remote server port (client mode). + RemotePort uint32 `protobuf:"varint,8,opt,name=remote_port,json=remotePort,proto3" json:"remote_port,omitempty"` + // Local port for receiving responses from the relay (client mode). + RecvPort uint32 `protobuf:"varint,9,opt,name=recv_port,json=recvPort,proto3" json:"recv_port,omitempty"` + // List of IP addresses to spoof as source. + SpoofIps []string `protobuf:"bytes,10,rep,name=spoof_ips,json=spoofIps,proto3" json:"spoof_ips,omitempty"` + // List of transport protocols to use: tcp, udp, icmp, icmpv6. + Protocols []string `protobuf:"bytes,11,rep,name=protocols,proto3" json:"protocols,omitempty"` + // MTU for packet fragmentation. + Mtu uint32 `protobuf:"varint,12,opt,name=mtu,proto3" json:"mtu,omitempty"` + // Target address for the relay (e.g. "127.0.0.1:443"). + Target string `protobuf:"bytes,13,opt,name=target,proto3" json:"target,omitempty"` + // TTL for outgoing packets. + Ttl uint32 `protobuf:"varint,14,opt,name=ttl,proto3" json:"ttl,omitempty"` + // Send transport: "tcp", "udp", "icmp", "icmpv6" (default: "tcp"). + SendTransport string `protobuf:"bytes,15,opt,name=send_transport,json=sendTransport,proto3" json:"send_transport,omitempty"` + // Receive transport: "tcp", "udp", "icmp", "icmpv6" (default: "udp"). + RecvTransport string `protobuf:"bytes,16,opt,name=recv_transport,json=recvTransport,proto3" json:"recv_transport,omitempty"` + // Relay address (for the relay sender, defaults to first spoof IP). + RelayAddress string `protobuf:"bytes,17,opt,name=relay_address,json=relayAddress,proto3" json:"relay_address,omitempty"` + // Port the relay listens on (remote/server mode). + RelayPort uint32 `protobuf:"varint,18,opt,name=relay_port,json=relayPort,proto3" json:"relay_port,omitempty"` + // Client IP address (remote/server mode) — where to send responses. + ClientIp string `protobuf:"bytes,19,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"` + // Client port (remote/server mode). + ClientPort uint32 `protobuf:"varint,20,opt,name=client_port,json=clientPort,proto3" json:"client_port,omitempty"` + // Expected peer spoof IP for receive filtering (nil = accept all). + PeerSpoofIp string `protobuf:"bytes,21,opt,name=peer_spoof_ip,json=peerSpoofIp,proto3" json:"peer_spoof_ip,omitempty"` + // Source port to use in spoofed packets (default: 443). + SpoofPort uint32 `protobuf:"varint,22,opt,name=spoof_port,json=spoofPort,proto3" json:"spoof_port,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -70,23 +91,58 @@ func (*Config) Descriptor() ([]byte, []int) { return file_transport_internet_finalmask_rawpacket_config_proto_rawDescGZIP(), []int{0} } -func (x *Config) GetPayload() string { +func (x *Config) GetMode() string { if x != nil { - return x.Payload + return x.Mode } return "" } -func (x *Config) GetSni() string { +func (x *Config) GetRemoteAddress() string { if x != nil { - return x.Sni + return x.RemoteAddress } return "" } -func (x *Config) GetMethod() string { +func (x *Config) GetRemotePort() uint32 { if x != nil { - return x.Method + return x.RemotePort + } + return 0 +} + +func (x *Config) GetRecvPort() uint32 { + if x != nil { + return x.RecvPort + } + return 0 +} + +func (x *Config) GetSpoofIps() []string { + if x != nil { + return x.SpoofIps + } + return nil +} + +func (x *Config) GetProtocols() []string { + if x != nil { + return x.Protocols + } + return nil +} + +func (x *Config) GetMtu() uint32 { + if x != nil { + return x.Mtu + } + return 0 +} + +func (x *Config) GetTarget() string { + if x != nil { + return x.Target } return "" } @@ -98,9 +154,58 @@ func (x *Config) GetTtl() uint32 { return 0 } -func (x *Config) GetCount() int32 { +func (x *Config) GetSendTransport() string { + if x != nil { + return x.SendTransport + } + return "" +} + +func (x *Config) GetRecvTransport() string { + if x != nil { + return x.RecvTransport + } + return "" +} + +func (x *Config) GetRelayAddress() string { + if x != nil { + return x.RelayAddress + } + return "" +} + +func (x *Config) GetRelayPort() uint32 { + if x != nil { + return x.RelayPort + } + return 0 +} + +func (x *Config) GetClientIp() string { + if x != nil { + return x.ClientIp + } + return "" +} + +func (x *Config) GetClientPort() uint32 { + if x != nil { + return x.ClientPort + } + return 0 +} + +func (x *Config) GetPeerSpoofIp() string { if x != nil { - return x.Count + return x.PeerSpoofIp + } + return "" +} + +func (x *Config) GetSpoofPort() uint32 { + if x != nil { + return x.SpoofPort } return 0 } @@ -109,13 +214,30 @@ var File_transport_internet_finalmask_rawpacket_config_proto protoreflect.FileDe const file_transport_internet_finalmask_rawpacket_config_proto_rawDesc = "" + "\n" + - "3transport/internet/finalmask/rawpacket/config.proto\x12+xray.transport.internet.finalmask.rawpacket\"t\n" + - "\x06Config\x12\x18\n" + - "\apayload\x18\x01 \x01(\tR\apayload\x12\x10\n" + - "\x03sni\x18\x05 \x01(\tR\x03sni\x12\x16\n" + - "\x06method\x18\x02 \x01(\tR\x06method\x12\x10\n" + - "\x03ttl\x18\x03 \x01(\rR\x03ttl\x12\x14\n" + - "\x05count\x18\x04 \x01(\x05R\x05countB\xa3\x01\n" + + "3transport/internet/finalmask/rawpacket/config.proto\x12+xray.transport.internet.finalmask.rawpacket\"\x8b\x04\n" + + "\x06Config\x12\x12\n" + + "\x04mode\x18\x06 \x01(\tR\x04mode\x12%\n" + + "\x0eremote_address\x18\a \x01(\tR\rremoteAddress\x12\x1f\n" + + "\vremote_port\x18\b \x01(\rR\n" + + "remotePort\x12\x1b\n" + + "\trecv_port\x18\t \x01(\rR\brecvPort\x12\x1b\n" + + "\tspoof_ips\x18\n" + + " \x03(\tR\bspoofIps\x12\x1c\n" + + "\tprotocols\x18\v \x03(\tR\tprotocols\x12\x10\n" + + "\x03mtu\x18\f \x01(\rR\x03mtu\x12\x16\n" + + "\x06target\x18\r \x01(\tR\x06target\x12\x10\n" + + "\x03ttl\x18\x0e \x01(\rR\x03ttl\x12%\n" + + "\x0esend_transport\x18\x0f \x01(\tR\rsendTransport\x12%\n" + + "\x0erecv_transport\x18\x10 \x01(\tR\rrecvTransport\x12#\n" + + "\rrelay_address\x18\x11 \x01(\tR\frelayAddress\x12\x1d\n" + + "\n" + + "relay_port\x18\x12 \x01(\rR\trelayPort\x12\x1b\n" + + "\tclient_ip\x18\x13 \x01(\tR\bclientIp\x12\x1f\n" + + "\vclient_port\x18\x14 \x01(\rR\n" + + "clientPort\x12\"\n" + + "\rpeer_spoof_ip\x18\x15 \x01(\tR\vpeerSpoofIp\x12\x1d\n" + + "\n" + + "spoof_port\x18\x16 \x01(\rR\tspoofPortB\xa3\x01\n" + "/com.xray.transport.internet.finalmask.rawpacketP\x01Z@github.com/xtls/xray-core/transport/internet/finalmask/rawpacket\xaa\x02+Xray.Transport.Internet.Finalmask.Rawpacketb\x06proto3" var ( diff --git a/transport/internet/finalmask/rawpacket/config.proto b/transport/internet/finalmask/rawpacket/config.proto index 8a25852468b9..e84fc770207f 100644 --- a/transport/internet/finalmask/rawpacket/config.proto +++ b/transport/internet/finalmask/rawpacket/config.proto @@ -7,21 +7,54 @@ option java_package = "com.xray.transport.internet.finalmask.rawpacket"; option java_multiple_files = true; message Config { - // Base64-encoded fake payload bytes to inject before the real traffic. - // When empty, sni is used to auto-generate a TLS ClientHello. - string payload = 1; + // Mode: "local" (client) or "remote" (server). + string mode = 6; - // Fake SNI hostname for auto-generated ClientHello when payload is empty. - string sni = 5; + // Remote server address (local/client mode). + string remote_address = 7; - // Corruption method to make the fake packet dropped by the server. - // Available: wrong-sequence, wrong-checksum, wrong-ack, wrong-md5, wrong-timestamp. - string method = 2; + // Remote server port (client mode). + uint32 remote_port = 8; - // TTL of the fake packet. A low value (e.g. 3-5) ensures the packet - // is seen by middleboxes but does not reach the destination server. - uint32 ttl = 3; + // Local port for receiving responses from the relay (client mode). + uint32 recv_port = 9; - // How many Write() calls trigger injection. 0 or 1 = single-shot (default). - int32 count = 4; + // List of IP addresses to spoof as source. + repeated string spoof_ips = 10; + + // List of transport protocols to use: tcp, udp, icmp, icmpv6. + repeated string protocols = 11; + + // MTU for packet fragmentation. + uint32 mtu = 12; + + // Target address for the relay (e.g. "127.0.0.1:443"). + string target = 13; + + // TTL for outgoing packets. + uint32 ttl = 14; + + // Send transport: "tcp", "udp", "icmp", "icmpv6" (default: "tcp"). + string send_transport = 15; + + // Receive transport: "tcp", "udp", "icmp", "icmpv6" (default: "udp"). + string recv_transport = 16; + + // Relay address (for the relay sender, defaults to first spoof IP). + string relay_address = 17; + + // Port the relay listens on (remote/server mode). + uint32 relay_port = 18; + + // Client IP address (remote/server mode) — where to send responses. + string client_ip = 19; + + // Client port (remote/server mode). + uint32 client_port = 20; + + // Expected peer spoof IP for receive filtering (nil = accept all). + string peer_spoof_ip = 21; + + // Source port to use in spoofed packets (default: 443). + uint32 spoof_port = 22; } diff --git a/transport/internet/finalmask/rawpacket/config_register.go b/transport/internet/finalmask/rawpacket/config_register.go new file mode 100644 index 000000000000..29f830504da1 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/config_register.go @@ -0,0 +1,74 @@ +package rawpacket + +import ( + "context" + stdnet "net" + + "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/internet/stat" +) + +const ProtocolName = "rawpacket" + +type rawpacketListener struct { + addr stdnet.Addr + done chan struct{} + relay *Relay +} + +func (l *rawpacketListener) Close() error { + if l.relay != nil { + l.relay.Close() + } + close(l.done) + return nil +} + +func (l *rawpacketListener) Addr() stdnet.Addr { + return l.addr +} + +func init() { + common.Must(internet.RegisterProtocolConfigCreator(ProtocolName, func() interface{} { + return new(Config) + })) + common.Must(internet.RegisterTransportDialer(ProtocolName, Dial)) + common.Must(internet.RegisterTransportListener(ProtocolName, listenRawpacket)) +} + +func listenRawpacket(ctx context.Context, address net.Address, port net.Port, settings *internet.MemoryStreamConfig, handler internet.ConnHandler) (internet.Listener, error) { + config := settings.ProtocolSettings.(*Config) + if config.Mode == "remote" { + cfg, err := config.buildRelayConfig() + if err == nil { + if cfg.ListenPort == 0 { + cfg.ListenPort = uint16(port) + } + r, err := NewRelay(cfg) + if err == nil { + go r.Run() + return &rawpacketListener{ + addr: &stdnet.TCPAddr{IP: stdnet.IP{0, 0, 0, 0}, Port: int(port)}, + done: make(chan struct{}), + relay: r, + }, nil + } + } + } + return &rawpacketListener{ + addr: &stdnet.TCPAddr{IP: stdnet.IP{0, 0, 0, 0}, Port: int(port)}, + done: make(chan struct{}), + }, nil +} + +func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) { + config := streamSettings.ProtocolSettings.(*Config) + + conn, err := config.WrapConnClient(nil) + if err != nil { + return nil, err + } + return stat.Connection(conn), nil +} diff --git a/transport/internet/finalmask/rawpacket/conn.go b/transport/internet/finalmask/rawpacket/conn.go index 4c762a3afc2a..a2fadff3a10a 100644 --- a/transport/internet/finalmask/rawpacket/conn.go +++ b/transport/internet/finalmask/rawpacket/conn.go @@ -1,187 +1,189 @@ package rawpacket import ( - "encoding/base64" - "errors" "fmt" "net" - "runtime" - "syscall" + "net/netip" + "strconv" ) -type Method int +func (c *Config) TCP() {} -const ( - MethodWrongSequence Method = iota - MethodWrongChecksum - MethodWrongAcknowledgment - MethodWrongMD5Sig - MethodWrongTimestamp -) +func (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) { + if !PlatformSupported { + return nil, fmt.Errorf("rawpacket is not supported on this platform") + } -const ( - MethodNameWrongSequence = "wrong-sequence" - MethodNameWrongChecksum = "wrong-checksum" - MethodNameWrongAcknowledgment = "wrong-ack" - MethodNameWrongMD5Sig = "wrong-md5" - MethodNameWrongTimestamp = "wrong-timestamp" -) + mode := c.Mode + if mode == "" { + mode = "local" + } -func ParseMethod(s string) (Method, error) { - switch s { - case "", MethodNameWrongSequence: - return MethodWrongSequence, nil - case MethodNameWrongChecksum: - return MethodWrongChecksum, nil - case MethodNameWrongAcknowledgment: - return MethodWrongAcknowledgment, nil - case MethodNameWrongMD5Sig: - return MethodWrongMD5Sig, nil - case MethodNameWrongTimestamp: - return MethodWrongTimestamp, nil + switch mode { + case "local": + return c.dialLocal() + case "remote": + return nil, fmt.Errorf("rawpacket: remote mode must be used as server") default: - return 0, fmt.Errorf("rawpacket: unknown method: %s", s) + return nil, fmt.Errorf("rawpacket: unknown mode: %s", mode) } } -func (m Method) String() string { - switch m { - case MethodWrongSequence: - return MethodNameWrongSequence - case MethodWrongChecksum: - return MethodNameWrongChecksum - case MethodWrongAcknowledgment: - return MethodNameWrongAcknowledgment - case MethodWrongMD5Sig: - return MethodNameWrongMD5Sig - case MethodWrongTimestamp: - return MethodNameWrongTimestamp - default: - return "unknown" +func (c *Config) WrapConnServer(raw net.Conn) (net.Conn, error) { + if c.Mode == "remote" { + go c.startRelay() + return raw, nil } + return raw, nil } -type rawSpoofer interface { - Inject(payload []byte) error - Close() error +func toNetIP(s string) net.IP { + if s == "" { + return nil + } + return net.ParseIP(s) } -type Conn struct { - net.Conn - spoofer rawSpoofer - fakePayload []byte - injectionCount int - maxInjections int +func toNetipAddr(s string) netip.Addr { + if s == "" { + return netip.Addr{} + } + ip, err := netip.ParseAddr(s) + if err != nil { + return netip.Addr{} + } + return ip.Unmap() } -func NewConnClient(cfg *Config, conn net.Conn) (net.Conn, error) { - if cfg.Payload == "" && cfg.Sni == "" { - return conn, nil +func (c *Config) dialLocal() (net.Conn, error) { + remoteIP := c.RemoteAddress + if remoteIP == "" { + return nil, fmt.Errorf("rawpacket: remoteAddress required") } - if !PlatformSupported { - return nil, errors.New("rawpacket is not supported on this platform") - } - var payload []byte - var err error - if cfg.Payload != "" { - payload, err = base64.StdEncoding.DecodeString(cfg.Payload) - if err != nil { - return nil, fmt.Errorf("rawpacket: invalid base64 payload: %w", err) - } - if len(payload) == 0 { - return nil, errors.New("rawpacket: payload is empty") - } - } else { - payload, err = BuildFakeClientHello(cfg.Sni) - if err != nil { - return nil, fmt.Errorf("rawpacket: build fake ClientHello: %w", err) - } + remotePort := uint16(c.RemotePort) + if remotePort == 0 { + remotePort = 443 } - method, err := ParseMethod(cfg.Method) - if err != nil { - return nil, err + + recvPort := uint16(c.RecvPort) + if recvPort == 0 { + recvPort = 60000 } - ttl := uint8(cfg.Ttl) + + spoofIPs := c.SpoofIps + if len(spoofIPs) == 0 { + return nil, fmt.Errorf("rawpacket: at least one spoof IP required") + } + + ttl := uint8(c.Ttl) if ttl == 0 { - ttl = 3 + ttl = 64 } - spoofer, err := newRawSpoofer(conn, method, ttl) + + sendProto := c.SendTransport + if sendProto == "" { + sendProto = "tcp" + } + + recvProto := c.RecvTransport + if recvProto == "" { + recvProto = "udp" + } + + relayAddrPort, err := netip.ParseAddrPort(net.JoinHostPort(remoteIP, strconv.Itoa(int(remotePort)))) if err != nil { - return nil, wrapPermissionError(err) + return nil, fmt.Errorf("rawpacket: parse remote address: %w", err) } - maxInjections := int(cfg.Count) - if maxInjections <= 0 { - maxInjections = 1 + + ips, err := ParseIPs(spoofIPs) + if err != nil { + return nil, err } - return &Conn{ - Conn: conn, - spoofer: spoofer, - fakePayload: payload, - maxInjections: maxInjections, - }, nil -} -func NewConnServer(_ *Config, conn net.Conn) (net.Conn, error) { - return conn, nil + return DialSpoof(relayAddrPort, ips, recvPort, ttl, sendProto, recvProto, toNetipAddr(c.PeerSpoofIp)) } -func (c *Conn) Write(b []byte) (n int, err error) { - if c.injectionCount >= c.maxInjections { - return c.Conn.Write(b) - } - closeSpoofer := false - defer func() { - if closeSpoofer { - if closeErr := c.spoofer.Close(); closeErr != nil && err == nil { - err = fmt.Errorf("rawpacket: close spoofer: %w", closeErr) - } - } - }() - err = c.spoofer.Inject(c.fakePayload) +func (c *Config) startRelay() { + cfg, err := c.buildRelayConfig() if err != nil { - return 0, fmt.Errorf("rawpacket: inject: %w", err) + return } - c.injectionCount++ - if c.injectionCount >= c.maxInjections { - closeSpoofer = true + r, err := NewRelay(cfg) + if err != nil { + return } - n, err = c.Conn.Write(b) - return n, err + defer r.Close() + r.Run() } -func (c *Conn) Close() error { - spooferErr := c.spoofer.Close() - connErr := c.Conn.Close() - if spooferErr != nil { - return spooferErr +func (c *Config) buildRelayConfig() (*RelayConfig, error) { + spoofIPs := c.SpoofIps + if len(spoofIPs) == 0 { + return nil, fmt.Errorf("rawpacket: at least one spoof IP required for relay") } - return connErr -} -func (c *Conn) TcpMaskConn() {} + target := c.Target + if target == "" { + target = "127.0.0.1:443" + } -func (c *Conn) RawConn() net.Conn { - return c.Conn -} + relayPort := uint16(c.RelayPort) + if relayPort == 0 { + relayPort = 443 + } -func (c *Conn) Splice() bool { - return c.injectionCount >= c.maxInjections + sendProto := c.SendTransport + if sendProto == "" { + sendProto = "udp" + } + + recvProto := c.RecvTransport + if recvProto == "" { + recvProto = "tcp" + } + + clientIP := c.ClientIp + clientPort := uint16(c.ClientPort) + + if clientIP == "" { + clientIP = firstStr(spoofIPs) + } + if clientPort == 0 { + clientPort = uint16(c.RecvPort) + if clientPort == 0 { + clientPort = 60000 + } + } + + spoofPort := uint16(c.SpoofPort) + if spoofPort == 0 { + spoofPort = 443 + } + + fwdTransport := c.SendTransport + if fwdTransport == "udp" { + fwdTransport = "udp" + } else { + fwdTransport = "tcp" + } + + return &RelayConfig{ + ListenPort: relayPort, + ForwardAddr: target, + ForwardTransport: fwdTransport, + ClientIP: toNetipAddr(clientIP), + ClientPort: clientPort, + SpoofIPs: spoofIPs, + SpoofPort: spoofPort, + PeerSpoofIP: toNetipAddr(c.PeerSpoofIp), + SendTransport: sendProto, + RecvTransport: recvProto, + }, nil } -func wrapPermissionError(err error) error { - if !errors.Is(err, syscall.EPERM) && !errors.Is(err, syscall.EACCES) { - return err - } - switch runtime.GOOS { - case "linux": - return fmt.Errorf("%w\n Hint: run as root, or grant capabilities:\n sudo setcap cap_net_raw,cap_net_admin+ep /path/to/xray", err) - case "darwin": - return fmt.Errorf("%w\n Hint: rawpacket requires root on macOS. Run with: sudo ./xray", err) - case "freebsd": - return fmt.Errorf("%w\n Hint: rawpacket requires root on FreeBSD. Run with: sudo ./xray", err) - case "windows": - return fmt.Errorf("%w\n Hint: rawpacket requires Administrator on Windows (WinDivert driver)", err) - default: - return err +func firstStr(ss []string) string { + if len(ss) > 0 { + return ss[0] } + return "" } diff --git a/transport/internet/finalmask/rawpacket/conn_test.go b/transport/internet/finalmask/rawpacket/conn_test.go deleted file mode 100644 index 64166f5440dc..000000000000 --- a/transport/internet/finalmask/rawpacket/conn_test.go +++ /dev/null @@ -1,151 +0,0 @@ -package rawpacket - -import ( - "errors" - "io" - "net" - "sync" - "testing" - "time" -) - -type mockSpoofer struct { - mu sync.Mutex - calls []string - injectErr error - closeErr error -} - -func (m *mockSpoofer) Inject([]byte) error { - m.mu.Lock() - defer m.mu.Unlock() - m.calls = append(m.calls, "inject") - return m.injectErr -} - -func (m *mockSpoofer) Close() error { - m.mu.Lock() - defer m.mu.Unlock() - m.calls = append(m.calls, "close") - return m.closeErr -} - -func (m *mockSpoofer) callOrder() []string { - m.mu.Lock() - defer m.mu.Unlock() - out := make([]string, len(m.calls)) - copy(out, m.calls) - return out -} - -type recordingConn struct { - mu sync.Mutex - writes [][]byte -} - -func (c *recordingConn) Read([]byte) (int, error) { return 0, io.EOF } -func (c *recordingConn) Write(b []byte) (int, error) { - c.mu.Lock() - defer c.mu.Unlock() - dup := make([]byte, len(b)) - copy(dup, b) - c.writes = append(c.writes, dup) - return len(b), nil -} -func (c *recordingConn) Close() error { return nil } -func (c *recordingConn) LocalAddr() net.Addr { return nil } -func (c *recordingConn) RemoteAddr() net.Addr { return nil } -func (c *recordingConn) SetDeadline(time.Time) error { return nil } -func (c *recordingConn) SetReadDeadline(time.Time) error { return nil } -func (c *recordingConn) SetWriteDeadline(time.Time) error { return nil } - -func (c *recordingConn) wrotePayloads() [][]byte { - c.mu.Lock() - defer c.mu.Unlock() - out := make([][]byte, len(c.writes)) - for i, w := range c.writes { - dup := make([]byte, len(w)) - copy(dup, w) - out[i] = dup - } - return out -} - -func TestWriteCallOrder(t *testing.T) { - spoofer := &mockSpoofer{} - client, server := net.Pipe() - defer client.Close() - defer server.Close() - - go func() { - _, _ = io.ReadAll(server) - }() - - conn := &Conn{ - Conn: client, - spoofer: spoofer, - fakePayload: []byte("fake"), - maxInjections: 1, - } - - if _, err := conn.Write([]byte("real")); err != nil { - t.Fatalf("Write: %v", err) - } - - order := spoofer.callOrder() - if len(order) != 2 || order[0] != "inject" || order[1] != "close" { - t.Fatalf("call order = %v, want [inject close]", order) - } -} - -func TestWriteCloseAfterUnderlyingWrite(t *testing.T) { - spoofer := &mockSpoofer{} - rec := &recordingConn{} - conn := &Conn{ - Conn: rec, - spoofer: spoofer, - fakePayload: []byte("fake"), - maxInjections: 1, - } - - if _, err := conn.Write([]byte("real")); err != nil { - t.Fatalf("Write: %v", err) - } - if len(rec.wrotePayloads()) != 1 { - t.Fatalf("expected one underlying write, got %d", len(rec.wrotePayloads())) - } - if order := spoofer.callOrder(); len(order) != 2 || order[1] != "close" { - t.Fatalf("close not last: %v", order) - } -} - -func TestBuildFakeClientHello(t *testing.T) { - hello, err := BuildFakeClientHello("hcaptcha.com") - if err != nil { - t.Fatalf("buildFakeClientHello: %v", err) - } - if len(hello) == 0 { - t.Fatal("empty ClientHello") - } - if hello[0] != 0x16 { - t.Fatalf("expected TLS handshake record (0x16), got 0x%x", hello[0]) - } -} - -func TestBuildFakeClientHelloEmptySNI(t *testing.T) { - _, err := BuildFakeClientHello("") - if err == nil { - t.Fatal("expected error for empty sni") - } -} - -type discardConn struct{} - -func (discardConn) Read([]byte) (int, error) { return 0, io.EOF } -func (discardConn) Write([]byte) (int, error) { return 0, errors.New("unexpected write") } -func (discardConn) Close() error { return nil } -func (discardConn) LocalAddr() net.Addr { return nil } -func (discardConn) RemoteAddr() net.Addr { return nil } -func (discardConn) SetDeadline(time.Time) error { return nil } -func (discardConn) SetReadDeadline(time.Time) error { return nil } -func (discardConn) SetWriteDeadline(time.Time) error { return nil } diff --git a/transport/internet/finalmask/rawpacket/endpoints.go b/transport/internet/finalmask/rawpacket/endpoints.go deleted file mode 100644 index cc37c3223c9f..000000000000 --- a/transport/internet/finalmask/rawpacket/endpoints.go +++ /dev/null @@ -1,26 +0,0 @@ -package rawpacket - -import ( - "errors" - "net" - "net/netip" -) - -// The returned addresses are v4-unmapped and share the same family. -func tcpEndpoints(conn net.Conn) (*net.TCPConn, netip.AddrPort, netip.AddrPort, error) { - tcpConn, isTCP := conn.(*net.TCPConn) - if !isTCP { - return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("rawpacket: underlying conn is not *net.TCPConn") - } - local := tcpConn.LocalAddr().(*net.TCPAddr).AddrPort() - remote := tcpConn.RemoteAddr().(*net.TCPAddr).AddrPort() - if !local.IsValid() || !remote.IsValid() { - return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("rawpacket: invalid conn address") - } - local = netip.AddrPortFrom(local.Addr().Unmap(), local.Port()) - remote = netip.AddrPortFrom(remote.Addr().Unmap(), remote.Port()) - if local.Addr().Is4() != remote.Addr().Is4() { - return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("rawpacket: local/remote address family mismatch") - } - return tcpConn, local, remote, nil -} diff --git a/transport/internet/finalmask/rawpacket/packet.go b/transport/internet/finalmask/rawpacket/packet.go deleted file mode 100644 index f29be37f7138..000000000000 --- a/transport/internet/finalmask/rawpacket/packet.go +++ /dev/null @@ -1,320 +0,0 @@ -package rawpacket - -import ( - "encoding/binary" - "errors" - "fmt" - "net/netip" - "time" -) - -const ( - defaultWindowSize uint16 = 0xFFFF - tcpHeaderLen = TCPMinimumSize - - tcpOptionMD5Signature = 19 - tcpOptionMD5SignatureLength = 18 - tcpTimestampBackdate = 3600000 -) - -type spoofPacketInfo struct { - seqNum uint32 - ackNum uint32 - corrupt bool - options []byte -} - -func buildTCPSegment( - src netip.AddrPort, - dst netip.AddrPort, - packetInfo spoofPacketInfo, - payload []byte, - ttl uint8, -) []byte { - if src.Addr().Is4() != dst.Addr().Is4() { - panic("rawpacket: mixed IPv4/IPv6 address family") - } - var ( - frame []byte - ipHeaderLen int - ) - ipPayloadLen := tcpHeaderLen + len(packetInfo.options) + len(payload) - if src.Addr().Is4() { - ipHeaderLen = IPv4MinimumSize - frame = make([]byte, ipHeaderLen+ipPayloadLen) - ip := IPv4(frame[:ipHeaderLen]) - ip.Encode(uint16(len(frame)), 0, ttl, TCPProtocolNumber, src.Addr(), dst.Addr()) - } else { - ipHeaderLen = IPv6MinimumSize - frame = make([]byte, ipHeaderLen+ipPayloadLen) - ip := IPv6(frame[:ipHeaderLen]) - ip.Encode(uint16(ipPayloadLen), TCPProtocolNumber, ttl, src.Addr(), dst.Addr()) - } - encodeTCP(frame, ipHeaderLen, src, dst, packetInfo, payload) - return frame -} - -func encodeTCP(frame []byte, ipHeaderLen int, src, dst netip.AddrPort, packetInfo spoofPacketInfo, payload []byte) { - tcp := TCP(frame[ipHeaderLen:]) - copy(frame[ipHeaderLen+tcpHeaderLen:], packetInfo.options) - optionsLen := len(packetInfo.options) - copy(frame[ipHeaderLen+tcpHeaderLen+optionsLen:], payload) - tcp.Encode(src.Port(), dst.Port(), packetInfo.seqNum, packetInfo.ackNum, uint8(tcpHeaderLen+optionsLen), TCPFlagAck|TCPFlagPsh, defaultWindowSize) - applyTCPChecksum(tcp, src.Addr(), dst.Addr(), payload, packetInfo.corrupt) -} - -func buildSpoofFrame(method Method, src, dst netip.AddrPort, sendNext, receiveNext, timestamp uint32, tcpOptions, payload []byte, ttl uint8) ([]byte, error) { - packetInfo, err := resolveSpoofPacketInfo(method, sendNext, receiveNext, timestamp, tcpOptions, payload) - if err != nil { - return nil, err - } - return buildTCPSegment(src, dst, packetInfo, payload, ttl), nil -} - -// buildSpoofTCPSegment returns a TCP segment without an IP header, for -// platforms where the kernel synthesises the IP header (darwin IPv6). -func buildSpoofTCPSegment(method Method, src, dst netip.AddrPort, sendNext, receiveNext, timestamp uint32, payload []byte) ([]byte, error) { - packetInfo, err := resolveSpoofPacketInfo(method, sendNext, receiveNext, timestamp, nil, payload) - if err != nil { - return nil, err - } - segment := make([]byte, tcpHeaderLen+len(packetInfo.options)+len(payload)) - encodeTCP(segment, 0, src, dst, packetInfo, payload) - return segment, nil -} - -func buildTimestampOption(tsVal, tsEcr uint32) []byte { - b := make([]byte, TCPOptionTSLength+2) - EncodeTSOption(tsVal, tsEcr, b) - return b -} - -func resolveSpoofPacketInfo(method Method, sendNext, receiveNext, timestamp uint32, tcpOptions, payload []byte) (spoofPacketInfo, error) { - packetInfo := spoofPacketInfo{seqNum: sendNext, ackNum: receiveNext} - // Always include a valid TCP timestamp option in all methods. - // Modern TCP connections always carry timestamps. A segment without - // them is immediately flagged as anomalous by DPI equipment. - tsVal := timestamp - if tsVal == 0 { - tsVal = uint32(time.Now().UnixMilli()) - } - packetInfo.seqNum = sendNext - uint32(len(payload) + int(defaultWindowSize)) - tsOpt := buildTimestampOption(tsVal, 0) - switch method { - case MethodWrongSequence: - packetInfo.options = tsOpt - case MethodWrongChecksum: - packetInfo.corrupt = true - packetInfo.options = tsOpt - case MethodWrongAcknowledgment: - packetInfo.ackNum = receiveNext - uint32(defaultWindowSize/2) - packetInfo.options = tsOpt - case MethodWrongMD5Sig: - md5Opt := buildMD5SignatureOptions() - combined := make([]byte, 0, len(tsOpt)+2+len(md5Opt)) - combined = append(combined, tsOpt...) - combined = append(combined, TCPOptionNOP, TCPOptionNOP) - combined = append(combined, md5Opt...) - packetInfo.options = combined - case MethodWrongTimestamp: - backdated := tsVal - if backdated > tcpTimestampBackdate { - backdated -= tcpTimestampBackdate - } else { - backdated = 0 - } - if rewriteTCPOptionTimestamp(tcpOptions, backdated) { - packetInfo.options = tcpOptions - } else { - packetInfo.options = buildTimestampOption(backdated, 0) - } - default: - return packetInfo, fmt.Errorf("rawpacket: unknown method %v", method) - } - return packetInfo, nil -} - -func buildMD5SignatureOptions() []byte { - options := make([]byte, tcpOptionMD5SignatureLength+2) - options[0] = tcpOptionMD5Signature - options[1] = tcpOptionMD5SignatureLength - return options -} - -// rewriteTCPOptionTimestamp finds the TS option in tcpOptions and writes -// timestamp into its TSVal field in place. The caller must own tcpOptions -// (parseTCPPacket already returns a private copy on Windows). -func rewriteTCPOptionTimestamp(tcpOptions []byte, timestamp uint32) bool { - for i := 0; i < len(tcpOptions); { - switch tcpOptions[i] { - case TCPOptionEOL: - return false - case TCPOptionNOP: - i++ - continue - } - if i+1 >= len(tcpOptions) { - return false - } - optionLen := int(tcpOptions[i+1]) - if optionLen < 2 || i+optionLen > len(tcpOptions) { - return false - } - if tcpOptions[i] == TCPOptionTS && optionLen == TCPOptionTSLength { - binary.BigEndian.PutUint32(tcpOptions[i+2:], timestamp) - return true - } - i += optionLen - } - return false -} - -// buildSpoofFromCapturedPacket takes a captured IP+TCP packet and builds a -// spoofed version that preserves the real connection's TCP options, IP ID -// sequencing, and window size. -func buildSpoofFromCapturedPacket(captured []byte, isV6 bool, synSeq uint32, fakePayload []byte, method Method) ([]byte, error) { - var ipHdrLen int - var srcAddr, dstAddr netip.Addr - - if isV6 { - if len(captured) < IPv6MinimumSize+TCPMinimumSize { - return nil, errors.New("rawpacket: captured packet too short for IPv6") - } - ip := IPv6(captured) - if ip.TransportProtocol() != TCPProtocolNumber { - return nil, errors.New("rawpacket: captured packet is not TCP") - } - ipHdrLen = IPv6MinimumSize - srcAddr = ip.Src() - dstAddr = ip.Dst() - } else { - if len(captured) < IPv4MinimumSize+TCPMinimumSize { - return nil, errors.New("rawpacket: captured packet too short for IPv4") - } - ip := IPv4(captured) - if ip.Protocol() != TCPProtocolNumber { - return nil, errors.New("rawpacket: captured packet is not TCP") - } - ipHdrLen = int(ip.HeaderLength()) - if ipHdrLen < IPv4MinimumSize || ipHdrLen > len(captured) { - return nil, fmt.Errorf("rawpacket: invalid IPv4 header length %d", ipHdrLen) - } - srcAddr = ip.Src() - dstAddr = ip.Dst() - } - - if ipHdrLen+TCPMinimumSize > len(captured) { - return nil, errors.New("rawpacket: captured packet truncated") - } - - tcp := TCP(captured[ipHdrLen:]) - tcpHdrLen := int(tcp.DataOffset()) - if tcpHdrLen < TCPMinimumSize || ipHdrLen+tcpHdrLen > len(captured) { - return nil, fmt.Errorf("rawpacket: invalid TCP header length %d in captured packet", tcpHdrLen) - } - - capturedAck := tcp.AckNumber() - capturedFlags := tcp.Flags() - - // Preserve captured TCP options (timestamp, SACK, window scale, etc.) - // We work on a copy, not the original. - tcpOpts := make([]byte, tcpHdrLen-TCPMinimumSize) - copy(tcpOpts, tcp.Options()) - - // Determine captured total length - var totalLen int - if isV6 { - totalLen = ipHdrLen + int(IPv6(captured).PayloadLength()) - } else { - totalLen = int(IPv4(captured).TotalLength()) - } - if totalLen > len(captured) { - totalLen = len(captured) - } - originalPayloadLen := totalLen - ipHdrLen - tcpHdrLen - if originalPayloadLen < 0 { - originalPayloadLen = 0 - } - - // Allocate output: IP hdr + TCP hdr (with copied options) + fake payload - newTotalLen := ipHdrLen + tcpHdrLen + len(fakePayload) - out := make([]byte, newTotalLen) - - // Copy IP header - copy(out[:ipHdrLen], captured[:ipHdrLen]) - - // Copy TCP header + options (original payload is NOT copied) - copy(out[ipHdrLen:ipHdrLen+tcpHdrLen], captured[ipHdrLen:ipHdrLen+tcpHdrLen]) - - // Write fake payload - copy(out[ipHdrLen+tcpHdrLen:], fakePayload) - - // --- Modify IP header --- - if isV6 { - ip6 := IPv6(out) - ip6.SetPayloadLength(uint16(tcpHdrLen + len(fakePayload))) - } else { - ip4 := IPv4(out) - ip4.SetTotalLength(uint16(newTotalLen)) - // Increment IP ID by 1 to maintain sequential appearance - ip4.SetID(ip4.ID() + 1) - ip4.RecalcChecksum() - } - - // --- Modify TCP header --- - tcpOut := TCP(out[ipHdrLen:]) - - // Always use before-window seq so the server drops the fake packet. - // The offset is large enough to place the fake clearly outside the - // server's receive window, preventing the server from accepting it. - newSeq := (synSeq + 1 - uint32(len(fakePayload)+int(defaultWindowSize))) & 0xffffffff - tcpOut.SetSequenceNumber(newSeq) - - if method == MethodWrongAcknowledgment { - tcpOut.SetAckNumber(capturedAck - uint32(defaultWindowSize/2)) - } - - // Backdate timestamp for wrong-timestamp method - if method == MethodWrongTimestamp { - opts := tcpOut.Options() - tsSlice := make([]byte, len(opts)) - copy(tsSlice, opts) - if tsVal, hasTS := ParseTCPOptions(tsSlice); hasTS { - backdated := tsVal - if backdated > tcpTimestampBackdate { - backdated -= tcpTimestampBackdate - } else { - backdated = 0 - } - rewriteTCPOptionTimestamp(opts, backdated) - } - } - - // Set PSH flag since the spoofed packet is a data segment - tcpOut.SetFlags(capturedFlags | TCPFlagPsh) - - // Recalculate TCP checksum - tcpOut.SetChecksum(0) - tcpLen := tcpHdrLen + len(fakePayload) - pseudo := PseudoHeaderChecksum(TCPProtocolNumber, srcAddr.AsSlice(), dstAddr.AsSlice(), uint16(tcpLen)) - tcpChecksum := ^tcpOut.CalculateChecksum(pseudo) - - // Apply checksum corruption for wrong-checksum method - if method == MethodWrongChecksum { - tcpChecksum ^= 0xFFFF - } - tcpOut.SetChecksum(tcpChecksum) - - return out, nil -} - -func applyTCPChecksum(tcp TCP, srcAddr, dstAddr netip.Addr, payload []byte, corrupt bool) { - tcpLen := int(tcp.DataOffset()) + len(payload) - pseudo := PseudoHeaderChecksum(TCPProtocolNumber, srcAddr.AsSlice(), dstAddr.AsSlice(), uint16(tcpLen)) - payloadChecksum := Checksum(payload, 0) - tcpChecksum := ^tcp.CalculateChecksum(CombineChecksum(pseudo, payloadChecksum)) - if corrupt { - tcpChecksum ^= 0xFFFF - } - tcp.SetChecksum(tcpChecksum) -} diff --git a/transport/internet/finalmask/rawpacket/platform.go b/transport/internet/finalmask/rawpacket/platform.go new file mode 100644 index 000000000000..34a3a855dea4 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/platform.go @@ -0,0 +1,5 @@ +//go:build darwin || freebsd || linux || (windows && (amd64 || 386)) + +package rawpacket + +const PlatformSupported = true diff --git a/transport/internet/finalmask/rawpacket/platform_unsupported.go b/transport/internet/finalmask/rawpacket/platform_unsupported.go new file mode 100644 index 000000000000..5ef3145901aa --- /dev/null +++ b/transport/internet/finalmask/rawpacket/platform_unsupported.go @@ -0,0 +1,5 @@ +//go:build !darwin && !freebsd && !linux && !(windows && (amd64 || 386)) + +package rawpacket + +const PlatformSupported = false diff --git a/transport/internet/finalmask/rawpacket/raw_darwin.go b/transport/internet/finalmask/rawpacket/raw_darwin.go deleted file mode 100644 index 3d2af14afdd1..000000000000 --- a/transport/internet/finalmask/rawpacket/raw_darwin.go +++ /dev/null @@ -1,203 +0,0 @@ -package rawpacket - -import ( - "encoding/binary" - "errors" - "fmt" - "math/rand" - "net" - "net/netip" - "strconv" - "strings" - "sync" - "syscall" - - "golang.org/x/sys/unix" -) - -const PlatformSupported = true - -// Offsets into xinpcb_n within each net.inet.tcp.pcblist_n record, identical -// to the values used by common/process/searcher_darwin_shared.go. -const ( - darwinXinpgenSize = 24 - darwinXsocketOffset = 104 - darwinXinpcbForeignPort = 16 - darwinXinpcbLocalPort = 18 - darwinXinpcbVFlag = 44 - darwinXinpcbForeignAddr = 48 - darwinXinpcbLocalAddr = 64 - darwinXinpcbIPv4Offset = 12 - - darwinTCPExtraSize = 208 - - darwinXtcpcbSndNxtOffset = 56 - darwinXtcpcbRcvNxtOffset = 80 -) - -// darwinStructSize returns the size of xinpcb_n for the running Darwin kernel. -// Darwin 22 (macOS 13 Ventura) grew the struct from 384 to 408 bytes; there is -// no ABI-stable way to read it, so we key off the kernel version. -var darwinStructSize = sync.OnceValues(func() (int, error) { - value, err := syscall.Sysctl("kern.osrelease") - if err != nil { - return 0, fmt.Errorf("sysctl kern.osrelease: %w", err) - } - major, _, ok := strings.Cut(value, ".") - if !ok { - return 0, fmt.Errorf("unexpected kern.osrelease format: %s", value) - } - n, err := strconv.ParseInt(major, 10, 64) - if err != nil { - return 0, fmt.Errorf("parse kern.osrelease major version: : %w", err) - } - if n >= 22 { - return 408, nil - } - return 384, nil -}) - -type darwinSpoofer struct { - method Method - src netip.AddrPort - dst netip.AddrPort - rawFD int - rawSockAddr unix.Sockaddr - sendNext uint32 - receiveNext uint32 - ttl uint8 -} - -func newRawSpoofer(conn net.Conn, method Method, ttl uint8) (rawSpoofer, error) { - if method == MethodWrongTimestamp { - return nil, errors.New("rawpacket: wrong-timestamp is not supported on macOS") - } - _, src, dst, err := tcpEndpoints(conn) - if err != nil { - return nil, err - } - fd, sockaddr, err := openDarwinRawSocket(src, dst) - if err != nil { - return nil, err - } - sendNext, receiveNext, err := readDarwinTCPSequence(src, dst) - if err != nil { - unix.Close(fd) - return nil, err - } - return &darwinSpoofer{ - method: method, - src: src, - dst: dst, - rawFD: fd, - rawSockAddr: sockaddr, - sendNext: sendNext, - receiveNext: receiveNext, - ttl: ttl, - }, nil -} - -// readDarwinTCPSequence scans net.inet.tcp.pcblist_n for the PCB that matches -// src -> dst and returns (snd_nxt, rcv_nxt). These live in xtcpcb_n at the end -// of each record; see darwin-xnu bsd/netinet/in_pcblist.c:get_pcblist_n. -func readDarwinTCPSequence(src, dst netip.AddrPort) (uint32, uint32, error) { - buffer, err := unix.SysctlRaw("net.inet.tcp.pcblist_n") - if err != nil { - return 0, 0, fmt.Errorf("sysctl net.inet.tcp.pcblist_n: %w", err) - } - structSize, err := darwinStructSize() - if err != nil { - return 0, 0, err - } - itemSize := structSize + darwinTCPExtraSize - for i := darwinXinpgenSize; i+itemSize <= len(buffer); i += itemSize { - inpcb := buffer[i : i+darwinXsocketOffset] - xtcpcb := buffer[i+structSize : i+itemSize] - localPort := binary.BigEndian.Uint16(inpcb[darwinXinpcbLocalPort : darwinXinpcbLocalPort+2]) - remotePort := binary.BigEndian.Uint16(inpcb[darwinXinpcbForeignPort : darwinXinpcbForeignPort+2]) - if localPort != src.Port() || remotePort != dst.Port() { - continue - } - versionFlag := inpcb[darwinXinpcbVFlag] - var localAddr, remoteAddr netip.Addr - switch { - case versionFlag&0x1 != 0: - localAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset : darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset+4])) - remoteAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset : darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset+4])) - case versionFlag&0x2 != 0: - localAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbLocalAddr : darwinXinpcbLocalAddr+16])) - remoteAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbForeignAddr : darwinXinpcbForeignAddr+16])) - default: - continue - } - if localAddr.Unmap() != src.Addr() || remoteAddr.Unmap() != dst.Addr() { - continue - } - sendNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbSndNxtOffset : darwinXtcpcbSndNxtOffset+4]) - receiveNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbRcvNxtOffset : darwinXtcpcbRcvNxtOffset+4]) - return sendNext, receiveNext, nil - } - return 0, 0, fmt.Errorf("rawpacket: connection %v->%v not found in pcblist_n", src, dst) -} - -func openDarwinRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) { - if dst.Addr().Is4() { - return openIPv4RawSocket(dst) - } - // macOS does not accept IPV6_HDRINCL on AF_INET6 SOCK_RAW IPPROTO_TCP - // sockets, so the kernel builds the IPv6 header itself. Bind to the real - // connection's source address so in6_selectsrc returns it, and rely on - // in6p_cksum defaulting to -1 so the user-supplied TCP checksum is - // preserved (including deliberately corrupted ones). - fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) - if err != nil { - return -1, nil, fmt.Errorf("open AF_INET6 SOCK_RAW: %w", err) - } - err = unix.Bind(fd, &unix.SockaddrInet6{Addr: src.Addr().As16()}) - if err != nil { - unix.Close(fd) - return -1, nil, fmt.Errorf("bind AF_INET6 SOCK_RAW: %w", err) - } - sockaddr := &unix.SockaddrInet6{Port: int(dst.Port()), Addr: dst.Addr().As16()} - return fd, sockaddr, nil -} - -func (s *darwinSpoofer) Inject(payload []byte) error { - if !s.src.Addr().Is4() { - segment, err := buildSpoofTCPSegment(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, payload) - if err != nil { - return err - } - err = unix.Sendto(s.rawFD, segment, 0, s.rawSockAddr) - if err != nil { - return fmt.Errorf("sendto raw socket: %w", err) - } - return nil - } - frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, nil, payload, s.ttl) - if err != nil { - return err - } - ip := IPv4(frame) - // Non-zero IP ID avoids DPI flagging. - ip.SetID(uint16(rand.Uint32())) - // Darwin inherits the historical BSD quirk: with IP_HDRINCL the kernel - // expects ip_len and ip_off in host byte order, not network byte order. - binary.NativeEndian.PutUint16(ip[2:4], ip.TotalLength()) - binary.NativeEndian.PutUint16(ip[6:8], uint16(ip.Flags())<<13|ip.FragmentOffset()) - ip.RecalcChecksum() - err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) - if err != nil { - return fmt.Errorf("sendto raw socket: %w", err) - } - return nil -} - -func (s *darwinSpoofer) Close() error { - if s.rawFD < 0 { - return nil - } - err := unix.Close(s.rawFD) - s.rawFD = -1 - return err -} diff --git a/transport/internet/finalmask/rawpacket/raw_freebsd.go b/transport/internet/finalmask/rawpacket/raw_freebsd.go deleted file mode 100644 index a8cd12db9ef0..000000000000 --- a/transport/internet/finalmask/rawpacket/raw_freebsd.go +++ /dev/null @@ -1,178 +0,0 @@ -package rawpacket - -import ( - "encoding/binary" - "errors" - "fmt" - "math/rand" - "net" - "net/netip" - "syscall" - "unsafe" - - "golang.org/x/sys/unix" -) - -const PlatformSupported = true - -// FreeBSD tcp_info offsets for snd_nxt and rcv_nxt. -// Derived from FreeBSD sys/netinet/tcp.h struct tcp_info layout. -// -// struct tcp_info { -// u8 state, __ca, __retrans, __probes, __backoff, opts, wscale = 8 bytes (with pad) -// u32 rto, __ato, snd_mss, rcv_mss = 16 bytes (offset 8) -// u32 __unacked, __sacked, __lost, __retrans, __fackets = 20 bytes (offset 24) -// u32 __last_data_sent, __last_ack_sent, last_data_recv, __last_ack_recv = 16 bytes (offset 44) -// u32 __pmtu, __rcv_ssthresh, rtt, rttvar, snd_ssthresh, snd_cwnd, __advmss, __reordering = 32 bytes (offset 60) -// u32 __rcv_rtt, rcv_space = 8 bytes (offset 92) -// u32 snd_wnd, snd_bwnd = 8 bytes (offset 100) -// u32 snd_nxt, rcv_nxt = 8 bytes (offset 108) -// ... remaining fields -// } -const ( - freebsdTCPInfoSndNxtOffset = 108 - freebsdTCPInfoRcvNxtOffset = 112 - freebsdTCPInfoMinSize = 116 // must read at least through rcv_nxt -) - -type freebsdSpoofer struct { - method Method - src netip.AddrPort - dst netip.AddrPort - rawFD int - rawSockAddr unix.Sockaddr - sendNext uint32 - receiveNext uint32 - ttl uint8 -} - -func newRawSpoofer(conn net.Conn, method Method, ttl uint8) (rawSpoofer, error) { - if method == MethodWrongTimestamp { - return nil, errors.New("rawpacket: wrong-timestamp is not supported on FreeBSD") - } - tcpConn, src, dst, err := tcpEndpoints(conn) - if err != nil { - return nil, err - } - fd, sockaddr, err := openFreeBSDRawSocket(src, dst) - if err != nil { - return nil, err - } - sendNext, receiveNext, err := readFreeBSDTCPSequence(tcpConn) - if err != nil { - unix.Close(fd) - return nil, err - } - return &freebsdSpoofer{ - method: method, - src: src, - dst: dst, - rawFD: fd, - rawSockAddr: sockaddr, - sendNext: sendNext, - receiveNext: receiveNext, - ttl: ttl, - }, nil -} - -// readFreeBSDTCPSequence retrieves snd_nxt and rcv_nxt via TCP_INFO getsockopt. -func readFreeBSDTCPSequence(conn *net.TCPConn) (uint32, uint32, error) { - raw, err := conn.SyscallConn() - if err != nil { - return 0, 0, fmt.Errorf("rawpacket: SyscallConn: %w", err) - } - var sendNext, receiveNext uint32 - var sockErr error - err = raw.Control(func(fd uintptr) { - buf := make([]byte, 256) // generous buffer for tcp_info - bufLen := uint32(len(buf)) - _, _, errno := syscall.Syscall6( - syscall.SYS_GETSOCKOPT, - fd, - uintptr(syscall.IPPROTO_TCP), - uintptr(0x20), // TCP_INFO = 0x20 - uintptr(unsafe.Pointer(&buf[0])), - uintptr(unsafe.Pointer(&bufLen)), - 0, - ) - if errno != 0 { - sockErr = fmt.Errorf("rawpacket: getsockopt TCP_INFO: %w", errno) - return - } - if bufLen < freebsdTCPInfoMinSize { - sockErr = fmt.Errorf("rawpacket: TCP_INFO too short: %d < %d", bufLen, freebsdTCPInfoMinSize) - return - } - sendNext = binary.NativeEndian.Uint32(buf[freebsdTCPInfoSndNxtOffset : freebsdTCPInfoSndNxtOffset+4]) - receiveNext = binary.NativeEndian.Uint32(buf[freebsdTCPInfoRcvNxtOffset : freebsdTCPInfoRcvNxtOffset+4]) - }) - if err != nil { - return 0, 0, err - } - if sockErr != nil { - return 0, 0, sockErr - } - return sendNext, receiveNext, nil -} - -func openFreeBSDRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) { - if dst.Addr().Is4() { - return openIPv4RawSocket(dst) - } - // FreeBSD, like macOS, does not support IPV6_HDRINCL on SOCK_RAW/IPPROTO_TCP. - // The kernel constructs the IPv6 header. Bind to the source address - // and let the kernel fill in the IPv6 header automatically. - fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) - if err != nil { - return -1, nil, fmt.Errorf("rawpacket: open AF_INET6 SOCK_RAW: %w", err) - } - err = unix.Bind(fd, &unix.SockaddrInet6{Addr: src.Addr().As16()}) - if err != nil { - unix.Close(fd) - return -1, nil, fmt.Errorf("rawpacket: bind AF_INET6 SOCK_RAW: %w", err) - } - sockaddr := &unix.SockaddrInet6{Port: int(dst.Port()), Addr: dst.Addr().As16()} - return fd, sockaddr, nil -} - -func (s *freebsdSpoofer) Inject(payload []byte) error { - if !s.src.Addr().Is4() { - // IPv6: kernel builds the IP header, we supply TCP segment only. - segment, err := buildSpoofTCPSegment(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, payload) - if err != nil { - return err - } - err = unix.Sendto(s.rawFD, segment, 0, s.rawSockAddr) - if err != nil { - return fmt.Errorf("rawpacket: sendto raw socket: %w", err) - } - return nil - } - // IPv4: we build the full IP+TCP frame with IP_HDRINCL. - frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, nil, payload, s.ttl) - if err != nil { - return err - } - ip := IPv4(frame) - // Non-zero IP ID avoids DPI flagging. - ip.SetID(uint16(rand.Uint32())) - // FreeBSD inherits the historical BSD quirk: with IP_HDRINCL the kernel - // expects ip_len and ip_off in host byte order, not network byte order. - binary.NativeEndian.PutUint16(ip[2:4], ip.TotalLength()) - binary.NativeEndian.PutUint16(ip[6:8], uint16(ip.Flags())<<13|ip.FragmentOffset()) - ip.RecalcChecksum() - err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) - if err != nil { - return fmt.Errorf("rawpacket: sendto raw socket: %w", err) - } - return nil -} - -func (s *freebsdSpoofer) Close() error { - if s.rawFD < 0 { - return nil - } - err := unix.Close(s.rawFD) - s.rawFD = -1 - return err -} diff --git a/transport/internet/finalmask/rawpacket/raw_linux.go b/transport/internet/finalmask/rawpacket/raw_linux.go deleted file mode 100644 index 0786ca9851be..000000000000 --- a/transport/internet/finalmask/rawpacket/raw_linux.go +++ /dev/null @@ -1,169 +0,0 @@ -package rawpacket - -import ( - "fmt" - "math/rand" - "net" - "net/netip" - - "golang.org/x/sys/unix" -) - -const PlatformSupported = true - -const ( - // Values of enum { TCP_NO_QUEUE, TCP_RECV_QUEUE, TCP_SEND_QUEUE } from - // include/net/tcp.h; not exported by golang.org/x/sys/unix. - tcpRecvQueue = 1 - tcpSendQueue = 2 -) - -type linuxSpoofer struct { - method Method - src netip.AddrPort - dst netip.AddrPort - rawFD int - rawSockAddr unix.Sockaddr - sendNext uint32 - receiveNext uint32 - timestamp uint32 - ttl uint8 -} - -func newRawSpoofer(conn net.Conn, method Method, ttl uint8) (rawSpoofer, error) { - tcpConn, src, dst, err := tcpEndpoints(conn) - if err != nil { - return nil, err - } - fd, sockaddr, err := openLinuxRawSocket(dst) - if err != nil { - return nil, err - } - spoofer := &linuxSpoofer{ - method: method, - src: src, - dst: dst, - rawFD: fd, - rawSockAddr: sockaddr, - ttl: ttl, - } - err = spoofer.loadSequenceNumbers(tcpConn) - if err != nil { - unix.Close(fd) - return nil, err - } - return spoofer, nil -} - -func openLinuxRawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) { - if dst.Addr().Is4() { - return openIPv4RawSocket(dst) - } - fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) - if err != nil { - return -1, nil, fmt.Errorf("open AF_INET6 SOCK_RAW: %w", err) - } - err = unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_HDRINCL, 1) - if err != nil { - unix.Close(fd) - return -1, nil, fmt.Errorf("set IPV6_HDRINCL: %w", err) - } - // Linux raw IPv6 sockets interpret sin6_port as a nexthdr protocol number - // (see raw(7)); any value other than 0 or the socket's IPPROTO_TCP causes - // sendto to fail with EINVAL. The destination is already encoded in the - // user-supplied IPv6 header under IPV6_HDRINCL. - sockaddr := &unix.SockaddrInet6{Addr: dst.Addr().As16()} - return fd, sockaddr, nil -} - -// loadSequenceNumbers briefly enters TCP_REPAIR mode to read snd_nxt and -// rcv_nxt from the kernel, then immediately exits TCP_REPAIR. TCP_REPAIR -// requires CAP_NET_ADMIN. -func (s *linuxSpoofer) loadSequenceNumbers(tcpConn *net.TCPConn) error { - rawConn, err := tcpConn.SyscallConn() - if err != nil { - return err - } - var ctrlErr error - err = rawConn.Control(func(raw uintptr) { - fd := int(raw) - - timestamp, tsErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_TIMESTAMP) - if tsErr == nil { - s.timestamp = uint32(timestamp) - } - - ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR, unix.TCP_REPAIR_ON) - if ctrlErr != nil { - ctrlErr = fmt.Errorf("rawpacket: enter TCP_REPAIR (need CAP_NET_ADMIN): %w", ctrlErr) - return - } - defer func() { - offErr := unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR, unix.TCP_REPAIR_OFF) - if offErr != nil { - offErr = fmt.Errorf("rawpacket: leave TCP_REPAIR: %w", offErr) - if ctrlErr == nil { - ctrlErr = offErr - } else { - ctrlErr = fmt.Errorf("%v; also %w", ctrlErr, offErr) - } - } - }() - - ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR_QUEUE, tcpSendQueue) - if ctrlErr != nil { - ctrlErr = fmt.Errorf("rawpacket: select TCP_SEND_QUEUE: %w", ctrlErr) - return - } - sendSequence, seqErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_QUEUE_SEQ) - if seqErr != nil { - ctrlErr = fmt.Errorf("rawpacket: read send queue sequence: %w", seqErr) - return - } - ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR_QUEUE, tcpRecvQueue) - if ctrlErr != nil { - ctrlErr = fmt.Errorf("rawpacket: select TCP_RECV_QUEUE: %w", ctrlErr) - return - } - receiveSequence, seqErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_QUEUE_SEQ) - if seqErr != nil { - ctrlErr = fmt.Errorf("rawpacket: read recv queue sequence: %w", seqErr) - return - } - s.sendNext = uint32(sendSequence) - s.receiveNext = uint32(receiveSequence) - }) - if err != nil { - return err - } - return ctrlErr -} - -func (s *linuxSpoofer) Inject(payload []byte) error { - frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, s.timestamp, nil, payload, s.ttl) - if err != nil { - return err - } - // Use a non-zero IP ID. The buildSpoofFrame → buildTCPSegment path - // passes id=0 to IPv4.Encode; override it with a random value since - // IP ID 0 is a DPI red flag. - if s.src.Addr().Is4() && len(frame) >= IPv4MinimumSize { - ip := IPv4(frame) - ip.SetID(uint16(rand.Uint32())) - ip.RecalcChecksum() - } - err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) - if err != nil { - return fmt.Errorf("sendto raw socket: %w", err) - } - return nil -} - -func (s *linuxSpoofer) Close() error { - if s.rawFD < 0 { - return nil - } - err := unix.Close(s.rawFD) - s.rawFD = -1 - return err -} diff --git a/transport/internet/finalmask/rawpacket/raw_stub.go b/transport/internet/finalmask/rawpacket/raw_stub.go deleted file mode 100644 index 596a9713a5f1..000000000000 --- a/transport/internet/finalmask/rawpacket/raw_stub.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build !linux && !darwin && !freebsd && !(windows && (amd64 || 386)) - -package rawpacket - -import ( - "errors" - "net" -) - -const PlatformSupported = false - -func newRawSpoofer(conn net.Conn, method Method, ttl uint8) (rawSpoofer, error) { - return nil, errors.New("rawpacket: unsupported platform") -} diff --git a/transport/internet/finalmask/rawpacket/raw_unix.go b/transport/internet/finalmask/rawpacket/raw_unix.go deleted file mode 100644 index bccd0fefe0de..000000000000 --- a/transport/internet/finalmask/rawpacket/raw_unix.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build linux || darwin || freebsd - -package rawpacket - -import ( - "fmt" - "net/netip" - - "golang.org/x/sys/unix" -) - -func openIPv4RawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) { - fd, err := unix.Socket(unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_TCP) - if err != nil { - return -1, nil, fmt.Errorf("open AF_INET SOCK_RAW: %w", err) - } - err = unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_HDRINCL, 1) - if err != nil { - unix.Close(fd) - return -1, nil, fmt.Errorf("set IP_HDRINCL: %w", err) - } - sockaddr := &unix.SockaddrInet4{Port: int(dst.Port())} - sockaddr.Addr = dst.Addr().As4() - return fd, sockaddr, nil -} diff --git a/transport/internet/finalmask/rawpacket/raw_windows.go b/transport/internet/finalmask/rawpacket/raw_windows.go deleted file mode 100644 index 31df91f266c9..000000000000 --- a/transport/internet/finalmask/rawpacket/raw_windows.go +++ /dev/null @@ -1,310 +0,0 @@ -//go:build windows && (amd64 || 386) - -package rawpacket - -import ( - "encoding/binary" - "errors" - "fmt" - "net" - "net/netip" - "slices" - "sync" - "sync/atomic" - "time" - - "github.com/xtls/xray-core/common/log" - "github.com/xtls/xray-core/transport/internet/finalmask/rawpacket/windivert" - "golang.org/x/sys/windows" -) - -const PlatformSupported = true - -// closeGracePeriod caps how long Close() waits for the divert goroutine to -// observe the kernel-emitted real ClientHello and perform the reorder -// (fake → real). In practice this completes in microseconds; the cap -// bounds the pathological case where the kernel buffers the packet. -const closeGracePeriod = 2 * time.Second - -// windowsSpoofer uses a single WinDivert handle for both capture and -// injection. Sequential Send() calls on one handle traverse one driver queue, -// so the fake provably precedes the released real on the wire — a guarantee -// two separate handles cannot make because cross-handle order depends on the -// scheduler. -type windowsSpoofer struct { - method Method - src, dst netip.AddrPort - divertH *windivert.Handle - ttl uint8 - - fakeReady chan []byte // buffered(1): staged by Inject - done chan struct{} // closed by run() on exit - closeOnce sync.Once - runErr atomic.Pointer[error] -} - -func newRawSpoofer(conn net.Conn, method Method, ttl uint8) (rawSpoofer, error) { - _, src, dst, err := tcpEndpoints(conn) - if err != nil { - return nil, err - } - filter, err := windivert.BidirectionalTCP(src, dst) - if err != nil { - return nil, err - } - log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: opening WinDivert handle filter=%q src=%s dst=%s method=%s", filter, src, dst, method)}) - divertH, err := windivert.Open(filter, windivert.LayerNetwork, windivert.PriorityLowest, 0) - if err != nil { - return nil, err - } - log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: WinDivert opened src=%s dst=%s method=%s", src, dst, method)}) - s := &windowsSpoofer{ - method: method, - src: src, - dst: dst, - divertH: divertH, - ttl: ttl, - fakeReady: make(chan []byte, 1), - done: make(chan struct{}), - } - go s.run() - return s, nil -} - -func (s *windowsSpoofer) Inject(payload []byte) error { - log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: Inject called payload_len=%d", len(payload))}) - select { - case s.fakeReady <- payload: - log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: "rawpacket: injected payload onto fakeReady"}) - return nil - case <-s.done: - if p := s.runErr.Load(); p != nil { - log.Record(&log.GeneralMessage{Severity: log.Severity_Error, Content: fmt.Sprintf("rawpacket: Inject failed spoofer closed err=%v", *p)}) - return *p - } - return errors.New("rawpacket: spoofer closed before Inject") - } -} - -func (s *windowsSpoofer) Close() error { - s.closeOnce.Do(func() { - // Give run() a grace window to finish handling the real packet. - select { - case <-s.done: - case <-time.After(closeGracePeriod): - // Force Recv() to return by closing the divert handle. - s.divertH.Close() - <-s.done - } - }) - if p := s.runErr.Load(); p != nil { - return *p - } - return nil -} - -func (s *windowsSpoofer) recordErr(err error) { s.runErr.Store(&err) } - -func (s *windowsSpoofer) run() { - defer close(s.done) - defer s.divertH.Close() - defer log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: "rawpacket: run() exiting"}) - - log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: "rawpacket: run() started"}) - buf := make([]byte, windivert.MTUMax) - packetCount := 0 - for { - n, addr, err := s.divertH.Recv(buf) - if err != nil { - if errors.Is(err, windows.ERROR_OPERATION_ABORTED) || - errors.Is(err, windows.ERROR_NO_DATA) { - log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: Recv returned expected err=%v", err)}) - return - } - log.Record(&log.GeneralMessage{Severity: log.Severity_Error, Content: fmt.Sprintf("rawpacket: Recv err=%v", err)}) - s.recordErr(err) - return - } - pkt := buf[:n] - packetCount++ - seq, _, _, payloadLen, ok := parseTCPPacket(pkt, addr.IPv6()) - if !ok { - log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: pkt#%d not TCP/passthrough len=%d", packetCount, n)}) - _, sendErr := s.divertH.Send(pkt, &addr) - if sendErr != nil { - log.Record(&log.GeneralMessage{Severity: log.Severity_Error, Content: fmt.Sprintf("rawpacket: Send err after parse fail=%v", sendErr)}) - s.recordErr(sendErr) - return - } - continue - } - - // Check direction. s.src is the local (client) address. - var isOutbound bool - if addr.IPv6() { - if len(pkt) < IPv6MinimumSize+TCPMinimumSize { - _, _ = s.divertH.Send(pkt, &addr) - continue - } - ip6 := IPv6(pkt) - srcIP := ip6.Src() - srcPort := binary.BigEndian.Uint16(pkt[IPv6MinimumSize:]) - if srcIP == s.src.Addr() && srcPort == s.src.Port() { - isOutbound = true - } else if srcIP == s.dst.Addr() && srcPort == s.dst.Port() { - isOutbound = false - } else { - log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: pkt#%d direction-unknown (neither side) passthrough", packetCount)}) - _, _ = s.divertH.Send(pkt, &addr) - continue - } - } else { - if len(pkt) < IPv4MinimumSize+TCPMinimumSize { - _, _ = s.divertH.Send(pkt, &addr) - continue - } - ip4 := IPv4(pkt) - srcIP := ip4.Src() - srcPort := binary.BigEndian.Uint16(pkt[IPv4MinimumSize:]) - if srcIP == s.src.Addr() && srcPort == s.src.Port() { - isOutbound = true - } else if srcIP == s.dst.Addr() && srcPort == s.dst.Port() { - isOutbound = false - } else { - log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: pkt#%d direction-unknown (neither side) passthrough", packetCount)}) - _, _ = s.divertH.Send(pkt, &addr) - continue - } - } - - if !isOutbound { - log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: pkt#%d inbound seq=%d payload=%d passthrough", packetCount, seq, payloadLen)}) - _, err := s.divertH.Send(pkt, &addr) - if err != nil { - log.Record(&log.GeneralMessage{Severity: log.Severity_Error, Content: fmt.Sprintf("rawpacket: Send err inbound=%v", err)}) - s.recordErr(err) - return - } - continue - } - - if payloadLen == 0 { - log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: pkt#%d outbound ack/ctrl passthrough", packetCount)}) - _, err := s.divertH.Send(pkt, &addr) - if err != nil { - log.Record(&log.GeneralMessage{Severity: log.Severity_Error, Content: fmt.Sprintf("rawpacket: Send err outbound-ctrl=%v", err)}) - s.recordErr(err) - return - } - continue - } - - // Outbound data packet — the real ClientHello. - log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: pkt#%d outbound DATA seq=%d payload=%d", packetCount, seq, payloadLen)}) - var fake []byte - select { - case fake = <-s.fakeReady: - log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: fakeReady consumed fake_len=%d", len(fake))}) - default: - // Inject() not yet called — pass through and keep observing. - log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: "rawpacket: fakeReady empty, pass-through until Inject called"}) - _, err := s.divertH.Send(pkt, &addr) - if err != nil { - log.Record(&log.GeneralMessage{Severity: log.Severity_Error, Content: fmt.Sprintf("rawpacket: Send err data-passthrough=%v", err)}) - s.recordErr(err) - return - } - continue - } - - // Build the spoofed packet from the captured real packet template. - // This preserves all TCP options and IP ID sequencing from the real - // connection. synSeq is derived from the captured data seq (first - // data after handshake always has seq = synSeq + 1). - synSeq := seq - 1 - log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: fmt.Sprintf("rawpacket: building spoof seq=%d synSeq=%d fake=%d method=%s", seq, synSeq, len(fake), s.method)}) - frame, err := buildSpoofFromCapturedPacket(pkt, addr.IPv6(), synSeq, fake, s.method) - if err != nil { - log.Record(&log.GeneralMessage{Severity: log.Severity_Error, Content: fmt.Sprintf("rawpacket: buildSpoofFromCapturedPacket err=%v", err)}) - s.recordErr(err) - return - } - fakeAddr := addr // inherit Outbound, IfIdx - // buildSpoofFromCapturedPacket emits ready-to-wire bytes with - // correct checksums. The driver would recompute checksums on Send - // when TCPChecksum/IPChecksum are 0. Force both to 1 to preserve - // intentional corruption (wrong-checksum method) and keep our bytes. - fakeAddr.SetIPChecksum(true) - fakeAddr.SetTCPChecksum(true) - log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: "rawpacket: sending fake frame"}) - _, err = s.divertH.Send(frame, &fakeAddr) - if err != nil { - log.Record(&log.GeneralMessage{Severity: log.Severity_Error, Content: fmt.Sprintf("rawpacket: Send fake err=%v", err)}) - s.recordErr(err) - return - } - log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: "rawpacket: sending real frame"}) - _, err = s.divertH.Send(pkt, &addr) - if err != nil { - log.Record(&log.GeneralMessage{Severity: log.Severity_Error, Content: fmt.Sprintf("rawpacket: Send real err=%v", err)}) - s.recordErr(err) - return - } - log.Record(&log.GeneralMessage{Severity: log.Severity_Debug, Content: "rawpacket: reorder complete"}) - return // single-shot reorder complete - } -} - -func parseTCPPacket(pkt []byte, isV6 bool) (seq, ack uint32, options []byte, payloadLen int, ok bool) { - if isV6 { - if len(pkt) < IPv6MinimumSize+TCPMinimumSize { - return 0, 0, nil, 0, false - } - ip := IPv6(pkt) - if ip.TransportProtocol() != TCPProtocolNumber { - return 0, 0, nil, 0, false - } - tcp := TCP(pkt[IPv6MinimumSize:]) - tcpHdr := int(tcp.DataOffset()) - if tcpHdr < TCPMinimumSize || IPv6MinimumSize+tcpHdr > len(pkt) { - return 0, 0, nil, 0, false - } - total := IPv6MinimumSize + int(ip.PayloadLength()) - if total == IPv6MinimumSize || total > len(pkt) { - total = len(pkt) - } - if total < IPv6MinimumSize+tcpHdr { - return 0, 0, nil, 0, false - } - return tcp.SequenceNumber(), tcp.AckNumber(), slices.Clone(tcp.Options()), - total - IPv6MinimumSize - tcpHdr, true - } - if len(pkt) < IPv4MinimumSize+TCPMinimumSize { - return 0, 0, nil, 0, false - } - ip := IPv4(pkt) - if ip.Protocol() != TCPProtocolNumber { - return 0, 0, nil, 0, false - } - ihl := int(ip.HeaderLength()) - // ihl+TCPMinimumSize guards the TCP-header field reads below; without - // this, an IPv4 packet with options (ihl>20) against a 40-byte buffer - // reads past the TCP slice when calling DataOffset. - if ihl < IPv4MinimumSize || ihl+TCPMinimumSize > len(pkt) { - return 0, 0, nil, 0, false - } - tcp := TCP(pkt[ihl:]) - tcpHdr := int(tcp.DataOffset()) - if tcpHdr < TCPMinimumSize || ihl+tcpHdr > len(pkt) { - return 0, 0, nil, 0, false - } - total := int(ip.TotalLength()) - if total == 0 || total > len(pkt) { - total = len(pkt) - } - if total < ihl+tcpHdr { - return 0, 0, nil, 0, false - } - return tcp.SequenceNumber(), tcp.AckNumber(), slices.Clone(tcp.Options()), - total - ihl - tcpHdr, true -} diff --git a/transport/internet/finalmask/rawpacket/spoof_conn.go b/transport/internet/finalmask/rawpacket/spoof_conn.go new file mode 100644 index 000000000000..f2b66458102b --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_conn.go @@ -0,0 +1,105 @@ +package rawpacket + +import ( + "fmt" + "net" + "net/netip" + "sync" + "time" +) + +type SpoofConn struct { + sender SpoofSender + recver SpoofReceiver + relayIP netip.Addr + relayPort uint16 + + recvBuf []byte + readClosed bool + + closeOnce sync.Once +} + +func DialSpoof(relayAddr netip.AddrPort, spoofIPs []netip.Addr, srcPort uint16, ttl uint8, sendProto, recvProto string, peerSpoofIP netip.Addr) (net.Conn, error) { + if sendProto == "" { + sendProto = "tcp" + } + if recvProto == "" { + recvProto = "udp" + } + + if recvProto == "icmp" || recvProto == "icmpv6" { + suppressICMPEchoReply() + } + + sender, err := NewSender(sendProto, &SpoofSenderConfig{ + SourceIPs: spoofIPs, + SourcePort: srcPort, + TTL: ttl, + }) + if err != nil { + return nil, fmt.Errorf("rawpacket: create sender: %w", err) + } + + recver, err := NewReceiver(recvProto, &SpoofReceiverConfig{ + ListenPort: srcPort, + PeerSpoofIP: peerSpoofIP, + BufferSize: 4 * 1024 * 1024, + }) + if err != nil { + sender.Close() + return nil, fmt.Errorf("rawpacket: create receiver: %w", err) + } + + return &SpoofConn{ + sender: sender, + recver: recver, + relayIP: relayAddr.Addr(), + relayPort: relayAddr.Port(), + recvBuf: make([]byte, 65536), + }, nil +} + +func (c *SpoofConn) Write(b []byte) (int, error) { + if len(b) == 0 { + return 0, nil + } + if err := c.sender.Send(b, c.relayIP, c.relayPort); err != nil { + return 0, err + } + return len(b), nil +} + +func (c *SpoofConn) Read(buf []byte) (int, error) { + data, _, _, err := c.recver.Receive() + if err != nil { + return 0, err + } + if len(data) == 0 { + return 0, nil + } + n := copy(buf, data) + return n, nil +} + +func (c *SpoofConn) Close() error { + c.sender.Close() + c.recver.Close() + return nil +} + +func (c *SpoofConn) LocalAddr() net.Addr { + return &net.TCPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 0} +} + +func (c *SpoofConn) RemoteAddr() net.Addr { + return &net.TCPAddr{IP: c.relayIP.AsSlice(), Port: int(c.relayPort)} +} + +func (c *SpoofConn) SetDeadline(t time.Time) error { return nil } +func (c *SpoofConn) SetReadDeadline(t time.Time) error { return nil } +func (c *SpoofConn) SetWriteDeadline(t time.Time) error { return nil } + +func (c *SpoofConn) TcpMaskConn() {} +func (c *SpoofConn) RawConn() net.Conn { return nil } +func (c *SpoofConn) Splice() bool { return false } diff --git a/transport/internet/finalmask/rawpacket/spoof_icmp_utils.go b/transport/internet/finalmask/rawpacket/spoof_icmp_utils.go new file mode 100644 index 000000000000..3b14f7681dbb --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_icmp_utils.go @@ -0,0 +1,48 @@ +package rawpacket + +import ( + "log" + "os/exec" + "runtime" +) + +func suppressICMPEchoReply() bool { + switch runtime.GOOS { + case "linux": + err := exec.Command("sysctl", "-w", "net.ipv4.icmp_echo_ignore_all=1").Run() + if err != nil { + log.Printf("[rawpacket] failed to suppress ICMP echo replies: %v", err) + return false + } + log.Printf("[rawpacket] suppressed kernel ICMP echo replies") + return true + case "freebsd", "openbsd": + err := exec.Command("sysctl", "net.inet.icmp.bmcastecho=0").Run() + if err != nil { + log.Printf("[rawpacket] failed to suppress ICMP echo replies on %s: %v", runtime.GOOS, err) + return false + } + log.Printf("[rawpacket] suppressed kernel ICMP echo replies on %s", runtime.GOOS) + return true + default: + log.Printf("[rawpacket] ICMP echo reply suppression not supported on %s", runtime.GOOS) + return false + } +} + +func restoreICMPEchoReply() { + switch runtime.GOOS { + case "linux": + err := exec.Command("sysctl", "-w", "net.ipv4.icmp_echo_ignore_all=0").Run() + if err != nil { + log.Printf("[rawpacket] failed to restore ICMP echo replies: %v", err) + return + } + log.Printf("[rawpacket] restored kernel ICMP echo replies") + case "freebsd", "openbsd": + err := exec.Command("sysctl", "net.inet.icmp.bmcastecho=1").Run() + if err != nil { + log.Printf("[rawpacket] failed to restore ICMP echo replies on %s: %v", runtime.GOOS, err) + } + } +} diff --git a/transport/internet/finalmask/rawpacket/spoof_ip.go b/transport/internet/finalmask/rawpacket/spoof_ip.go new file mode 100644 index 000000000000..663310d862ab --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_ip.go @@ -0,0 +1,69 @@ +package rawpacket + +import ( + "encoding/binary" + "net/netip" +) + +func BuildIPv4Header(totalLen uint16, id uint16, ttl uint8, protocol uint8, src, dst netip.Addr) []byte { + b := make([]byte, 20) + b[0] = (4 << 4) | 5 + b[1] = 0 + binary.BigEndian.PutUint16(b[2:], totalLen) + binary.BigEndian.PutUint16(b[4:], id) + binary.BigEndian.PutUint16(b[6:], 0) + b[8] = ttl + b[9] = protocol + copy(b[12:16], src.AsSlice()) + copy(b[16:20], dst.AsSlice()) + return b +} + +func BuildIPv6Header(payloadLen uint16, transportProtocol uint8, hopLimit uint8, src, dst netip.Addr) []byte { + b := make([]byte, 40) + binary.BigEndian.PutUint32(b[0:], 6<<28) + binary.BigEndian.PutUint16(b[4:], payloadLen) + b[6] = transportProtocol + b[7] = hopLimit + copy(b[8:24], src.AsSlice()) + copy(b[24:40], dst.AsSlice()) + return b +} + +func IPv4PseudoHeaderChecksum(src, dst netip.Addr, protocol uint8, tcpLen uint16) uint16 { + var csum uint32 + srcB := src.As4() + dstB := dst.As4() + for i := 0; i < 4; i += 2 { + csum += uint32(binary.BigEndian.Uint16(srcB[i:])) + } + for i := 0; i < 4; i += 2 { + csum += uint32(binary.BigEndian.Uint16(dstB[i:])) + } + csum += uint32(protocol) + csum += uint32(tcpLen) + for csum > 0xffff { + csum = (csum >> 16) + (csum & 0xffff) + } + return uint16(csum) +} + +func IPv6PseudoHeaderChecksum(src, dst netip.Addr, protocol uint8, totalLen uint32) uint16 { + var csum uint32 + srcB := src.As16() + dstB := dst.As16() + for i := 0; i < 16; i += 2 { + csum += uint32(binary.BigEndian.Uint16(srcB[i:])) + } + for i := 0; i < 16; i += 2 { + csum += uint32(binary.BigEndian.Uint16(dstB[i:])) + } + csum += uint32(protocol) + csum += totalLen + for csum > 0xffff { + csum = (csum >> 16) + (csum & 0xffff) + } + return uint16(csum) +} + + diff --git a/transport/internet/finalmask/rawpacket/spoof_rawsend.go b/transport/internet/finalmask/rawpacket/spoof_rawsend.go new file mode 100644 index 000000000000..11511b1b3a33 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_rawsend.go @@ -0,0 +1,59 @@ +//go:build darwin || freebsd || linux + +package rawpacket + +import ( + "fmt" + "net/netip" + "sync" + + "golang.org/x/sys/unix" +) + +type rawSendFD struct { + fd int + sockAddr unix.Sockaddr + mu sync.Mutex + closed bool +} + +func openRawSender(dstIP netip.Addr) (*rawSendFD, error) { + if !dstIP.Is4() { + return nil, fmt.Errorf("rawpacket: IPv6 raw sender not yet supported") + } + + fd, err := unix.Socket(unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_RAW) + if err != nil { + return nil, fmt.Errorf("rawpacket: open SOCK_RAW: %w", err) + } + err = unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_HDRINCL, 1) + if err != nil { + unix.Close(fd) + return nil, fmt.Errorf("rawpacket: set IP_HDRINCL: %w", err) + } + + _ = unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_SNDBUF, 4*1024*1024) + + sa := &unix.SockaddrInet4{} + sa.Addr = dstIP.As4() + return &rawSendFD{fd: fd, sockAddr: sa}, nil +} + +func (r *rawSendFD) send(packet []byte) error { + r.mu.Lock() + defer r.mu.Unlock() + if r.closed { + return fmt.Errorf("rawpacket: raw sender closed") + } + return unix.Sendto(r.fd, packet, 0, r.sockAddr) +} + +func (r *rawSendFD) close() error { + r.mu.Lock() + defer r.mu.Unlock() + if r.closed { + return nil + } + r.closed = true + return unix.Close(r.fd) +} diff --git a/transport/internet/finalmask/rawpacket/spoof_rawsend_stub.go b/transport/internet/finalmask/rawpacket/spoof_rawsend_stub.go new file mode 100644 index 000000000000..1ba88f076198 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_rawsend_stub.go @@ -0,0 +1,22 @@ +//go:build !darwin && !freebsd && !linux && !(windows && (amd64 || 386)) + +package rawpacket + +import ( + "fmt" + "net/netip" +) + +type rawSendFD struct{} + +func openRawSender(dstIP netip.Addr) (*rawSendFD, error) { + return nil, fmt.Errorf("rawpacket: raw sockets not supported on this platform") +} + +func (r *rawSendFD) send(packet []byte) error { + return fmt.Errorf("rawpacket: raw sockets not supported on this platform") +} + +func (r *rawSendFD) close() error { + return nil +} diff --git a/transport/internet/finalmask/rawpacket/spoof_rawsend_windows.go b/transport/internet/finalmask/rawpacket/spoof_rawsend_windows.go new file mode 100644 index 000000000000..fae1986264a6 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_rawsend_windows.go @@ -0,0 +1,52 @@ +//go:build windows && (amd64 || 386) + +package rawpacket + +import ( + "fmt" + "net/netip" + "sync" + + "github.com/xtls/xray-core/transport/internet/finalmask/rawpacket/windivert" +) + +type rawSendFD struct { + h *windivert.Handle + mu sync.Mutex + closed bool +} + +func openRawSender(dstIP netip.Addr) (*rawSendFD, error) { + filter := fmt.Sprintf("outbound and ip.DstAddr == %s", dstIP.String()) + h, err := windivert.Open(filter, windivert.LayerNetwork, windivert.PriorityLowest, uint64(windivert.FlagSendOnly)) + if err != nil { + return nil, fmt.Errorf("rawpacket: WinDivert open: %w", err) + } + return &rawSendFD{h: h}, nil +} + +func (r *rawSendFD) send(packet []byte) error { + r.mu.Lock() + defer r.mu.Unlock() + if r.closed { + return fmt.Errorf("rawpacket: WinDivert sender closed") + } + var addr windivert.Address + addr.SetIPChecksum(true) + addr.SetTCPChecksum(true) + _, err := r.h.Send(packet, &addr) + if err != nil { + return fmt.Errorf("rawpacket: WinDivert send: %w", err) + } + return nil +} + +func (r *rawSendFD) close() error { + r.mu.Lock() + defer r.mu.Unlock() + if r.closed { + return nil + } + r.closed = true + return r.h.Close() +} diff --git a/transport/internet/finalmask/rawpacket/spoof_receiver.go b/transport/internet/finalmask/rawpacket/spoof_receiver.go new file mode 100644 index 000000000000..be547962e091 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_receiver.go @@ -0,0 +1,193 @@ +package rawpacket + +import ( + "fmt" + "net/netip" + + "golang.org/x/sys/unix" +) + +type rawRecvSocket struct { + fd int + buf []byte + closed bool + proto uint8 +} + +func newRawRecvSocket(domain, proto int, bufSize int) (*rawRecvSocket, error) { + fd, err := unix.Socket(domain, unix.SOCK_RAW, proto) + if err != nil { + return nil, fmt.Errorf("rawpacket: socket: %w", err) + } + if bufSize > 0 { + _ = unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_RCVBUF, bufSize) + } + // 1-second timeout for clean shutdown + tv := unix.Timeval{Sec: 1, Usec: 0} + _ = unix.SetsockoptTimeval(fd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv) + return &rawRecvSocket{fd: fd, buf: make([]byte, 65536)}, nil +} + +func (r *rawRecvSocket) recv() ([]byte, bool) { + n, _, err := unix.Recvfrom(r.fd, r.buf, 0) + if err != nil { + return nil, false + } + if n == 0 { + return nil, false + } + out := make([]byte, n) + copy(out, r.buf[:n]) + return out, true +} + +func (r *rawRecvSocket) close() { + if !r.closed { + r.closed = true + unix.Close(r.fd) + } +} + +type tcpReceiver struct { + raw *rawRecvSocket + cfg *SpoofReceiverConfig +} + +func newTCPReceiver(cfg *SpoofReceiverConfig) (*tcpReceiver, error) { + raw, err := newRawRecvSocket(unix.AF_INET, unix.IPPROTO_TCP, cfg.BufferSize) + if err != nil { + return nil, err + } + return &tcpReceiver{raw: raw, cfg: cfg}, nil +} + +func (r *tcpReceiver) Receive() ([]byte, netip.Addr, uint16, error) { + for { + pkt, ok := r.raw.recv() + if !ok { + continue + } + _, flags, payload, srcIP, _, srcPort, dstPort, ok := ParseRawTCPPacket(pkt) + if !ok || dstPort != r.cfg.ListenPort || flags&TCPFlagSyn == 0 { + continue + } + if r.cfg.PeerSpoofIP.IsValid() && srcIP != r.cfg.PeerSpoofIP { + continue + } + return payload, srcIP, srcPort, nil + } +} + +func (r *tcpReceiver) Close() error { + r.raw.close() + return nil +} + +type udpReceiver struct { + raw *rawRecvSocket + cfg *SpoofReceiverConfig +} + +func newUDPReceiver(cfg *SpoofReceiverConfig) (*udpReceiver, error) { + raw, err := newRawRecvSocket(unix.AF_INET, unix.IPPROTO_UDP, cfg.BufferSize) + if err != nil { + return nil, err + } + return &udpReceiver{raw: raw, cfg: cfg}, nil +} + +func (r *udpReceiver) Receive() ([]byte, netip.Addr, uint16, error) { + for { + pkt, ok := r.raw.recv() + if !ok { + continue + } + payload, srcPort, dstPort, ok := ParseUDPPacket(pkt) + if !ok || dstPort != r.cfg.ListenPort { + continue + } + srcIP, _, _ := ParseSrcIP(pkt, false) + if r.cfg.PeerSpoofIP.IsValid() && srcIP != r.cfg.PeerSpoofIP { + continue + } + return payload, srcIP, srcPort, nil + } +} + +func (r *udpReceiver) Close() error { + r.raw.close() + return nil +} + +type icmpReceiver struct { + raw *rawRecvSocket + cfg *SpoofReceiverConfig +} + +func newICMPReceiver(cfg *SpoofReceiverConfig) (*icmpReceiver, error) { + raw, err := newRawRecvSocket(unix.AF_INET, unix.IPPROTO_ICMP, cfg.BufferSize) + if err != nil { + return nil, err + } + return &icmpReceiver{raw: raw, cfg: cfg}, nil +} + +func (r *icmpReceiver) Receive() ([]byte, netip.Addr, uint16, error) { + for { + pkt, ok := r.raw.recv() + if !ok { + continue + } + id, _, payload, ok := ParseICMPv4Echo(pkt) + if !ok { + continue + } + srcIP, _, _ := ParseSrcIP(pkt, false) + if r.cfg.PeerSpoofIP.IsValid() && srcIP != r.cfg.PeerSpoofIP { + continue + } + return payload, srcIP, id, nil + } +} + +func (r *icmpReceiver) Close() error { + r.raw.close() + return nil +} + +type icmpv6Receiver struct { + raw *rawRecvSocket + cfg *SpoofReceiverConfig +} + +func newICMPv6Receiver(cfg *SpoofReceiverConfig) (*icmpv6Receiver, error) { + // Non-standard: protocol 58 on IPv4 (same as reference) + raw, err := newRawRecvSocket(unix.AF_INET, int(ProtocolICMPv6), cfg.BufferSize) + if err != nil { + return nil, err + } + return &icmpv6Receiver{raw: raw, cfg: cfg}, nil +} + +func (r *icmpv6Receiver) Receive() ([]byte, netip.Addr, uint16, error) { + for { + pkt, ok := r.raw.recv() + if !ok { + continue + } + id, _, payload, ok := ParseICMPv6Echo(pkt) + if !ok { + continue + } + srcIP, _, _ := ParseSrcIP(pkt, false) + if r.cfg.PeerSpoofIP.IsValid() && srcIP != r.cfg.PeerSpoofIP { + continue + } + return payload, srcIP, id, nil + } +} + +func (r *icmpv6Receiver) Close() error { + r.raw.close() + return nil +} diff --git a/transport/internet/finalmask/rawpacket/spoof_relay.go b/transport/internet/finalmask/rawpacket/spoof_relay.go new file mode 100644 index 000000000000..4312e5f18cc8 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_relay.go @@ -0,0 +1,239 @@ +package rawpacket + +import ( + "io" + "net" + "net/netip" + "sync" + "time" +) + +type Relay struct { + cfg *RelayConfig + recver SpoofReceiver + sender SpoofSender + done chan struct{} + closeOnce sync.Once + icmpSuppressed bool + + // UDP forwarding (reference mode) + targetUDPConn *net.UDPConn + fwdUDPAddr *net.UDPAddr + + // TCP forwarding (Xray mode) + man *SessionManager +} + +func NewRelay(cfg *RelayConfig) (*Relay, error) { + if cfg.SendTransport == "" { + cfg.SendTransport = "udp" + } + if cfg.RecvTransport == "" { + cfg.RecvTransport = "tcp" + } + + if cfg.RecvTransport == "icmp" || cfg.RecvTransport == "icmpv6" { + if suppressICMPEchoReply() { + cfg.icmpSuppressed = true + } + } + + var spoofIPs []netip.Addr + if len(cfg.SpoofIPs) > 0 { + spoofIPs, _ = ParseIPs(cfg.SpoofIPs) + } + if len(spoofIPs) == 0 && cfg.SpoofIP.IsValid() { + spoofIPs = []netip.Addr{cfg.SpoofIP} + } + if len(spoofIPs) == 0 { + spoofIPs = []netip.Addr{netip.MustParseAddr("127.0.0.1")} + } + + recver, err := NewReceiver(cfg.RecvTransport, &SpoofReceiverConfig{ + ListenPort: cfg.ListenPort, + PeerSpoofIP: cfg.PeerSpoofIP, + BufferSize: 4 * 1024 * 1024, + }) + if err != nil { + if cfg.icmpSuppressed { + restoreICMPEchoReply() + } + return nil, err + } + + sender, err := NewSender(cfg.SendTransport, &SpoofSenderConfig{ + SourceIPs: spoofIPs, + SourcePort: cfg.SpoofPort, + TTL: 64, + }) + if err != nil { + recver.Close() + if cfg.icmpSuppressed { + restoreICMPEchoReply() + } + return nil, err + } + + r := &Relay{ + cfg: cfg, + recver: recver, + sender: sender, + done: make(chan struct{}), + icmpSuppressed: cfg.icmpSuppressed, + } + + if cfg.ForwardTransport == "tcp" { + // TCP mode: create session manager for Xray integration + r.man = NewSessionManager() + } else { + // UDP mode: match reference behavior + fwdAddr, err := net.ResolveUDPAddr("udp4", cfg.ForwardAddr) + if err != nil { + recver.Close() + sender.Close() + if cfg.icmpSuppressed { + restoreICMPEchoReply() + } + return nil, err + } + udpConn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) + if err != nil { + recver.Close() + sender.Close() + if cfg.icmpSuppressed { + restoreICMPEchoReply() + } + return nil, err + } + r.targetUDPConn = udpConn + r.fwdUDPAddr = fwdAddr + } + + return r, nil +} + +func (r *Relay) Run() { + go r.uplinkLoop() + if r.man != nil { + go r.forwardResponses() + } else { + go r.downlinkLoop() + } + <-r.done +} + +func (r *Relay) uplinkLoop() { + for { + select { + case <-r.done: + return + default: + } + + data, srcIP, srcPort, err := r.recver.Receive() + if err != nil { + return + } + if len(data) == 0 { + continue + } + + if r.man != nil { + // TCP mode: forward to target via TCP with session + r.handleTCPForward(data, srcIP, srcPort) + } else { + // UDP mode: forward to target via UDP (reference behavior) + if _, err := r.targetUDPConn.WriteToUDP(data, r.fwdUDPAddr); err != nil { + continue + } + } + } +} + +func (r *Relay) handleTCPForward(data []byte, srcIP netip.Addr, srcPort uint16) { + session := r.man.Get(srcIP, srcPort) + if session == nil { + targetConn, err := net.DialTimeout("tcp", r.cfg.ForwardAddr, 10*time.Second) + if err != nil { + return + } + session = r.man.Add(srcIP, srcPort, targetConn, r.cfg.ClientIP) + } + session.mu.Lock() + defer session.mu.Unlock() + if !session.closed { + session.TargetConn.SetWriteDeadline(time.Now().Add(10 * time.Second)) + _, _ = session.TargetConn.Write(data) + } +} + +func (r *Relay) downlinkLoop() { + buf := make([]byte, 65536) + for { + select { + case <-r.done: + return + default: + } + + n, _, err := r.targetUDPConn.ReadFromUDP(buf) + if err != nil { + continue + } + if n == 0 { + continue + } + _ = r.sender.Send(buf[:n], r.cfg.ClientIP, r.cfg.ClientPort) + } +} + +func (r *Relay) forwardResponses() { + for { + select { + case <-r.done: + return + default: + } + + for _, s := range r.man.All() { + s.mu.Lock() + if s.closed { + s.mu.Unlock() + continue + } + buf := make([]byte, 65536) + s.TargetConn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + n, err := s.TargetConn.Read(buf) + if err != nil { + if err == io.EOF { + s.closed = true + s.TargetConn.Close() + } + s.mu.Unlock() + continue + } + s.mu.Unlock() + if n > 0 { + _ = r.sender.Send(buf[:n], s.ClientIP, s.ClientPort) + } + } + time.Sleep(50 * time.Millisecond) + } +} + +func (r *Relay) Close() { + r.closeOnce.Do(func() { + close(r.done) + r.recver.Close() + r.sender.Close() + if r.targetUDPConn != nil { + r.targetUDPConn.Close() + } + if r.man != nil { + r.man.Close() + } + if r.icmpSuppressed { + restoreICMPEchoReply() + } + }) +} diff --git a/transport/internet/finalmask/rawpacket/spoof_sender.go b/transport/internet/finalmask/rawpacket/spoof_sender.go new file mode 100644 index 000000000000..c5b652f2485e --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_sender.go @@ -0,0 +1,187 @@ +package rawpacket + +import ( + "math/rand" + "net/netip" + "sync" +) + +type tcpSender struct { + srcIPs []netip.Addr + rotator *SourceIPRotator + srcPort uint16 + ttl uint8 + seqNum uint32 + seqMu sync.Mutex + fd *rawSendFD +} + +func newTCPSender(cfg *SpoofSenderConfig) (*tcpSender, error) { + ips := cfg.SourceIPs + if len(ips) == 0 { + ips = []netip.Addr{cfg.SourceIP} + } + fd, err := openRawSender(ips[0]) + if err != nil { + return nil, err + } + return &tcpSender{ + srcIPs: ips, + rotator: NewSourceIPRotator(ips), + srcPort: cfg.SourcePort, + ttl: cfg.TTL, + seqNum: uint32(rand.Int63n(1 << 31)), + fd: fd, + }, nil +} + +func (s *tcpSender) Send(payload []byte, dstIP netip.Addr, dstPort uint16) error { + s.seqMu.Lock() + seq := s.seqNum + s.seqNum += uint32(len(payload)) + s.seqMu.Unlock() + + spoofIP := s.rotator.Next() + pkt := BuildTCPSYN(spoofIP, dstIP, s.srcPort, dstPort, seq, payload, s.ttl) + return s.fd.send(pkt) +} + +func (s *tcpSender) Close() error { + if s.fd != nil { + s.fd.close() + } + return nil +} + +type udpSender struct { + srcIPs []netip.Addr + rotator *SourceIPRotator + srcPort uint16 + ttl uint8 + fd *rawSendFD +} + +func newUDPSender(cfg *SpoofSenderConfig) (*udpSender, error) { + ips := cfg.SourceIPs + if len(ips) == 0 { + ips = []netip.Addr{cfg.SourceIP} + } + fd, err := openRawSender(ips[0]) + if err != nil { + return nil, err + } + return &udpSender{ + srcIPs: ips, + rotator: NewSourceIPRotator(ips), + srcPort: cfg.SourcePort, + ttl: cfg.TTL, + fd: fd, + }, nil +} + +func (s *udpSender) Send(payload []byte, dstIP netip.Addr, dstPort uint16) error { + spoofIP := s.rotator.Next() + pkt := BuildRawUDP(spoofIP, dstIP, s.srcPort, dstPort, payload, s.ttl) + return s.fd.send(pkt) +} + +func (s *udpSender) Close() error { + if s.fd != nil { + s.fd.close() + } + return nil +} + +type icmpSender struct { + srcIPs []netip.Addr + rotator *SourceIPRotator + id uint16 + seq uint16 + ttl uint8 + seqMu sync.Mutex + fd *rawSendFD +} + +func newICMPSender(cfg *SpoofSenderConfig) (*icmpSender, error) { + ips := cfg.SourceIPs + if len(ips) == 0 { + ips = []netip.Addr{cfg.SourceIP} + } + fd, err := openRawSender(ips[0]) + if err != nil { + return nil, err + } + return &icmpSender{ + srcIPs: ips, + rotator: NewSourceIPRotator(ips), + id: cfg.SourcePort, + seq: 1, + ttl: cfg.TTL, + fd: fd, + }, nil +} + +func (s *icmpSender) Send(payload []byte, dstIP netip.Addr, dstPort uint16) error { + s.seqMu.Lock() + seq := s.seq + s.seq++ + s.seqMu.Unlock() + + spoofIP := s.rotator.Next() + pkt := BuildICMPv4Echo(spoofIP, dstIP, s.id, seq, payload, s.ttl) + return s.fd.send(pkt) +} + +func (s *icmpSender) Close() error { + if s.fd != nil { + s.fd.close() + } + return nil +} + +type icmpv6Sender struct { + srcIPs []netip.Addr + rotator *SourceIPRotator + id uint16 + seq uint16 + ttl uint8 + seqMu sync.Mutex + fd *rawSendFD +} + +func newICMPv6Sender(cfg *SpoofSenderConfig) (*icmpv6Sender, error) { + ips := cfg.SourceIPs + if len(ips) == 0 { + ips = []netip.Addr{cfg.SourceIP} + } + fd, err := openRawSender(ips[0]) + if err != nil { + return nil, err + } + return &icmpv6Sender{ + srcIPs: ips, + rotator: NewSourceIPRotator(ips), + id: cfg.SourcePort, + seq: 1, + ttl: cfg.TTL, + fd: fd, + }, nil +} + +func (s *icmpv6Sender) Send(payload []byte, dstIP netip.Addr, dstPort uint16) error { + s.seqMu.Lock() + seq := s.seq + s.seq++ + s.seqMu.Unlock() + + spoofIP := s.rotator.Next() + pkt := BuildICMPv6Echo(spoofIP, dstIP, s.id, seq, payload, s.ttl) + return s.fd.send(pkt) +} + +func (s *icmpv6Sender) Close() error { + if s.fd != nil { + s.fd.close() + } + return nil +} diff --git a/transport/internet/finalmask/rawpacket/spoof_session.go b/transport/internet/finalmask/rawpacket/spoof_session.go new file mode 100644 index 000000000000..03349401def4 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_session.go @@ -0,0 +1,99 @@ +package rawpacket + +import ( + "net" + "net/netip" + "sync" + "time" +) + +type RelaySession struct { + ClientIP netip.Addr + ClientPort uint16 + ServerAddr netip.Addr + TargetConn net.Conn + LastSeen time.Time + mu sync.Mutex + closed bool +} + +type SessionManager struct { + sessions map[sessionKey]*RelaySession + mu sync.Mutex +} + +type sessionKey struct { + ip [16]byte + port uint16 +} + +func addrToKey(ip netip.Addr) [16]byte { + var out [16]byte + b := ip.As16() + copy(out[:], b[:]) + return out +} + +func NewSessionManager() *SessionManager { + return &SessionManager{ + sessions: make(map[sessionKey]*RelaySession), + } +} + +func (sm *SessionManager) Add(clientIP netip.Addr, clientPort uint16, targetConn net.Conn, serverAddr netip.Addr) *RelaySession { + key := sessionKey{ip: addrToKey(clientIP), port: clientPort} + sm.mu.Lock() + defer sm.mu.Unlock() + s := &RelaySession{ + ClientIP: clientIP, + ClientPort: clientPort, + ServerAddr: serverAddr, + TargetConn: targetConn, + LastSeen: time.Now(), + } + sm.sessions[key] = s + return s +} + +func (sm *SessionManager) Get(clientIP netip.Addr, clientPort uint16) *RelaySession { + key := sessionKey{ip: addrToKey(clientIP), port: clientPort} + sm.mu.Lock() + defer sm.mu.Unlock() + s, ok := sm.sessions[key] + if !ok { + return nil + } + s.LastSeen = time.Now() + return s +} + +func (sm *SessionManager) Remove(clientIP netip.Addr, clientPort uint16) { + key := sessionKey{ip: addrToKey(clientIP), port: clientPort} + sm.mu.Lock() + defer sm.mu.Unlock() + if s, ok := sm.sessions[key]; ok { + s.closed = true + s.TargetConn.Close() + delete(sm.sessions, key) + } +} + +func (sm *SessionManager) All() []*RelaySession { + sm.mu.Lock() + defer sm.mu.Unlock() + out := make([]*RelaySession, 0, len(sm.sessions)) + for _, s := range sm.sessions { + out = append(out, s) + } + return out +} + +func (sm *SessionManager) Close() { + sm.mu.Lock() + defer sm.mu.Unlock() + for _, s := range sm.sessions { + s.closed = true + s.TargetConn.Close() + } + clear(sm.sessions) +} diff --git a/transport/internet/finalmask/rawpacket/spoof_source_ip.go b/transport/internet/finalmask/rawpacket/spoof_source_ip.go new file mode 100644 index 000000000000..58a232a6093f --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_source_ip.go @@ -0,0 +1,33 @@ +package rawpacket + +import ( + "net/netip" + "sync/atomic" +) + +type SourceIPRotator struct { + ips []netip.Addr + next atomic.Uint64 +} + +func NewSourceIPRotator(ips []netip.Addr) *SourceIPRotator { + if len(ips) == 0 { + return nil + } + return &SourceIPRotator{ips: ips} +} + +func (r *SourceIPRotator) Next() netip.Addr { + if r == nil || len(r.ips) == 0 { + return netip.Addr{} + } + i := r.next.Add(1) - 1 + return r.ips[i%uint64(len(r.ips))] +} + +func (r *SourceIPRotator) Len() int { + if r == nil { + return 0 + } + return len(r.ips) +} diff --git a/transport/internet/finalmask/rawpacket/spoof_tcp.go b/transport/internet/finalmask/rawpacket/spoof_tcp.go new file mode 100644 index 000000000000..8224e12e3111 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_tcp.go @@ -0,0 +1,224 @@ +package rawpacket + +import ( + "encoding/binary" + "math/rand" + "net/netip" +) + +func BuildTCPSYN(srcIP, dstIP netip.Addr, srcPort, dstPort uint16, seqNum uint32, payload []byte, ttl uint8) []byte { + ipHdrLen := 20 + tcpHdrLen := 20 + totalLen := ipHdrLen + tcpHdrLen + len(payload) + + frame := make([]byte, totalLen) + ip := BuildIPv4Header(uint16(totalLen), uint16(rand.Intn(65535)), ttl, 6, srcIP, dstIP) + copy(frame, ip) + + tcp := frame[ipHdrLen:] + binary.BigEndian.PutUint16(tcp[0:], srcPort) + binary.BigEndian.PutUint16(tcp[2:], dstPort) + binary.BigEndian.PutUint32(tcp[4:], seqNum) + binary.BigEndian.PutUint32(tcp[8:], 0) + tcp[12] = byte((tcpHdrLen / 4) << 4) + tcp[13] = TCPFlagSyn + binary.BigEndian.PutUint16(tcp[14:], 65535) + + if len(payload) > 0 { + copy(frame[ipHdrLen+tcpHdrLen:], payload) + } + + pseudo := IPv4PseudoHeaderChecksum(srcIP, dstIP, 6, uint16(tcpHdrLen+len(payload))) + csum := Checksum(tcp[:tcpHdrLen+len(payload)], pseudo) + binary.BigEndian.PutUint16(tcp[16:], ^csum) + + return frame +} + +func BuildICMPv4Echo(srcIP, dstIP netip.Addr, id, seq uint16, payload []byte, ttl uint8) []byte { + totalLen := 20 + 8 + len(payload) + frame := make([]byte, totalLen) + ip := BuildIPv4Header(uint16(totalLen), uint16(rand.Intn(65535)), ttl, 1, srcIP, dstIP) + copy(frame, ip) + + icmp := frame[20:] + icmp[0] = 8 // Echo Request + icmp[1] = 0 + binary.BigEndian.PutUint16(icmp[4:], id) + binary.BigEndian.PutUint16(icmp[6:], seq) + copy(icmp[8:], payload) + + // ICMP checksum covers ICMP header + payload + csum := Checksum(icmp[:8+len(payload)], 0) + binary.BigEndian.PutUint16(icmp[2:], ^csum) + return frame +} + +func BuildICMPv6Echo(srcIP, dstIP netip.Addr, id, seq uint16, payload []byte, ttl uint8) []byte { + // Non-standard: ICMPv6 Echo Request (type 128) over IPv4 header with protocol 58. + icmpLen := 8 + len(payload) + totalLen := 20 + icmpLen + frame := make([]byte, totalLen) + ip := BuildIPv4Header(uint16(totalLen), uint16(rand.Intn(65535)), ttl, 58, srcIP, dstIP) + copy(frame, ip) + + icmp := frame[20:] + icmp[0] = 128 // Echo Request + icmp[1] = 0 + binary.BigEndian.PutUint16(icmp[4:], id) + binary.BigEndian.PutUint16(icmp[6:], seq) + copy(icmp[8:], payload) + + // ICMPv6 checksum with IPv4 pseudo-header (protocol 58) + pseudo := IPv4PseudoHeaderChecksum(srcIP, dstIP, 58, uint16(icmpLen)) + csum := Checksum(icmp[:icmpLen], pseudo) + binary.BigEndian.PutUint16(icmp[2:], ^csum) + return frame +} + +func BuildRawUDP(srcIP, dstIP netip.Addr, srcPort, dstPort uint16, payload []byte, ttl uint8) []byte { + ipHdrLen := 20 + udpHdrLen := 8 + totalLen := ipHdrLen + udpHdrLen + len(payload) + + frame := make([]byte, totalLen) + ip := BuildIPv4Header(uint16(totalLen), uint16(rand.Intn(65535)), ttl, 17, srcIP, dstIP) + copy(frame, ip) + + udp := frame[ipHdrLen:] + binary.BigEndian.PutUint16(udp[0:], srcPort) + binary.BigEndian.PutUint16(udp[2:], dstPort) + udpLen := uint16(udpHdrLen + len(payload)) + binary.BigEndian.PutUint16(udp[4:], udpLen) + binary.BigEndian.PutUint16(udp[6:], 0) // checksum = 0 (optional in IPv4) + + if len(payload) > 0 { + copy(frame[ipHdrLen+udpHdrLen:], payload) + } + + return frame +} + +func ParseSrcIP(buf []byte, isV6 bool) (netip.Addr, netip.Addr, bool) { + if len(buf) < 20 { + return netip.Addr{}, netip.Addr{}, false + } + if buf[0]>>4 != 4 { + return netip.Addr{}, netip.Addr{}, false + } + src, _ := netip.AddrFromSlice(buf[12:16]) + dst, _ := netip.AddrFromSlice(buf[16:20]) + return src, dst, true +} + +func ParseRawTCPPacket(buf []byte) (seq uint32, flags uint8, payload []byte, srcIP netip.Addr, dstIP netip.Addr, srcPort, dstPort uint16, ok bool) { + if len(buf) < 40 { + return + } + if buf[0]>>4 != 4 { + return + } + ihl := (buf[0] & 0x0f) * 4 + if int(ihl) < 20 || int(ihl)+20 > len(buf) { + return + } + if buf[9] != 6 { + return + } + srcIP, _ = netip.AddrFromSlice(buf[12:16]) + dstIP, _ = netip.AddrFromSlice(buf[16:20]) + tcp := buf[ihl:] + seq = binary.BigEndian.Uint32(tcp[4:]) + flags = tcp[13] + srcPort = binary.BigEndian.Uint16(tcp[0:]) + dstPort = binary.BigEndian.Uint16(tcp[2:]) + do := int((tcp[12] >> 4) * 4) + ihlInt := int(ihl) + if do < 20 || ihlInt+do > len(buf) { + return + } + payload = buf[ihlInt+do:] + ok = true + return +} + +func ParseUDPPacket(buf []byte) (payload []byte, srcPort, dstPort uint16, ok bool) { + if len(buf) < 28 { + return + } + ihl := (buf[0] & 0x0f) * 4 + if int(ihl) < 20 { + return + } + if buf[9] != 17 { + return + } + udp := buf[ihl:] + srcPort = binary.BigEndian.Uint16(udp[0:]) + dstPort = binary.BigEndian.Uint16(udp[2:]) + udpLen := int(binary.BigEndian.Uint16(udp[4:])) + if udpLen < 8 || int(ihl)+udpLen > len(buf) { + return + } + payload = udp[8:udpLen] + ok = true + return +} + +func ParseICMPv4Echo(buf []byte) (id, seq uint16, payload []byte, ok bool) { + if len(buf) < 28 { + return + } + ihl := (buf[0] & 0x0f) * 4 + if int(ihl) < 20 { + return + } + if buf[9] != 1 { + return + } + icmp := buf[ihl:] + if len(icmp) < 8 { + return + } + if icmp[0] != 8 { + return + } + id = binary.BigEndian.Uint16(icmp[4:]) + seq = binary.BigEndian.Uint16(icmp[6:]) + payload = icmp[8:] + ok = true + return +} + +func ParseICMPv6Echo(buf []byte) (id, seq uint16, payload []byte, ok bool) { + if len(buf) < 28 { + return + } + var hdrLen int + switch buf[0] >> 4 { + case 4: + if buf[9] != 58 { + return + } + hdrLen = int(buf[0]&0x0f) * 4 + case 6: + if buf[6] != 58 { + return + } + hdrLen = 40 + default: + return + } + if hdrLen < 20 || len(buf) < hdrLen+8 { + return + } + icmp := buf[hdrLen:] + if icmp[0] != 128 { + return + } + id = binary.BigEndian.Uint16(icmp[4:]) + seq = binary.BigEndian.Uint16(icmp[6:]) + payload = icmp[8:] + ok = true + return +} diff --git a/transport/internet/finalmask/rawpacket/spoof_transport.go b/transport/internet/finalmask/rawpacket/spoof_transport.go new file mode 100644 index 000000000000..69a4298b81ed --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_transport.go @@ -0,0 +1,54 @@ +package rawpacket + +import "net/netip" + +type SpoofSender interface { + Send(payload []byte, dstIP netip.Addr, dstPort uint16) error + Close() error +} + +type SpoofReceiver interface { + Receive() (payload []byte, srcIP netip.Addr, srcPort uint16, err error) + Close() error +} + +func NewSender(transport string, cfg *SpoofSenderConfig) (SpoofSender, error) { + switch transport { + case "tcp", "": + return newTCPSender(cfg) + case "udp": + return newUDPSender(cfg) + case "icmp": + return newICMPSender(cfg) + case "icmpv6": + return newICMPv6Sender(cfg) + } + return nil, nil +} + +func NewReceiver(transport string, cfg *SpoofReceiverConfig) (SpoofReceiver, error) { + switch transport { + case "tcp", "": + return newTCPReceiver(cfg) + case "udp": + return newUDPReceiver(cfg) + case "icmp": + return newICMPReceiver(cfg) + case "icmpv6": + return newICMPv6Receiver(cfg) + } + return nil, nil +} + +type SpoofSenderConfig struct { + SourceIP netip.Addr + SourceIPs []netip.Addr + SourcePort uint16 + TTL uint8 +} + +type SpoofReceiverConfig struct { + ListenPort uint16 + PeerSpoofIP netip.Addr + BufferSize int +} From cd7f8e6762d6418da3d6f4f0b56170c28d420190 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Fri, 5 Jun 2026 20:50:21 +0600 Subject: [PATCH 37/42] tls: remove cmdFakeHello (old ClientHello spoofer) --- main/commands/all/tls/tls.go | 1 - 1 file changed, 1 deletion(-) diff --git a/main/commands/all/tls/tls.go b/main/commands/all/tls/tls.go index 27bc4e8c373c..17a9465a7851 100644 --- a/main/commands/all/tls/tls.go +++ b/main/commands/all/tls/tls.go @@ -15,6 +15,5 @@ var CmdTLS = &base.Command{ cmdPing, cmdHash, cmdECH, - cmdFakeHello, }, } From 2d7aa77c4dc360edc91ec376cd514c7d8441f6f1 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Fri, 5 Jun 2026 20:52:19 +0600 Subject: [PATCH 38/42] Delete fakehello.go --- main/commands/all/tls/fakehello.go | 48 ------------------------------ 1 file changed, 48 deletions(-) delete mode 100644 main/commands/all/tls/fakehello.go diff --git a/main/commands/all/tls/fakehello.go b/main/commands/all/tls/fakehello.go deleted file mode 100644 index d7d84bca6041..000000000000 --- a/main/commands/all/tls/fakehello.go +++ /dev/null @@ -1,48 +0,0 @@ -package tls - -import ( - "encoding/base64" - "fmt" - "os" - - "github.com/xtls/xray-core/main/commands/base" - "github.com/xtls/xray-core/transport/internet/finalmask/rawpacket" -) - -var cmdFakeHello = &base.Command{ - UsageLine: "{{.Exec}} tls fake-hello [-hex] ", - Short: "Generate a fake TLS ClientHello payload for rawpacket", - Long: ` -Generate a fake TLS ClientHello for use in finalmask rawpacket settings. - -Arguments: - - -base64 - Output base64-encoded payload (default). - -hex - Output hex-encoded payload instead of base64. -`, -} - -func init() { - cmdFakeHello.Run = executeFakeHello -} - -var fakeHelloHex = cmdFakeHello.Flag.Bool("hex", false, "") - -func executeFakeHello(cmd *base.Command, args []string) { - if cmdFakeHello.Flag.NArg() < 1 { - base.Fatalf("sni not specified") - } - sni := cmdFakeHello.Flag.Arg(0) - payload, err := rawpacket.BuildFakeClientHello(sni) - if err != nil { - base.Fatalf("failed to build ClientHello: %s", err) - } - switch { - case *fakeHelloHex: - fmt.Fprintf(os.Stdout, "%x\n", payload) - default: - fmt.Fprintln(os.Stdout, base64.StdEncoding.EncodeToString(payload)) - } -} From 482fba5646b03509e6565726543d9eda14fee875 Mon Sep 17 00:00:00 2001 From: Tamim Hossain Date: Wed, 19 Aug 2026 09:24:31 +0600 Subject: [PATCH 39/42] rawpacket: TCP sim, TLS spoof, masquerade --- infra/conf/transport_finalmask.go | 6 + main/commands/all/tls/fakehello.go | 48 + main/commands/all/tls/tls.go | 1 + .../finalmask/rawpacket/client_hello.go | 51 + .../internet/finalmask/rawpacket/config.go | 26 +- .../internet/finalmask/rawpacket/config.pb.go | 45 +- .../internet/finalmask/rawpacket/config.proto | 14 + .../internet/finalmask/rawpacket/conn.go | 30 +- .../internet/finalmask/rawpacket/frame.go | 323 +++++ .../finalmask/rawpacket/frame_demux.go | 141 ++ .../finalmask/rawpacket/frame_test.go | 283 ++++ .../finalmask/rawpacket/masquerade.go | 405 ++++++ .../finalmask/rawpacket/masquerade_test.go | 301 +++++ .../finalmask/rawpacket/spoof_bpf_linux.go | 53 + .../finalmask/rawpacket/spoof_bpf_stub.go | 7 + .../finalmask/rawpacket/spoof_conn.go | 215 ++- .../internet/finalmask/rawpacket/spoof_ip.go | 14 +- .../finalmask/rawpacket/spoof_rawsend.go | 46 +- .../finalmask/rawpacket/spoof_rawsend_stub.go | 8 + .../rawpacket/spoof_rawsend_windows.go | 22 +- .../finalmask/rawpacket/spoof_receiver.go | 105 +- .../finalmask/rawpacket/spoof_recv_stub.go | 21 + .../finalmask/rawpacket/spoof_recv_unix.go | 63 + .../finalmask/rawpacket/spoof_recv_windows.go | 59 + .../finalmask/rawpacket/spoof_relay.go | 312 +++-- .../finalmask/rawpacket/spoof_rst_linux.go | 42 + .../finalmask/rawpacket/spoof_rst_stub.go | 17 + .../finalmask/rawpacket/spoof_sender.go | 136 +- .../finalmask/rawpacket/spoof_session.go | 48 +- .../internet/finalmask/rawpacket/spoof_tcp.go | 64 +- .../finalmask/rawpacket/spoof_tcp_test.go | 155 +++ .../finalmask/rawpacket/spoof_transport.go | 19 +- .../internet/finalmask/rawpacket/tcp_state.go | 111 ++ .../internet/finalmask/rawpacket/tcpip.go | 4 +- .../finalmask/rawpacket/windivert/filter.go | 9 + transport/internet/grpc/dial.go | 9 + transport/internet/httpupgrade/dialer.go | 6 + transport/internet/kcp/dialer.go | 9 +- transport/internet/splithttp/dialer.go | 6 + transport/internet/tcp/dialer.go | 10 + transport/internet/tls/config.pb.go | 44 +- transport/internet/tls/config.proto | 12 + transport/internet/tls/tls.go | 33 + .../internet/tls/tlsspoof/client_hello.go | 75 ++ transport/internet/tls/tlsspoof/endpoints.go | 27 + transport/internet/tls/tlsspoof/packet.go | 163 +++ transport/internet/tls/tlsspoof/raw_darwin.go | 198 +++ .../internet/tls/tlsspoof/raw_freebsd.go | 172 +++ transport/internet/tls/tlsspoof/raw_linux.go | 166 +++ transport/internet/tls/tlsspoof/raw_stub.go | 15 + transport/internet/tls/tlsspoof/raw_unix.go | 24 + .../internet/tls/tlsspoof/raw_windows.go | 234 ++++ transport/internet/tls/tlsspoof/spoof.go | 182 +++ .../tls/tlsspoof/spoof_freebsd_test.go | 82 ++ transport/internet/tls/tlsspoof/spoof_test.go | 111 ++ transport/internet/tls/tlsspoof/tcpip.go | 155 +++ .../tls/tlsspoof/windivert/assets/LICENSE.txt | 1191 +++++++++++++++++ .../tlsspoof/windivert/assets/WinDivert32.sys | Bin 0 -> 79792 bytes .../tlsspoof/windivert/assets/WinDivert64.sys | Bin 0 -> 94144 bytes .../tls/tlsspoof/windivert/assets_386.go | 14 + .../tls/tlsspoof/windivert/assets_amd64.go | 14 + .../tlsspoof/windivert/assets_unsupported.go | 7 + .../tls/tlsspoof/windivert/driver_windows.go | 211 +++ .../internet/tls/tlsspoof/windivert/filter.go | 181 +++ .../tls/tlsspoof/windivert/handle_windows.go | 323 +++++ .../tls/tlsspoof/windivert/windivert.go | 78 ++ transport/internet/websocket/dialer.go | 8 + 67 files changed, 6609 insertions(+), 355 deletions(-) create mode 100644 main/commands/all/tls/fakehello.go create mode 100644 transport/internet/finalmask/rawpacket/client_hello.go create mode 100644 transport/internet/finalmask/rawpacket/frame.go create mode 100644 transport/internet/finalmask/rawpacket/frame_demux.go create mode 100644 transport/internet/finalmask/rawpacket/frame_test.go create mode 100644 transport/internet/finalmask/rawpacket/masquerade.go create mode 100644 transport/internet/finalmask/rawpacket/masquerade_test.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_bpf_linux.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_bpf_stub.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_recv_stub.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_recv_unix.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_recv_windows.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_rst_linux.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_rst_stub.go create mode 100644 transport/internet/finalmask/rawpacket/spoof_tcp_test.go create mode 100644 transport/internet/finalmask/rawpacket/tcp_state.go create mode 100644 transport/internet/tls/tlsspoof/client_hello.go create mode 100644 transport/internet/tls/tlsspoof/endpoints.go create mode 100644 transport/internet/tls/tlsspoof/packet.go create mode 100644 transport/internet/tls/tlsspoof/raw_darwin.go create mode 100644 transport/internet/tls/tlsspoof/raw_freebsd.go create mode 100644 transport/internet/tls/tlsspoof/raw_linux.go create mode 100644 transport/internet/tls/tlsspoof/raw_stub.go create mode 100644 transport/internet/tls/tlsspoof/raw_unix.go create mode 100644 transport/internet/tls/tlsspoof/raw_windows.go create mode 100644 transport/internet/tls/tlsspoof/spoof.go create mode 100644 transport/internet/tls/tlsspoof/spoof_freebsd_test.go create mode 100644 transport/internet/tls/tlsspoof/spoof_test.go create mode 100644 transport/internet/tls/tlsspoof/tcpip.go create mode 100644 transport/internet/tls/tlsspoof/windivert/assets/LICENSE.txt create mode 100644 transport/internet/tls/tlsspoof/windivert/assets/WinDivert32.sys create mode 100644 transport/internet/tls/tlsspoof/windivert/assets/WinDivert64.sys create mode 100644 transport/internet/tls/tlsspoof/windivert/assets_386.go create mode 100644 transport/internet/tls/tlsspoof/windivert/assets_amd64.go create mode 100644 transport/internet/tls/tlsspoof/windivert/assets_unsupported.go create mode 100644 transport/internet/tls/tlsspoof/windivert/driver_windows.go create mode 100644 transport/internet/tls/tlsspoof/windivert/filter.go create mode 100644 transport/internet/tls/tlsspoof/windivert/handle_windows.go create mode 100644 transport/internet/tls/tlsspoof/windivert/windivert.go diff --git a/infra/conf/transport_finalmask.go b/infra/conf/transport_finalmask.go index f6daf471fe03..959b80f634d2 100644 --- a/infra/conf/transport_finalmask.go +++ b/infra/conf/transport_finalmask.go @@ -251,6 +251,9 @@ type RawpacketMask struct { ClientPort uint16 `json:"clientPort"` PeerSpoofIP string `json:"peerSpoofIP"` SpoofPort uint16 `json:"spoofPort"` + Auth string `json:"auth"` + SuppressRst bool `json:"suppressRst"` + Masquerade string `json:"masquerade"` } func (c *RawpacketMask) Build() (proto.Message, error) { @@ -272,6 +275,9 @@ func (c *RawpacketMask) Build() (proto.Message, error) { ClientPort: uint32(c.ClientPort), PeerSpoofIp: c.PeerSpoofIP, SpoofPort: uint32(c.SpoofPort), + Auth: c.Auth, + SuppressRst: c.SuppressRst, + Masquerade: c.Masquerade, } return config, nil } diff --git a/main/commands/all/tls/fakehello.go b/main/commands/all/tls/fakehello.go new file mode 100644 index 000000000000..d7d84bca6041 --- /dev/null +++ b/main/commands/all/tls/fakehello.go @@ -0,0 +1,48 @@ +package tls + +import ( + "encoding/base64" + "fmt" + "os" + + "github.com/xtls/xray-core/main/commands/base" + "github.com/xtls/xray-core/transport/internet/finalmask/rawpacket" +) + +var cmdFakeHello = &base.Command{ + UsageLine: "{{.Exec}} tls fake-hello [-hex] ", + Short: "Generate a fake TLS ClientHello payload for rawpacket", + Long: ` +Generate a fake TLS ClientHello for use in finalmask rawpacket settings. + +Arguments: + + -base64 + Output base64-encoded payload (default). + -hex + Output hex-encoded payload instead of base64. +`, +} + +func init() { + cmdFakeHello.Run = executeFakeHello +} + +var fakeHelloHex = cmdFakeHello.Flag.Bool("hex", false, "") + +func executeFakeHello(cmd *base.Command, args []string) { + if cmdFakeHello.Flag.NArg() < 1 { + base.Fatalf("sni not specified") + } + sni := cmdFakeHello.Flag.Arg(0) + payload, err := rawpacket.BuildFakeClientHello(sni) + if err != nil { + base.Fatalf("failed to build ClientHello: %s", err) + } + switch { + case *fakeHelloHex: + fmt.Fprintf(os.Stdout, "%x\n", payload) + default: + fmt.Fprintln(os.Stdout, base64.StdEncoding.EncodeToString(payload)) + } +} diff --git a/main/commands/all/tls/tls.go b/main/commands/all/tls/tls.go index 17a9465a7851..27bc4e8c373c 100644 --- a/main/commands/all/tls/tls.go +++ b/main/commands/all/tls/tls.go @@ -15,5 +15,6 @@ var CmdTLS = &base.Command{ cmdPing, cmdHash, cmdECH, + cmdFakeHello, }, } diff --git a/transport/internet/finalmask/rawpacket/client_hello.go b/transport/internet/finalmask/rawpacket/client_hello.go new file mode 100644 index 000000000000..04eec64584da --- /dev/null +++ b/transport/internet/finalmask/rawpacket/client_hello.go @@ -0,0 +1,51 @@ +package rawpacket + +import ( + "bytes" + "context" + "crypto/tls" + "errors" + "io" + "net" + "time" +) + +// BuildFakeClientHello drives crypto/tls against a write-only in-memory conn +// to capture a generated ClientHello. CurvePreferences pins classical groups +// to suppress Go's default X25519MLKEM768 hybrid key share; without this the +// post-quantum public key alone (~1184 bytes) pushes the record past one MSS, +// and middleboxes do not reassemble fragmented ClientHellos. The handshake +// error is discarded because the stub conn's Read returns immediately. +func BuildFakeClientHello(sni string) ([]byte, error) { + if sni == "" { + return nil, errors.New("empty sni") + } + var buf bytes.Buffer + tlsConn := tls.Client(&writeOnlyConn{w: &buf}, &tls.Config{ + ServerName: sni, + // Order matches what browsers advertised before post-quantum. + CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP384}, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS13, + NextProtos: []string{"h2", "http/1.1"}, + InsecureSkipVerify: true, + }) + _ = tlsConn.HandshakeContext(context.Background()) + if buf.Len() == 0 { + return nil, errors.New("tls ClientHello not produced") + } + return buf.Bytes(), nil +} + +type writeOnlyConn struct { + w io.Writer +} + +func (c *writeOnlyConn) Read([]byte) (int, error) { return 0, io.EOF } +func (c *writeOnlyConn) Write(p []byte) (int, error) { return c.w.Write(p) } +func (c *writeOnlyConn) Close() error { return nil } +func (c *writeOnlyConn) LocalAddr() net.Addr { return nil } +func (c *writeOnlyConn) RemoteAddr() net.Addr { return nil } +func (c *writeOnlyConn) SetDeadline(time.Time) error { return nil } +func (c *writeOnlyConn) SetReadDeadline(time.Time) error { return nil } +func (c *writeOnlyConn) SetWriteDeadline(time.Time) error { return nil } diff --git a/transport/internet/finalmask/rawpacket/config.go b/transport/internet/finalmask/rawpacket/config.go index 0db2e10d28ee..a82e30ecb36a 100644 --- a/transport/internet/finalmask/rawpacket/config.go +++ b/transport/internet/finalmask/rawpacket/config.go @@ -41,16 +41,20 @@ func ParseIPs(ss []string) ([]netip.Addr, error) { } type RelayConfig struct { - ListenPort uint16 - ForwardAddr string + ListenPort uint16 + ForwardAddr string ForwardTransport string // "tcp" (Xray) or "udp" (reference, default) - ClientIP netip.Addr - ClientPort uint16 - SpoofIP netip.Addr // single fallback - SpoofIPs []string - SpoofPort uint16 - PeerSpoofIP netip.Addr - SendTransport string - RecvTransport string - icmpSuppressed bool + ClientIP netip.Addr + ClientPort uint16 + SpoofIP netip.Addr // single fallback + SpoofIPs []string + SpoofPort uint16 + PeerSpoofIP netip.Addr + SendTransport string + RecvTransport string + Mtu uint32 + SuppressRst bool + Masquerade string // "off" | "http" | "tls" | "dns" + Auth []byte + icmpSuppressed bool } diff --git a/transport/internet/finalmask/rawpacket/config.pb.go b/transport/internet/finalmask/rawpacket/config.pb.go index cc7a895212ca..3704f09f977f 100644 --- a/transport/internet/finalmask/rawpacket/config.pb.go +++ b/transport/internet/finalmask/rawpacket/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v7.34.1 +// protoc v7.35.1 // source: transport/internet/finalmask/rawpacket/config.proto package rawpacket @@ -56,7 +56,18 @@ type Config struct { // Expected peer spoof IP for receive filtering (nil = accept all). PeerSpoofIp string `protobuf:"bytes,21,opt,name=peer_spoof_ip,json=peerSpoofIp,proto3" json:"peer_spoof_ip,omitempty"` // Source port to use in spoofed packets (default: 443). - SpoofPort uint32 `protobuf:"varint,22,opt,name=spoof_port,json=spoofPort,proto3" json:"spoof_port,omitempty"` + SpoofPort uint32 `protobuf:"varint,22,opt,name=spoof_port,json=spoofPort,proto3" json:"spoof_port,omitempty"` + // Pre-shared key authenticating and encrypting the tunnel. Required + // in both modes; must match on client and relay. + Auth string `protobuf:"bytes,23,opt,name=auth,proto3" json:"auth,omitempty"` + // Suppress kernel-generated TCP RST for the relay's listen port + // (Linux only; uses iptables OUTPUT REJECT rules). Prevents the kernel + // from answering the fake TCP handshake with RST. + SuppressRst bool `protobuf:"varint,24,opt,name=suppress_rst,json=suppressRst,proto3" json:"suppress_rst,omitempty"` + // Masquerade for non-tunnel traffic (probes, scanners): "http" or + // "tls" serve a fake web service to TCP probes, "dns" answers UDP + // queries. Empty or "off" disables. + Masquerade string `protobuf:"bytes,26,opt,name=masquerade,proto3" json:"masquerade,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -210,11 +221,32 @@ func (x *Config) GetSpoofPort() uint32 { return 0 } +func (x *Config) GetAuth() string { + if x != nil { + return x.Auth + } + return "" +} + +func (x *Config) GetSuppressRst() bool { + if x != nil { + return x.SuppressRst + } + return false +} + +func (x *Config) GetMasquerade() string { + if x != nil { + return x.Masquerade + } + return "" +} + var File_transport_internet_finalmask_rawpacket_config_proto protoreflect.FileDescriptor const file_transport_internet_finalmask_rawpacket_config_proto_rawDesc = "" + "\n" + - "3transport/internet/finalmask/rawpacket/config.proto\x12+xray.transport.internet.finalmask.rawpacket\"\x8b\x04\n" + + "3transport/internet/finalmask/rawpacket/config.proto\x12+xray.transport.internet.finalmask.rawpacket\"\xe2\x04\n" + "\x06Config\x12\x12\n" + "\x04mode\x18\x06 \x01(\tR\x04mode\x12%\n" + "\x0eremote_address\x18\a \x01(\tR\rremoteAddress\x12\x1f\n" + @@ -237,7 +269,12 @@ const file_transport_internet_finalmask_rawpacket_config_proto_rawDesc = "" + "clientPort\x12\"\n" + "\rpeer_spoof_ip\x18\x15 \x01(\tR\vpeerSpoofIp\x12\x1d\n" + "\n" + - "spoof_port\x18\x16 \x01(\rR\tspoofPortB\xa3\x01\n" + + "spoof_port\x18\x16 \x01(\rR\tspoofPort\x12\x12\n" + + "\x04auth\x18\x17 \x01(\tR\x04auth\x12!\n" + + "\fsuppress_rst\x18\x18 \x01(\bR\vsuppressRst\x12\x1e\n" + + "\n" + + "masquerade\x18\x1a \x01(\tR\n" + + "masqueradeB\xa3\x01\n" + "/com.xray.transport.internet.finalmask.rawpacketP\x01Z@github.com/xtls/xray-core/transport/internet/finalmask/rawpacket\xaa\x02+Xray.Transport.Internet.Finalmask.Rawpacketb\x06proto3" var ( diff --git a/transport/internet/finalmask/rawpacket/config.proto b/transport/internet/finalmask/rawpacket/config.proto index e84fc770207f..82f83023701a 100644 --- a/transport/internet/finalmask/rawpacket/config.proto +++ b/transport/internet/finalmask/rawpacket/config.proto @@ -57,4 +57,18 @@ message Config { // Source port to use in spoofed packets (default: 443). uint32 spoof_port = 22; + + // Pre-shared key authenticating and encrypting the tunnel. Required + // in both modes; must match on client and relay. + string auth = 23; + + // Suppress kernel-generated TCP RST for the relay's listen port + // (Linux only; uses iptables OUTPUT REJECT rules). Prevents the kernel + // from answering the fake TCP handshake with RST. + bool suppress_rst = 24; + + // Masquerade for non-tunnel traffic (probes, scanners): "http" or + // "tls" serve a fake web service to TCP probes, "dns" answers UDP + // queries. Empty or "off" disables. + string masquerade = 26; } diff --git a/transport/internet/finalmask/rawpacket/conn.go b/transport/internet/finalmask/rawpacket/conn.go index a2fadff3a10a..c4c4dde4af81 100644 --- a/transport/internet/finalmask/rawpacket/conn.go +++ b/transport/internet/finalmask/rawpacket/conn.go @@ -30,10 +30,8 @@ func (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) { } func (c *Config) WrapConnServer(raw net.Conn) (net.Conn, error) { - if c.Mode == "remote" { - go c.startRelay() - return raw, nil - } + // The relay is owned and started by the listener (listenRawpacket); + // starting it here too would run two relays over the same port. return raw, nil } @@ -100,20 +98,11 @@ func (c *Config) dialLocal() (net.Conn, error) { return nil, err } - return DialSpoof(relayAddrPort, ips, recvPort, ttl, sendProto, recvProto, toNetipAddr(c.PeerSpoofIp)) -} - -func (c *Config) startRelay() { - cfg, err := c.buildRelayConfig() - if err != nil { - return + if c.Auth == "" { + return nil, fmt.Errorf("rawpacket: auth (PSK) required in local mode") } - r, err := NewRelay(cfg) - if err != nil { - return - } - defer r.Close() - r.Run() + + return DialSpoof(relayAddrPort, ips, recvPort, ttl, c.Mtu, sendProto, recvProto, toNetipAddr(c.PeerSpoofIp), []byte(c.Auth)) } func (c *Config) buildRelayConfig() (*RelayConfig, error) { @@ -121,6 +110,9 @@ func (c *Config) buildRelayConfig() (*RelayConfig, error) { if len(spoofIPs) == 0 { return nil, fmt.Errorf("rawpacket: at least one spoof IP required for relay") } + if c.Auth == "" { + return nil, fmt.Errorf("rawpacket: auth (PSK) required in remote mode") + } target := c.Target if target == "" { @@ -178,6 +170,10 @@ func (c *Config) buildRelayConfig() (*RelayConfig, error) { PeerSpoofIP: toNetipAddr(c.PeerSpoofIp), SendTransport: sendProto, RecvTransport: recvProto, + Mtu: c.Mtu, + SuppressRst: c.SuppressRst, + Masquerade: c.Masquerade, + Auth: []byte(c.Auth), }, nil } diff --git a/transport/internet/finalmask/rawpacket/frame.go b/transport/internet/finalmask/rawpacket/frame.go new file mode 100644 index 000000000000..33d14ec5aff1 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/frame.go @@ -0,0 +1,323 @@ +package rawpacket + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "encoding/binary" + "errors" + "io" + "sync" + + "golang.org/x/crypto/hkdf" +) + +// Frame layout (all integers big-endian): +// +// 0:4 magic (0x58445250 "XRDP") +// 4:1 version (1) +// 5:1 flags +// 6:2 reserved (zero) +// 8:16 session ID (random, chosen by the client per connection) +// 16:4 sequence number (per direction, starting at 0) +// 20:2 payload length +// 22:8 nonce (seq BE || direction byte || zeros) +// 30: AES-256-GCM ciphertext || tag +// +// The header prefix (bytes 0..22) is authenticated as GCM AAD, so any +// modification of magic/version/flags/reserved/session/seq/len is +// detected. Direction byte in the nonce: 0 = client->relay, 1 = relay->client. +const ( + frameMagic uint32 = 0x58445250 + frameVersion byte = 1 + frameHeaderLen = 30 + frameTagLen = 16 + frameNonceLen = 8 + frameAADLen = 22 + frameOverhead = frameHeaderLen + frameTagLen + + frameFlagKeepalive byte = 0x01 + frameFlagServer byte = 0x02 + + // frameMaxPayload caps each datagram so the full frame fits inside a + // single unfragmented packet on typical links (1400 + 46 < 1480). + frameMaxPayload = 1400 +) + +var ( + errFrameShort = errors.New("rawpacket: frame too short") + errFrameBad = errors.New("rawpacket: invalid frame") + errFrameAuth = errors.New("rawpacket: frame authentication failed") + errFrameReplay = errors.New("rawpacket: frame replay rejected") +) + +// replayWindow is a sliding-window anti-replay filter for a single +// direction of a session (RFC 4303 style). The window covers the highest +// received sequence number and the replayWindowSize-1 sequence numbers +// below it, so packets may arrive up to replayWindowSize-1 out of order. +// Every sequence number is accepted exactly once. +type replayWindow struct { + base uint32 + bitmap uint64 +} + +const replayWindowSize = 64 + +// accept reports whether seq has not been seen before. Bit i of bitmap +// records seq = base - (replayWindowSize - 1 - i); bit 63 is base. +func (w *replayWindow) accept(seq uint32) bool { + if w.base == 0 && w.bitmap == 0 { + w.base = seq + w.bitmap = 1 << (replayWindowSize - 1) + return true + } + diff := int32(seq - w.base) + switch { + case diff > 0: + if diff < int32(replayWindowSize) { + // Advance: old entries shift down, new seq lands on top. + w.bitmap >>= uint(diff) + w.bitmap |= 1 << (replayWindowSize - 1) + } else { + // Jump: the whole old window is discarded. + w.bitmap = 1 << (replayWindowSize - 1) + } + w.base = seq + return true + case diff < 0: + offset := int32(replayWindowSize) - 1 + diff + if offset < 0 { + return false + } + bit := uint64(1) << uint(offset) + if w.bitmap&bit != 0 { + return false + } + w.bitmap |= bit + return true + default: + // seq == base: duplicate unless the top bit is somehow clear. + if w.bitmap&(1<<(replayWindowSize-1)) != 0 { + return false + } + w.bitmap |= 1 << (replayWindowSize - 1) + return true + } +} + +// frameCrypto owns the per-session keys and per-direction sequence/replay +// state. One instance exists per session on each side. The two directions +// have independent mutexes so uplink and downlink never contend. +type frameCrypto struct { + sid [8]byte + clientAEAD cipher.AEAD + serverAEAD cipher.AEAD + + clientSeq uint32 + serverSeq uint32 + clientRep replayWindow + serverRep replayWindow + + clientMu sync.Mutex + serverMu sync.Mutex +} + +func newFrameCrypto(psk []byte, sid [8]byte) (*frameCrypto, error) { + clientKey, err := deriveFrameKey(psk, sid, []byte("rawpacket-client-v1")) + if err != nil { + return nil, err + } + serverKey, err := deriveFrameKey(psk, sid, []byte("rawpacket-server-v1")) + if err != nil { + return nil, err + } + clientAEAD, err := newGCM(clientKey) + if err != nil { + return nil, err + } + serverAEAD, err := newGCM(serverKey) + if err != nil { + return nil, err + } + return &frameCrypto{ + sid: sid, + clientAEAD: clientAEAD, + serverAEAD: serverAEAD, + }, nil +} + +func deriveFrameKey(psk []byte, sid [8]byte, info []byte) ([]byte, error) { + key := make([]byte, 32) + r := hkdf.New(sha256.New, psk, sid[:], info) + if _, err := io.ReadFull(r, key); err != nil { + return nil, err + } + return key, nil +} + +func newGCM(key []byte) (cipher.AEAD, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + return cipher.NewGCMWithNonceSize(block, frameNonceLen) +} + +func newSessionID() ([8]byte, error) { + var sid [8]byte + if _, err := rand.Read(sid[:]); err != nil { + return sid, err + } + return sid, nil +} + +func frameNonce(seq uint32, server bool) [frameNonceLen]byte { + var n [frameNonceLen]byte + binary.BigEndian.PutUint32(n[0:4], seq) + if server { + n[4] = 1 + } + return n +} + +// seal builds and encrypts one frame from payload. server selects the +// relay->client direction. The sequence counter and replay window are +// updated. Payload is capped at frameMaxPayload. +func (f *frameCrypto) seal(server bool, payload []byte, keepalive bool) ([]byte, error) { + if len(payload) > frameMaxPayload { + return nil, errors.New("rawpacket: payload exceeds frameMaxPayload") + } + if server { + f.serverMu.Lock() + defer f.serverMu.Unlock() + } else { + f.clientMu.Lock() + defer f.clientMu.Unlock() + } + + seq := f.clientSeq + var aead cipher.AEAD + if server { + seq = f.serverSeq + f.serverSeq++ + aead = f.serverAEAD + } else { + seq = f.clientSeq + f.clientSeq++ + aead = f.clientAEAD + } + + flags := byte(0) + if server { + flags |= frameFlagServer + } + if keepalive { + flags |= frameFlagKeepalive + } + + out := make([]byte, frameHeaderLen+len(payload)+frameTagLen) + binary.BigEndian.PutUint32(out[0:4], frameMagic) + out[4] = frameVersion + out[5] = flags + // 6:7 reserved + copy(out[8:16], f.sid[:]) + binary.BigEndian.PutUint32(out[16:20], seq) + binary.BigEndian.PutUint16(out[20:22], uint16(len(payload))) + + nonce := frameNonce(seq, server) + aead.Seal(out[frameHeaderLen:frameHeaderLen], nonce[:], payload, out[:frameAADLen]) + return out, nil +} + +// open parses, authenticates and decrypts one frame. server selects the +// direction the frame is expected from. The returned payload is freshly +// allocated and safe to retain. +func (f *frameCrypto) open(b []byte, server bool) (payload []byte, flags byte, err error) { + if len(b) < frameHeaderLen+frameTagLen { + return nil, 0, errFrameShort + } + if binary.BigEndian.Uint32(b[0:4]) != frameMagic || b[4] != frameVersion { + return nil, 0, errFrameBad + } + flags = b[5] + if (flags&frameFlagServer != 0) != server { + return nil, 0, errFrameBad + } + pLen := int(binary.BigEndian.Uint16(b[20:22])) + if pLen != len(b)-frameHeaderLen-frameTagLen { + return nil, 0, errFrameBad + } + seq := binary.BigEndian.Uint32(b[16:20]) + + if server { + f.serverMu.Lock() + defer f.serverMu.Unlock() + } else { + f.clientMu.Lock() + defer f.clientMu.Unlock() + } + + var aead cipher.AEAD + var rep *replayWindow + if server { + aead = f.serverAEAD + rep = &f.serverRep + } else { + aead = f.clientAEAD + rep = &f.clientRep + } + if !rep.accept(seq) { + return nil, 0, errFrameReplay + } + nonce := frameNonce(seq, server) + ct := b[frameHeaderLen : frameHeaderLen+pLen+frameTagLen] + pt, err := aead.Open(nil, nonce[:], ct, b[:frameAADLen]) + if err != nil { + return nil, 0, errFrameAuth + } + return pt, flags, nil +} + +// frameSessionID extracts the session ID from a frame header without +// decrypting it. Returns false for non-frame traffic (which the relay must +// silently ignore: real TLS, kernel noise, etc.). +func frameSessionID(b []byte) (sid [8]byte, ok bool) { + if len(b) < frameHeaderLen { + return sid, false + } + if binary.BigEndian.Uint32(b[0:4]) != frameMagic || b[4] != frameVersion { + return sid, false + } + copy(sid[:], b[8:16]) + return sid, true +} + +// sealSplit splits a large payload into frames of at most maxPayload and +// seals each one in sequence order. +func (f *frameCrypto) sealSplit(server bool, payload []byte, maxPayload int, keepalive bool) ([][]byte, error) { + if maxPayload <= 0 { + maxPayload = frameMaxPayload + } + if len(payload) == 0 { + frame, err := f.seal(server, nil, keepalive) + if err != nil { + return nil, err + } + return [][]byte{frame}, nil + } + frames := make([][]byte, 0, (len(payload)+maxPayload-1)/maxPayload) + for len(payload) > 0 { + chunk := payload + if len(chunk) > maxPayload { + chunk = chunk[:maxPayload] + } + frame, err := f.seal(server, chunk, false) + if err != nil { + return nil, err + } + frames = append(frames, frame) + payload = payload[len(chunk):] + } + return frames, nil +} diff --git a/transport/internet/finalmask/rawpacket/frame_demux.go b/transport/internet/finalmask/rawpacket/frame_demux.go new file mode 100644 index 000000000000..0ec2ebd1bd3b --- /dev/null +++ b/transport/internet/finalmask/rawpacket/frame_demux.go @@ -0,0 +1,141 @@ +package rawpacket + +import ( + "net/netip" + "sync" +) + +// frameDemux demultiplexes decrypted relay->client frames to per-conn +// consumers. Multiple SpoofConns on the same host share one raw receiver +// per (protocol, listen port); a raw socket delivers a copy of every +// matching packet to every socket, so the receiver must be shared or +// frames would be duplicated across connections. +type frameDemux struct { + proto string + port uint16 + peerIP netip.Addr + + mu sync.Mutex + sessions map[[8]byte]*demuxSession + recver SpoofReceiver + refs int + running bool + gen uint64 +} + +type demuxSession struct { + crypto *frameCrypto + ch chan demuxData +} + +// demuxData is one decrypted relay->client payload with the originating +// TCP header metadata (for sequence/ack state tracking). +type demuxData struct { + payload []byte + tcp *TCPMeta +} + +type demuxKey struct { + proto string + port uint16 +} + +var demuxRegistry sync.Map // demuxKey -> *frameDemux + +func getFrameDemux(proto string, port uint16, peerIP netip.Addr) *frameDemux { + key := demuxKey{proto: proto, port: port} + if d, ok := demuxRegistry.Load(key); ok { + return d.(*frameDemux) + } + d := &frameDemux{ + proto: proto, + port: port, + peerIP: peerIP, + sessions: make(map[[8]byte]*demuxSession), + } + actual, _ := demuxRegistry.LoadOrStore(key, d) + return actual.(*frameDemux) +} + +// register subscribes sid to the demux, starting the shared receiver and +// demux goroutine on first use. +func (d *frameDemux) register(sid [8]byte, crypto *frameCrypto, ch chan demuxData) error { + d.mu.Lock() + defer d.mu.Unlock() + d.sessions[sid] = &demuxSession{crypto: crypto, ch: ch} + d.refs++ + if d.running { + return nil + } + recver, err := NewReceiver(d.proto, &SpoofReceiverConfig{ + ListenPort: d.port, + PeerSpoofIP: d.peerIP, + BufferSize: 4 * 1024 * 1024, + }) + if err != nil { + delete(d.sessions, sid) + d.refs-- + return err + } + d.recver = recver + d.running = true + d.gen++ + go d.run(d.gen) + return nil +} + +func (d *frameDemux) unregister(sid [8]byte) { + d.mu.Lock() + defer d.mu.Unlock() + delete(d.sessions, sid) + d.refs-- + if d.refs > 0 || !d.running { + return + } + d.running = false + d.recver.Close() + d.recver = nil + demuxRegistry.Delete(demuxKey{proto: d.proto, port: d.port}) +} + +func (d *frameDemux) run(gen uint64) { + for { + pkt, _, _, tcp, err := d.recver.Receive() + if err != nil { + d.mu.Lock() + if d.gen != gen { + // Superseded by a newer generation (the demux was torn + // down and restarted): leave the new sessions alone. + d.mu.Unlock() + return + } + // Receiver died unexpectedly (or the demux was torn down): + // unblock every consumer. + for _, s := range d.sessions { + close(s.ch) + } + d.sessions = make(map[[8]byte]*demuxSession) + d.running = false + d.mu.Unlock() + return + } + sid, ok := frameSessionID(pkt) + if !ok { + continue + } + d.mu.Lock() + s := d.sessions[sid] + d.mu.Unlock() + if s == nil { + continue + } + payload, flags, err := s.crypto.open(pkt, true) + if err != nil || flags&frameFlagKeepalive != 0 || len(payload) == 0 { + continue + } + select { + case s.ch <- demuxData{payload: payload, tcp: tcp}: + default: // consumer is slow: drop rather than stall the demux + } + } +} diff --git a/transport/internet/finalmask/rawpacket/frame_test.go b/transport/internet/finalmask/rawpacket/frame_test.go new file mode 100644 index 000000000000..84c5c2279758 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/frame_test.go @@ -0,0 +1,283 @@ +package rawpacket + +import ( + "bytes" + "crypto/rand" + "testing" +) + +func testFrameCrypto(t *testing.T) *frameCrypto { + t.Helper() + psk := []byte("test-psk-secret") + var sid [8]byte + if _, err := rand.Read(sid[:]); err != nil { + t.Fatal(err) + } + f, err := newFrameCrypto(psk, sid) + if err != nil { + t.Fatal(err) + } + return f +} + +func TestFrameSealOpenRoundTrip(t *testing.T) { + f := testFrameCrypto(t) + payload := []byte("hello rawpacket") + + frame, err := f.seal(false, payload, false) + if err != nil { + t.Fatal(err) + } + got, flags, err := f.open(frame, false) + if err != nil { + t.Fatalf("open: %v", err) + } + if !bytes.Equal(got, payload) { + t.Fatalf("payload mismatch: got %q", got) + } + if flags&frameFlagServer != 0 { + t.Fatal("client frame must not carry server flag") + } + + frame, err = f.seal(true, payload, true) + if err != nil { + t.Fatal(err) + } + got, flags, err = f.open(frame, true) + if err != nil { + t.Fatalf("open server frame: %v", err) + } + if !bytes.Equal(got, payload) { + t.Fatalf("payload mismatch: got %q", got) + } + if flags&frameFlagKeepalive == 0 || flags&frameFlagServer == 0 { + t.Fatalf("expected keepalive+server flags, got %#x", flags) + } +} + +func TestFrameDirectionEnforced(t *testing.T) { + f := testFrameCrypto(t) + frame, err := f.seal(false, []byte("up"), false) + if err != nil { + t.Fatal(err) + } + if _, _, err := f.open(frame, true); err == nil { + t.Fatal("opening a client frame as server frame must fail") + } +} + +func TestFrameTamperDetection(t *testing.T) { + f := testFrameCrypto(t) + frame, err := f.seal(false, []byte("authenticated"), false) + if err != nil { + t.Fatal(err) + } + + // Tamper every header byte region: magic, version, flags, session, + // seq, payload length. + flips := []int{0, 3, 4, 5, 8, 15, 16, 19, 20, 21} + for _, i := range flips { + bad := make([]byte, len(frame)) + copy(bad, frame) + bad[i] ^= 0xFF + if _, _, err := f.open(bad, false); err == nil { + t.Fatalf("tampered byte %d was accepted", i) + } + } + + // Flip a payload byte (after the AAD region). + bad := make([]byte, len(frame)) + copy(bad, frame) + bad[len(bad)-1] ^= 0xFF + if _, _, err := f.open(bad, false); err == nil { + t.Fatal("tampered payload was accepted") + } + + // A truncated frame must be rejected. + if _, _, err := f.open(frame[:len(frame)-1], false); err == nil { + t.Fatal("truncated frame was accepted") + } +} + +func TestFrameReplayProtection(t *testing.T) { + f := testFrameCrypto(t) + + frame, err := f.seal(false, []byte("one"), false) + if err != nil { + t.Fatal(err) + } + if _, _, err := f.open(frame, false); err != nil { + t.Fatalf("first open: %v", err) + } + // Replaying the exact same frame must be rejected. + if _, _, err := f.open(frame, false); err == nil { + t.Fatal("replayed frame was accepted") + } + // Same seq, different ciphertext (attacker re-encrypt guess) must fail. + if _, _, err := f.open(frame, false); err == nil { + t.Fatal("replayed frame accepted twice") + } +} + +func TestFrameReorderingWithinWindow(t *testing.T) { + f := testFrameCrypto(t) + + var frames [][]byte + for i := 0; i < 10; i++ { + fr, err := f.seal(false, []byte{byte(i)}, false) + if err != nil { + t.Fatal(err) + } + frames = append(frames, fr) + } + // Deliver 0..9 in scrambled order: all must be accepted exactly once. + order := []int{2, 0, 1, 5, 3, 4, 9, 6, 7, 8} + for _, i := range order { + if _, _, err := f.open(frames[i], false); err != nil { + t.Fatalf("frame %d out of order rejected: %v", i, err) + } + } + // All frames are now consumed; resending any must be rejected. + for i := 0; i < 10; i++ { + if _, _, err := f.open(frames[i], false); err == nil { + t.Fatalf("replayed frame %d accepted", i) + } + } +} + +func TestFrameSeqJumpAdvance(t *testing.T) { + f := testFrameCrypto(t) + var first []byte + for i := 0; i < 70; i++ { + fr, err := f.seal(false, []byte{byte(i)}, false) + if err != nil { + t.Fatal(err) + } + if i == 0 { + first = fr + } + if _, _, err := f.open(fr, false); err != nil { + t.Fatalf("frame %d: %v", i, err) + } + } + // The window has advanced past seq 0 (base=69); replaying it must fail. + if _, _, err := f.open(first, false); err == nil { + t.Fatal("stale frame accepted after window advance") + } +} + +func TestFrameKeepaliveFlag(t *testing.T) { + f := testFrameCrypto(t) + frame, err := f.seal(false, nil, true) + if err != nil { + t.Fatal(err) + } + payload, flags, err := f.open(frame, false) + if err != nil { + t.Fatalf("open: %v", err) + } + if len(payload) != 0 { + t.Fatalf("keepalive payload must be empty, got %d bytes", len(payload)) + } + if flags&frameFlagKeepalive == 0 { + t.Fatal("keepalive flag not set") + } +} + +func TestFrameWrongKeyRejected(t *testing.T) { + a := testFrameCrypto(t) + b, err := newFrameCrypto([]byte("other-psk"), a.sid) + if err != nil { + t.Fatal(err) + } + frame, err := a.seal(false, []byte("secret"), false) + if err != nil { + t.Fatal(err) + } + if _, _, err := b.open(frame, false); err == nil { + t.Fatal("frame sealed with a different PSK was accepted") + } +} + +func TestSealSplit(t *testing.T) { + f := testFrameCrypto(t) + payload := bytes.Repeat([]byte("x"), frameMaxPayload*3+7) + + frames, err := f.sealSplit(false, payload, 0, false) + if err != nil { + t.Fatal(err) + } + if len(frames) != 4 { + t.Fatalf("expected 4 frames, got %d", len(frames)) + } + var got []byte + for _, fr := range frames { + pt, _, err := f.open(fr, false) + if err != nil { + t.Fatalf("open: %v", err) + } + got = append(got, pt...) + } + if !bytes.Equal(got, payload) { + t.Fatal("split payload mismatch") + } + + // Empty payload produces a single empty frame. + frames, err = f.sealSplit(false, nil, 0, true) + if err != nil { + t.Fatal(err) + } + if len(frames) != 1 { + t.Fatalf("expected 1 frame for empty payload, got %d", len(frames)) + } +} + +func TestReplayWindow(t *testing.T) { + var w replayWindow + if !w.accept(100) { + t.Fatal("first seq must be accepted") + } + if w.accept(100) { + t.Fatal("duplicate must be rejected") + } + if !w.accept(101) { + t.Fatal("next seq must be accepted") + } + // seq 99 is below the max but still inside the window and unseen: + // tolerated reordering. + if !w.accept(99) { + t.Fatal("in-window reordering must be accepted") + } + if w.accept(99) { + t.Fatal("duplicate in-window seq must be rejected") + } + if !w.accept(160) { + t.Fatal("seq jump must be accepted") + } + // seq 99 is now outside the window (base=160, window floor=97). + if w.accept(99) { + t.Fatal("seq below window floor must be rejected") + } + if w.accept(160) { + t.Fatal("duplicate after jump must be rejected") + } +} + +func TestFrameSessionID(t *testing.T) { + f := testFrameCrypto(t) + frame, err := f.seal(false, []byte("x"), false) + if err != nil { + t.Fatal(err) + } + sid, ok := frameSessionID(frame) + if !ok { + t.Fatal("frameSessionID failed on valid frame") + } + if sid != f.sid { + t.Fatal("session id mismatch") + } + // Non-frame traffic (e.g. a real TLS ClientHello) must not parse. + if _, ok := frameSessionID([]byte("not a frame")); ok { + t.Fatal("garbage accepted as frame") + } +} diff --git a/transport/internet/finalmask/rawpacket/masquerade.go b/transport/internet/finalmask/rawpacket/masquerade.go new file mode 100644 index 000000000000..d0cf539eab05 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/masquerade.go @@ -0,0 +1,405 @@ +package rawpacket + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/binary" + "log" + "math/big" + "net/netip" + "strings" + "sync" + "time" +) + +// masquerade answers probes that do not speak the tunnel protocol, so the +// relay port looks like a real service: +// +// http/tls: fake TCP handshake, then serve a static HTTP page or a +// TLS ServerHello + certificate flow +// dns: answer UDP queries with a DNS reply (A record for type A, +// NXDOMAIN otherwise), using the relay's own address +// +// Replies are sent from the address the probe dialed (dst of the +// incoming packet), never from the tunnel's spoofed source IPs. + +type masqueradeMode int + +const ( + masqOff masqueradeMode = iota + masqHTTP + masqTLS + masqDNS + + masqFlowTTL = 2 * time.Minute + masqFlowCap = 4096 + masqMaxChunkLen = 1400 +) + +func parseMasqueradeMode(s string) masqueradeMode { + switch strings.ToLower(strings.TrimSpace(s)) { + case "http": + return masqHTTP + case "tls", "https": + return masqTLS + case "dns": + return masqDNS + default: + return masqOff + } +} + +func (m masqueradeMode) String() string { + switch m { + case masqHTTP: + return "http" + case masqTLS: + return "tls" + case masqDNS: + return "dns" + default: + return "off" + } +} + +type masqKey struct { + ip netip.Addr + port uint16 +} + +type fakeFlow struct { + tcp *TCPSimState + resp []byte + sent int + lastSeen time.Time +} + +type masquerade struct { + mode masqueradeMode + fd *rawSendFD + relayP uint16 // relay listen port, source port of replies + + mu sync.Mutex + flows map[masqKey]*fakeFlow + ipID uint16 + idMu sync.Mutex + + httpResp []byte + tlsResp []byte + + done chan struct{} +} + +// newMasquerade builds the responder for mode; mode ""/"off" returns nil. +func newMasquerade(mode string, relayPort uint16) (*masquerade, error) { + m := parseMasqueradeMode(mode) + if m == masqOff { + return nil, nil + } + fd, err := openRawSenderAny() + if err != nil { + return nil, err + } + resp, err := masqueradeResponses(m) + if err != nil { + fd.close() + return nil, err + } + mg := &masquerade{ + mode: m, + fd: fd, + relayP: relayPort, + flows: make(map[masqKey]*fakeFlow), + httpResp: resp, + tlsResp: resp, + done: make(chan struct{}), + } + go mg.gcLoop() + log.Printf("[rawpacket] masquerade enabled: %s (port %d)", m, relayPort) + return mg, nil +} + +// masqueradeResponses builds the static response payload for the mode. +func masqueradeResponses(m masqueradeMode) ([]byte, error) { + switch m { + case masqHTTP: + return fakeHTTPResponse(), nil + case masqTLS: + return buildFakeTLSServer() + default: + return nil, nil + } +} + +func (m *masquerade) close() { + if m == nil { + return + } + close(m.done) + m.fd.close() +} + +func (m *masquerade) nextIPID() uint16 { + m.idMu.Lock() + defer m.idMu.Unlock() + m.ipID++ + return m.ipID +} + +// onTCPSYN handles a bare SYN from a probe: answer SYN|ACK and start a +// fake flow so subsequent data can be served. +func (m *masquerade) onTCPSYN(relayIP netip.Addr, relayPort uint16, probeIP netip.Addr, probePort uint16, seq uint32) { + if m == nil || (m.mode != masqHTTP && m.mode != masqTLS) { + return + } + key := masqKey{probeIP, probePort} + m.mu.Lock() + f := m.flows[key] + if f == nil { + resp := m.httpResp + if m.mode == masqTLS { + resp = m.tlsResp + } + f = &fakeFlow{tcp: newTCPSimState(), resp: resp} + if len(m.flows) < masqFlowCap { + m.flows[key] = f + } + } + f.lastSeen = time.Now() + f.tcp.observeClientSeq(seq) + m.mu.Unlock() + m.sendTCP(relayIP, relayPort, probeIP, probePort, f, nil, true) +} + +// onTCPData serves the fake response to probe data. The response is +// streamed in MSS-sized chunks and repeats once exhausted, so repeated +// requests (e.g. a browser reloading) keep getting answers. +func (m *masquerade) onTCPData(relayIP netip.Addr, relayPort uint16, probeIP netip.Addr, probePort uint16, seq uint32, payload []byte) { + if m == nil || (m.mode != masqHTTP && m.mode != masqTLS) || len(payload) == 0 { + return + } + key := masqKey{probeIP, probePort} + m.mu.Lock() + f := m.flows[key] + if f == nil { + m.mu.Unlock() + return + } + f.lastSeen = time.Now() + f.tcp.observeClientSeq(seq) + chunk := f.resp[f.sent:] + if len(chunk) > masqMaxChunkLen { + chunk = chunk[:masqMaxChunkLen] + } + f.sent += len(chunk) + if f.sent >= len(f.resp) { + f.sent = 0 + } + m.mu.Unlock() + m.sendTCP(relayIP, relayPort, probeIP, probePort, f, chunk, false) +} + +func (m *masquerade) sendTCP(relayIP netip.Addr, relayPort uint16, probeIP netip.Addr, probePort uint16, f *fakeFlow, payload []byte, syn bool) { + var flags uint8 + if syn { + flags = TCPFlagSyn | TCPFlagAck + } else { + flags = TCPFlagAck + } + seq := f.tcp.nextSeq(len(payload)) + pkt := BuildTCPPacket(relayIP, probeIP, relayPort, probePort, seq, f.tcp.ack(), flags, payload, 64, m.nextIPID(), false) + _ = m.fd.sendTo(pkt, probeIP) +} + +// onUDP answers a probe datagram with a fake DNS reply. +func (m *masquerade) onUDP(relayIP netip.Addr, relayPort uint16, probeIP netip.Addr, probePort uint16, payload []byte) { + if m == nil || m.mode != masqDNS || len(payload) < 12 { + return + } + reply := fakeDNSReply(payload, relayIP) + if len(reply) == 0 { + return + } + pkt := BuildRawUDP(relayIP, probeIP, relayPort, probePort, reply, 64, m.nextIPID()) + _ = m.fd.sendTo(pkt, probeIP) +} + +// gcLoop drops stale fake flows. +func (m *masquerade) gcLoop() { + t := time.NewTicker(30 * time.Second) + defer t.Stop() + for { + select { + case <-m.done: + return + case <-t.C: + now := time.Now() + m.mu.Lock() + for k, f := range m.flows { + if now.Sub(f.lastSeen) > masqFlowTTL { + delete(m.flows, k) + } + } + m.mu.Unlock() + } + } +} + +// fakeHTTPResponse is a minimal nginx-style page. +func fakeHTTPResponse() []byte { + body := "\r\nWelcome to nginx!\r\n\r\n

Welcome to nginx!

\r\n\r\n\r\n" + hdr := "HTTP/1.1 200 OK\r\nServer: nginx\r\nContent-Type: text/html\r\nContent-Length: " + itoa(len(body)) + "\r\nConnection: close\r\n\r\n" + return append([]byte(hdr), body...) +} + +func itoa(n int) string { + if n == 0 { + return "0" + } + var b [16]byte + i := len(b) + for n > 0 { + i-- + b[i] = byte('0' + n%10) + n /= 10 + } + return string(b[i:]) +} + +// buildFakeTLSServer produces a canned TLS 1.2 ServerHello + Certificate + +// ServerHelloDone record flow backed by a self-signed ECDSA certificate. +// The handshake never completes (no key exchange), which is enough for +// passive DPI classification and port scanners. +func buildFakeTLSServer() ([]byte, error) { + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, err + } + tmpl := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: "nginx", Organization: []string{"nginx"}}, + NotBefore: time.Now().Add(-24 * time.Hour), + NotAfter: time.Now().Add(365 * 24 * time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + } + der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key) + if err != nil { + return nil, err + } + + var random [32]byte + _, _ = rand.Read(random[:]) + + // ServerHello (TLS 1.2, ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) + sh := make([]byte, 0, 38+32) + sh = append(sh, 0x03, 0x03) + sh = append(sh, random[:]...) + sh = append(sh, 0x20) + sh = append(sh, random[:32]...) + sh = append(sh, 0xC0, 0x2B, 0x00) + + // handshake wrapper: type + 24-bit length + hello := tlsHandshakeRecord(0x02, sh) + + // Certificate + certMsg := make([]byte, 0, 3+len(der)) + certMsg = binary.BigEndian.AppendUint32(certMsg, uint32(len(der)))[1:] + certMsg = append(certMsg, der...) + cert := tlsHandshakeRecord(0x0B, certMsg) + + // ServerHelloDone + done := tlsHandshakeRecord(0x0E, nil) + + var out []byte + out = append(out, hello...) + out = append(out, cert...) + out = append(out, done...) + return out, nil +} + +// tlsHandshakeRecord wraps a handshake message in a TLS record (content +// type 0x16) with a 24-bit handshake length header. +func tlsHandshakeRecord(msgType byte, body []byte) []byte { + out := make([]byte, 0, 5+4+len(body)) + out = append(out, 0x16, 0x03, 0x03) + l := 4 + len(body) + out = append(out, byte(l>>8), byte(l)) + out = append(out, msgType) + out = append(out, byte(len(body)>>16), byte(len(body)>>8), byte(len(body))) + out = append(out, body...) + return out +} + +// fakeDNSReply builds a DNS reply for query: an A record with the relay's +// address for type A queries, NXDOMAIN otherwise. Malformed queries yield +// nil (no reply). The question is always echoed. +func fakeDNSReply(query []byte, relayIP netip.Addr) []byte { + if len(query) < 12 { + return nil + } + id := query[0:2] + flags := binary.BigEndian.Uint16(query[2:4]) + qd := binary.BigEndian.Uint16(query[4:6]) + if qd == 0 { + return nil + } + off := 12 + qnameEnd := -1 + for off < len(query) { + l := int(query[off]) + if l == 0 { + off++ + qnameEnd = off + break + } + if l&0xC0 == 0xC0 { // compression pointer: name ends at 2-byte pointer + off += 2 + qnameEnd = off + break + } + if off+1+l > len(query) { + return nil + } + off += 1 + l + if off-12 > 255 { + return nil + } + } + if qnameEnd < 0 || off+4 > len(query) { + return nil + } + qtype := binary.BigEndian.Uint16(query[off:]) + qclass := binary.BigEndian.Uint16(query[off+2:]) + + // QR=1, opcode echoed, RD echoed, RA=1; rcode 3 (NXDOMAIN) unless we + // answer the query. + rcode := uint16(3) + ancount := uint16(0) + var answer []byte + if qtype == 1 && qclass == 1 && relayIP.Is4() { // A query + rcode = 0 + ancount = 1 + answer = make([]byte, 0, 16) + answer = append(answer, 0xC0, 0x0C) // name pointer to question + answer = binary.BigEndian.AppendUint16(answer, 1) // type A + answer = binary.BigEndian.AppendUint16(answer, 1) // class IN + answer = binary.BigEndian.AppendUint32(answer, 60) // TTL + answer = binary.BigEndian.AppendUint16(answer, 4) // rdlength + answer = append(answer, relayIP.AsSlice()...) + } + + reply := make([]byte, 0, len(query)+16) + reply = append(reply, id...) + reply = binary.BigEndian.AppendUint16(reply, 0x8000|(flags&0x7800)|0x0080|rcode) + reply = binary.BigEndian.AppendUint16(reply, 1) // QDCOUNT + reply = binary.BigEndian.AppendUint16(reply, ancount) + reply = binary.BigEndian.AppendUint16(reply, 0) // NS + reply = binary.BigEndian.AppendUint16(reply, 0) // AR + reply = append(reply, query[12:off+4]...) // question: qname + type + class + reply = append(reply, answer...) + return reply +} diff --git a/transport/internet/finalmask/rawpacket/masquerade_test.go b/transport/internet/finalmask/rawpacket/masquerade_test.go new file mode 100644 index 000000000000..42d233280377 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/masquerade_test.go @@ -0,0 +1,301 @@ +package rawpacket + +import ( + "encoding/binary" + "net/netip" + "testing" +) + +func TestParseMasqueradeMode(t *testing.T) { + cases := map[string]masqueradeMode{ + "": masqOff, + "off": masqOff, + "http": masqHTTP, + "tls": masqTLS, + "https": masqTLS, + "dns": masqDNS, + "HTTP": masqHTTP, + } + for in, want := range cases { + if got := parseMasqueradeMode(in); got != want { + t.Fatalf("parseMasqueradeMode(%q) = %v, want %v", in, got, want) + } + } +} + +func TestFakeHTTPResponse(t *testing.T) { + resp := fakeHTTPResponse() + if len(resp) == 0 { + t.Fatal("empty HTTP response") + } + s := string(resp) + if !startsWith(s, "HTTP/1.1 200 OK\r\n") { + t.Fatalf("missing status line: %q", s[:32]) + } + // Content-Length must match the body length + body := "\r\nWelcome to nginx!\r\n\r\n

Welcome to nginx!

\r\n\r\n\r\n" + cl := "Content-Length: " + itoa(len(body)) + if !contains(s, cl) { + t.Fatalf("missing %q in %q", cl, s[:120]) + } +} + +func TestBuildFakeTLSServer(t *testing.T) { + resp, err := buildFakeTLSServer() + if err != nil { + t.Fatal(err) + } + // Expect 3 TLS records: ServerHello, Certificate, ServerHelloDone + var off int + var types []byte + var lens []int + for off < len(resp) && len(types) < 3 { + if off+5 > len(resp) || resp[off] != 0x16 { + t.Fatalf("bad record header at %d: %x", off, resp[off:]) + } + l := int(resp[off+3])<<8 | int(resp[off+4]) + if off+5+l > len(resp) { + t.Fatalf("record overruns buffer at %d", off) + } + types = append(types, resp[off+5]) + lens = append(lens, l) + off += 5 + l + } + if off != len(resp) { + t.Fatalf("trailing bytes after 3 records: %d", len(resp)-off) + } + if types[0] != 0x02 || types[1] != 0x0B || types[2] != 0x0E { + t.Fatalf("record types = %x, want [2 b e]", types) + } + // Certificate record must contain a DER cert + if lens[1] < 4 { + t.Fatalf("certificate record too small: %d", lens[1]) + } +} + +func TestFakeDNSReplyA(t *testing.T) { + // build a DNS A query for example.com + q := buildDNSQuery("example.com", 1, 0x1234, 0x0100) + relay := netip.MustParseAddr("203.0.113.9") + reply := fakeDNSReply(q, relay) + if reply == nil { + t.Fatal("nil reply for A query") + } + if binary.BigEndian.Uint16(reply[0:2]) != 0x1234 { + t.Fatalf("bad ID") + } + flags := binary.BigEndian.Uint16(reply[2:4]) + if flags&0x8000 == 0 { + t.Fatal("QR not set") + } + if flags&0x000F != 0 { + t.Fatalf("rcode = %d, want 0 for A query", flags&0x000F) + } + if binary.BigEndian.Uint16(reply[4:6]) != 1 { + t.Fatalf("QDCOUNT = %d, want 1", binary.BigEndian.Uint16(reply[4:6])) + } + if an := binary.BigEndian.Uint16(reply[6:8]); an != 1 { + t.Fatalf("ANCOUNT = %d, want 1", an) + } + // answer: pointer to name + A record with relay IP + a := reply[len(reply)-4:] + if a[0] != 203 || a[3] != 9 { + t.Fatalf("answer = %v, want relay IP", a) + } + if rdlen := binary.BigEndian.Uint16(reply[len(reply)-6 : len(reply)-4]); rdlen != 4 { + t.Fatalf("rdlength = %d, want 4", rdlen) + } +} + +func TestFakeDNSReplyNXDOMAIN(t *testing.T) { + q := buildDNSQuery("host.example", 28, 0xABCD, 0x0100) // AAAA query + relay := netip.MustParseAddr("203.0.113.9") + reply := fakeDNSReply(q, relay) + if reply == nil { + t.Fatal("nil reply") + } + if flags := binary.BigEndian.Uint16(reply[2:4]); flags&0x000F != 3 { + t.Fatalf("rcode = %d, want 3 (NXDOMAIN)", flags&0x000F) + } + if an := binary.BigEndian.Uint16(reply[6:8]); an != 0 { + t.Fatalf("ANCOUNT = %d, want 0", an) + } +} + +func TestFakeDNSReplyMalformed(t *testing.T) { + relay := netip.MustParseAddr("203.0.113.9") + if fakeDNSReply([]byte("short"), relay) != nil { + t.Fatal("short query should be dropped") + } + // QDCOUNT = 0 + q := make([]byte, 12) + if fakeDNSReply(q, relay) != nil { + t.Fatal("zero-question query should be dropped") + } + // truncated name + q = buildDNSQuery("example.com", 1, 0x1, 0x0100) + if fakeDNSReply(q[:len(q)-4], relay) != nil { + t.Fatal("truncated query should be dropped") + } +} + +func TestMasqueradeFlowLifecycle(t *testing.T) { + // build directly to avoid opening a raw socket (needs privileges) + rec := &recordingFD{} + m := &masquerade{ + mode: masqHTTP, + fd: rec.fd(), + relayP: 443, + flows: make(map[masqKey]*fakeFlow), + httpResp: fakeHTTPResponse(), + done: make(chan struct{}), + } + defer close(m.done) + + probe := netip.MustParseAddr("198.51.100.7") + relay := netip.MustParseAddr("203.0.113.9") + + // bare SYN + m.onTCPSYN(relay, 443, probe, 50000, 1000) + if len(rec.pkts) != 1 { + t.Fatalf("SYN should produce 1 packet, got %d", len(rec.pkts)) + } + synack := rec.pkts[0] + if synack[33] != TCPFlagSyn|TCPFlagAck { + t.Fatalf("flags = %#x, want SYN|ACK", synack[33]) + } + if ack := binary.BigEndian.Uint32(synack[28:32]); ack != 1001 { + t.Fatalf("ack = %d, want 1001", ack) + } + if binary.BigEndian.Uint16(synack[20:22]) != 443 { + t.Fatalf("src port = %d, want 443", binary.BigEndian.Uint16(synack[20:22])) + } + + // data (HTTP GET) -> response chunk + rec.pkts = nil + m.onTCPData(relay, 443, probe, 50000, 1001, []byte("GET / HTTP/1.1\r\n\r\n")) + if len(rec.pkts) != 1 { + t.Fatalf("data should produce 1 packet, got %d", len(rec.pkts)) + } + data := rec.pkts[0] + payload := data[52:] // 20 ip + 32 tcp (20 hdr + 12 options) + if !startsWith(string(payload), "HTTP/1.1 200 OK") { + t.Fatalf("unexpected payload: %q", payload[:min(len(payload), 40)]) + } + if seq := binary.BigEndian.Uint32(data[24:28]); seq == 0 { + t.Fatal("seq should be relay ISN, got 0") + } + + // unknown flow data -> no reply + rec.pkts = nil + m.onTCPData(relay, 443, netip.MustParseAddr("198.51.100.8"), 51000, 1, []byte("x")) + if len(rec.pkts) != 0 { + t.Fatalf("unknown flow should not get a reply") + } +} + +func TestMasqueradeDNSFlow(t *testing.T) { + rec := &recordingFD{} + m := &masquerade{ + mode: masqDNS, + fd: rec.fd(), + relayP: 53, + flows: make(map[masqKey]*fakeFlow), + done: make(chan struct{}), + } + defer close(m.done) + + probe := netip.MustParseAddr("198.51.100.7") + relay := netip.MustParseAddr("203.0.113.9") + q := buildDNSQuery("example.com", 1, 0x4242, 0x0100) + m.onUDP(relay, 53, probe, 40000, q) + if len(rec.pkts) != 1 { + t.Fatalf("dns probe should get 1 reply, got %d", len(rec.pkts)) + } + pkt := rec.pkts[0] + if pkt[9] != 17 { + t.Fatalf("protocol = %d, want UDP", pkt[9]) + } + if binary.BigEndian.Uint16(pkt[22:24]) != 40000 { + t.Fatalf("dst port = %d, want 40000", binary.BigEndian.Uint16(pkt[22:24])) + } + udpPayload := pkt[28:] + // DNS reply parses + if len(udpPayload) < 12 || binary.BigEndian.Uint16(udpPayload[0:2]) != 0x4242 { + t.Fatalf("bad DNS reply in packet") + } + + // non-DNS garbage -> no reply + rec.pkts = nil + m.onUDP(relay, 53, probe, 40000, []byte("garbage")) + if len(rec.pkts) != 0 { + t.Fatalf("garbage should not get a DNS reply") + } +} + +func TestMasqueradeOff(t *testing.T) { + m, err := newMasquerade("", 443) + if err != nil || m != nil { + t.Fatalf("off mode should return nil, nil; got %v, %v", m, err) + } + m, err = newMasquerade("off", 443) + if err != nil || m != nil { + t.Fatalf("off mode should return nil, nil; got %v, %v", m, err) + } +} + +// --- helpers --- + +type recordingFD struct { + pkts [][]byte +} + +func (r *recordingFD) fd() *rawSendFD { + rf := &rawSendFD{} + rf.sendFn = func(pkt []byte, _ netip.Addr) error { + r.pkts = append(r.pkts, append([]byte(nil), pkt...)) + return nil + } + return rf +} + +func buildDNSQuery(name string, qtype uint16, id uint16, flags uint16) []byte { + q := make([]byte, 12) + binary.BigEndian.PutUint16(q[0:], id) + binary.BigEndian.PutUint16(q[2:], flags) + binary.BigEndian.PutUint16(q[4:], 1) + for _, part := range stringsSplit(name, ".") { + q = append(q, byte(len(part))) + q = append(q, part...) + } + q = append(q, 0) + q = binary.BigEndian.AppendUint16(q, qtype) + q = binary.BigEndian.AppendUint16(q, 1) + return q +} + +func stringsSplit(s, sep string) []string { + var out []string + start := 0 + for i := 0; i+len(sep) <= len(s); i++ { + if s[i:i+len(sep)] == sep { + out = append(out, s[start:i]) + start = i + len(sep) + } + } + out = append(out, s[start:]) + return out +} + +func startsWith(s, prefix string) bool { + return len(s) >= len(prefix) && s[:len(prefix)] == prefix +} + +func contains(s, sub string) bool { + for i := 0; i+len(sub) <= len(s); i++ { + if s[i:i+len(sub)] == sub { + return true + } + } + return false +} diff --git a/transport/internet/finalmask/rawpacket/spoof_bpf_linux.go b/transport/internet/finalmask/rawpacket/spoof_bpf_linux.go new file mode 100644 index 000000000000..00cfa484ac43 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_bpf_linux.go @@ -0,0 +1,53 @@ +//go:build linux + +package rawpacket + +import ( + "log" + "unsafe" + + "golang.org/x/sys/unix" +) + +// sockFilter and sockFprog mirror the kernel's struct sock_filter / +// struct sock_fprog (classic BPF). SO_ATTACH_FILTER is a Linux-only +// socket option; other platforms build the stub below. +type sockFilter struct { + Code uint16 + Jt uint8 + Jf uint8 + K uint32 +} + +type sockFprog struct { + Len uint16 + Filter *sockFilter +} + +const ( + bpfLdAbs = 0x30 // BPF_LD|BPF_B|BPF_ABS: load byte at absolute offset + bpfJeq = 0x15 // BPF_JMP|BPF_JEQ|BPF_K + bpfRet = 0x06 // BPF_RET|BPF_K + bpfKeepAll = 0xffffffff + bpfDrop = 0 + soAttachFilter = 26 // SOL_SOCKET level option (Linux) +) + +// attachBPFFilter installs a filter that keeps only IPv4 packets whose +// protocol byte (offset 9) matches proto, dropping everything else in +// the kernel. Failures are logged but non-fatal: userspace already +// filters by protocol. +func attachBPFFilter(fd int, proto uint8) { + filter := []sockFilter{ + {Code: bpfLdAbs, K: 9}, // A = ip.proto + {Code: bpfJeq, Jt: 0, Jf: 1, K: uint32(proto)}, // if A == proto keep + {Code: bpfRet, K: bpfDrop}, // else drop + {Code: bpfRet, K: bpfKeepAll}, // keep + } + fprog := sockFprog{Len: uint16(len(filter)), Filter: &filter[0]} + _, _, errno := unix.Syscall6(unix.SYS_SETSOCKOPT, uintptr(fd), uintptr(unix.SOL_SOCKET), uintptr(soAttachFilter), + uintptr(unsafe.Pointer(&fprog)), unsafe.Sizeof(fprog), 0) + if errno != 0 { + log.Printf("[rawpacket] failed to attach BPF filter (proto %d): %v", proto, errno) + } +} diff --git a/transport/internet/finalmask/rawpacket/spoof_bpf_stub.go b/transport/internet/finalmask/rawpacket/spoof_bpf_stub.go new file mode 100644 index 000000000000..3bc6b70d762f --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_bpf_stub.go @@ -0,0 +1,7 @@ +//go:build !linux + +package rawpacket + +// attachBPFFilter is a no-op on platforms without SO_ATTACH_FILTER +// (darwin/freebsd raw sockets do not support classic-BPF attachment). +func attachBPFFilter(fd int, proto uint8) {} diff --git a/transport/internet/finalmask/rawpacket/spoof_conn.go b/transport/internet/finalmask/rawpacket/spoof_conn.go index f2b66458102b..b0d72b9c91d8 100644 --- a/transport/internet/finalmask/rawpacket/spoof_conn.go +++ b/transport/internet/finalmask/rawpacket/spoof_conn.go @@ -1,26 +1,50 @@ package rawpacket import ( + "errors" "fmt" "net" "net/netip" + "os" "sync" + "sync/atomic" "time" ) +// dialCounter round-robins the spoof source IP across connections so each +// session uses exactly one source address (per-session rotation instead of +// per-packet). This keeps the relay's (sessionID -> client address) +// mapping stable. +var dialCounter atomic.Uint64 + type SpoofConn struct { - sender SpoofSender - recver SpoofReceiver - relayIP netip.Addr - relayPort uint16 + sid [8]byte + crypto *frameCrypto + sender SpoofSender + tcp *TCPSimState + demux *frameDemux + recvCh chan demuxData + relayIP netip.Addr + relayP uint16 + + maxPayload int + + writeMu sync.Mutex - recvBuf []byte - readClosed bool + keepaliveStop chan struct{} + closeOnce sync.Once - closeOnce sync.Once + readDeadline atomic.Int64 // unixNano, 0 = none + writeDeadline atomic.Int64 } -func DialSpoof(relayAddr netip.AddrPort, spoofIPs []netip.Addr, srcPort uint16, ttl uint8, sendProto, recvProto string, peerSpoofIP netip.Addr) (net.Conn, error) { +func DialSpoof(relayAddr netip.AddrPort, spoofIPs []netip.Addr, srcPort uint16, ttl uint8, mtu uint32, sendProto, recvProto string, peerSpoofIP netip.Addr, psk []byte) (net.Conn, error) { + if len(psk) == 0 { + return nil, errors.New("rawpacket: auth (PSK) required in local mode") + } + if len(spoofIPs) == 0 { + return nil, errors.New("rawpacket: at least one spoof IP required") + } if sendProto == "" { sendProto = "tcp" } @@ -32,8 +56,19 @@ func DialSpoof(relayAddr netip.AddrPort, spoofIPs []netip.Addr, srcPort uint16, suppressICMPEchoReply() } + sid, err := newSessionID() + if err != nil { + return nil, fmt.Errorf("rawpacket: session id: %w", err) + } + crypto, err := newFrameCrypto(psk, sid) + if err != nil { + return nil, fmt.Errorf("rawpacket: frame crypto: %w", err) + } + + // One source IP per session: per-session rotation. + srcIP := spoofIPs[dialCounter.Add(1)%uint64(len(spoofIPs))] sender, err := NewSender(sendProto, &SpoofSenderConfig{ - SourceIPs: spoofIPs, + SourceIPs: []netip.Addr{srcIP}, SourcePort: srcPort, TTL: ttl, }) @@ -41,50 +76,130 @@ func DialSpoof(relayAddr netip.AddrPort, spoofIPs []netip.Addr, srcPort uint16, return nil, fmt.Errorf("rawpacket: create sender: %w", err) } - recver, err := NewReceiver(recvProto, &SpoofReceiverConfig{ - ListenPort: srcPort, - PeerSpoofIP: peerSpoofIP, - BufferSize: 4 * 1024 * 1024, - }) - if err != nil { + demux := getFrameDemux(recvProto, srcPort, peerSpoofIP) + recvCh := make(chan demuxData, 64) + if err := demux.register(sid, crypto, recvCh); err != nil { sender.Close() return nil, fmt.Errorf("rawpacket: create receiver: %w", err) } - return &SpoofConn{ - sender: sender, - recver: recver, - relayIP: relayAddr.Addr(), - relayPort: relayAddr.Port(), - recvBuf: make([]byte, 65536), - }, nil + c := &SpoofConn{ + sid: sid, + crypto: crypto, + sender: sender, + tcp: newTCPSimState(), + demux: demux, + recvCh: recvCh, + relayIP: relayAddr.Addr(), + relayP: relayAddr.Port(), + maxPayload: maxPayloadForMTU(mtu), + keepaliveStop: make(chan struct{}), + } + go c.keepaliveLoop() + return c, nil +} + +// maxPayloadForMTU bounds the frame payload so the largest possible wire +// packet (IPv4 + TCP with options + frame overhead) stays within mtu. +func maxPayloadForMTU(mtu uint32) int { + if mtu == 0 { + return frameMaxPayload + } + p := int(mtu) - 20 - 40 - frameOverhead + if p < 1 { + p = 1 + } + if p > frameMaxPayload { + p = frameMaxPayload + } + return p } func (c *SpoofConn) Write(b []byte) (int, error) { if len(b) == 0 { return 0, nil } - if err := c.sender.Send(b, c.relayIP, c.relayPort); err != nil { - return 0, err + if dl := c.writeDeadline.Load(); dl != 0 { + if time.Now().UnixNano() >= dl { + return 0, os.ErrDeadlineExceeded + } + } + c.writeMu.Lock() + defer c.writeMu.Unlock() + + written := 0 + for len(b) > 0 { + chunk := b + if len(chunk) > c.maxPayload { + chunk = chunk[:c.maxPayload] + } + frame, err := c.crypto.seal(false, chunk, false) + if err != nil { + return written, err + } + if err := c.sender.Send(frame, c.relayIP, c.relayP, c.tcp); err != nil { + return written, err + } + written += len(chunk) + b = b[len(chunk):] } - return len(b), nil + return written, nil } func (c *SpoofConn) Read(buf []byte) (int, error) { - data, _, _, err := c.recver.Receive() - if err != nil { - return 0, err + dl := c.readDeadline.Load() + if dl != 0 && time.Now().UnixNano() >= dl { + return 0, os.ErrDeadlineExceeded } - if len(data) == 0 { - return 0, nil + + var timer *time.Timer + var timeout <-chan time.Time + if dl != 0 { + timer = time.NewTimer(time.Until(time.Unix(0, dl))) + timeout = timer.C + defer timer.Stop() + } + + select { + case data, ok := <-c.recvCh: + if !ok { + return 0, errors.New("rawpacket: connection closed") + } + if data.tcp != nil && data.tcp.Flags != 0 { + c.tcp.observePeer(data.tcp.Seq, len(data.payload)) + } + return copy(buf, data.payload), nil + case <-timeout: + return 0, os.ErrDeadlineExceeded + } +} + +// keepaliveLoop keeps the relay session alive during idle periods so the +// relay's garbage collector does not reap an idle but healthy connection. +func (c *SpoofConn) keepaliveLoop() { + t := time.NewTicker(25 * time.Second) + defer t.Stop() + for { + select { + case <-c.keepaliveStop: + return + case <-t.C: + c.writeMu.Lock() + frame, err := c.crypto.seal(false, nil, true) + if err == nil { + _ = c.sender.Send(frame, c.relayIP, c.relayP, c.tcp) + } + c.writeMu.Unlock() + } } - n := copy(buf, data) - return n, nil } func (c *SpoofConn) Close() error { - c.sender.Close() - c.recver.Close() + c.closeOnce.Do(func() { + close(c.keepaliveStop) + c.demux.unregister(c.sid) + c.sender.Close() + }) return nil } @@ -93,13 +208,33 @@ func (c *SpoofConn) LocalAddr() net.Addr { } func (c *SpoofConn) RemoteAddr() net.Addr { - return &net.TCPAddr{IP: c.relayIP.AsSlice(), Port: int(c.relayPort)} + return &net.TCPAddr{IP: c.relayIP.AsSlice(), Port: int(c.relayP)} } -func (c *SpoofConn) SetDeadline(t time.Time) error { return nil } -func (c *SpoofConn) SetReadDeadline(t time.Time) error { return nil } -func (c *SpoofConn) SetWriteDeadline(t time.Time) error { return nil } +func setDeadline(d *atomic.Int64, t time.Time) error { + if t.IsZero() { + d.Store(0) + return nil + } + d.Store(t.UnixNano()) + return nil +} + +func (c *SpoofConn) SetDeadline(t time.Time) error { + if err := setDeadline(&c.readDeadline, t); err != nil { + return err + } + return setDeadline(&c.writeDeadline, t) +} + +func (c *SpoofConn) SetReadDeadline(t time.Time) error { + return setDeadline(&c.readDeadline, t) +} + +func (c *SpoofConn) SetWriteDeadline(t time.Time) error { + return setDeadline(&c.writeDeadline, t) +} -func (c *SpoofConn) TcpMaskConn() {} +func (c *SpoofConn) TcpMaskConn() {} func (c *SpoofConn) RawConn() net.Conn { return nil } -func (c *SpoofConn) Splice() bool { return false } +func (c *SpoofConn) Splice() bool { return false } diff --git a/transport/internet/finalmask/rawpacket/spoof_ip.go b/transport/internet/finalmask/rawpacket/spoof_ip.go index 663310d862ab..505601372142 100644 --- a/transport/internet/finalmask/rawpacket/spoof_ip.go +++ b/transport/internet/finalmask/rawpacket/spoof_ip.go @@ -5,17 +5,25 @@ import ( "net/netip" ) -func BuildIPv4Header(totalLen uint16, id uint16, ttl uint8, protocol uint8, src, dst netip.Addr) []byte { +// BuildIPv4Header builds a 20-byte IPv4 header. df sets the Don't +// Fragment bit (real hosts set it; prevents MTU-blackhole fragmentation). +func BuildIPv4Header(totalLen uint16, id uint16, ttl uint8, protocol uint8, src, dst netip.Addr, df bool) []byte { b := make([]byte, 20) b[0] = (4 << 4) | 5 b[1] = 0 binary.BigEndian.PutUint16(b[2:], totalLen) binary.BigEndian.PutUint16(b[4:], id) - binary.BigEndian.PutUint16(b[6:], 0) + if df { + binary.BigEndian.PutUint16(b[6:], 0x4000) + } else { + binary.BigEndian.PutUint16(b[6:], 0) + } b[8] = ttl b[9] = protocol copy(b[12:16], src.AsSlice()) copy(b[16:20], dst.AsSlice()) + csum := Checksum(b[:20], 0) + binary.BigEndian.PutUint16(b[10:], ^csum) return b } @@ -65,5 +73,3 @@ func IPv6PseudoHeaderChecksum(src, dst netip.Addr, protocol uint8, totalLen uint } return uint16(csum) } - - diff --git a/transport/internet/finalmask/rawpacket/spoof_rawsend.go b/transport/internet/finalmask/rawpacket/spoof_rawsend.go index 11511b1b3a33..1c10faef91b0 100644 --- a/transport/internet/finalmask/rawpacket/spoof_rawsend.go +++ b/transport/internet/finalmask/rawpacket/spoof_rawsend.go @@ -13,8 +13,10 @@ import ( type rawSendFD struct { fd int sockAddr unix.Sockaddr - mu sync.Mutex - closed bool + // sendFn, when set, replaces the real send path (used by tests). + sendFn func(pkt []byte, dstIP netip.Addr) error + mu sync.Mutex + closed bool } func openRawSender(dstIP netip.Addr) (*rawSendFD, error) { @@ -22,6 +24,20 @@ func openRawSender(dstIP netip.Addr) (*rawSendFD, error) { return nil, fmt.Errorf("rawpacket: IPv6 raw sender not yet supported") } + fd, err := openRawSenderAny() + if err != nil { + return nil, err + } + sa := &unix.SockaddrInet4{} + sa.Addr = dstIP.As4() + fd.sockAddr = sa + return fd, nil +} + +// openRawSenderAny opens a raw sending socket without binding it to a +// fixed destination. Used by the masquerade responder, which must send +// to arbitrary probe addresses. +func openRawSenderAny() (*rawSendFD, error) { fd, err := unix.Socket(unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_RAW) if err != nil { return nil, fmt.Errorf("rawpacket: open SOCK_RAW: %w", err) @@ -33,10 +49,7 @@ func openRawSender(dstIP netip.Addr) (*rawSendFD, error) { } _ = unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_SNDBUF, 4*1024*1024) - - sa := &unix.SockaddrInet4{} - sa.Addr = dstIP.As4() - return &rawSendFD{fd: fd, sockAddr: sa}, nil + return &rawSendFD{fd: fd}, nil } func (r *rawSendFD) send(packet []byte) error { @@ -45,9 +58,30 @@ func (r *rawSendFD) send(packet []byte) error { if r.closed { return fmt.Errorf("rawpacket: raw sender closed") } + if r.sockAddr == nil { + return fmt.Errorf("rawpacket: raw sender has no destination") + } return unix.Sendto(r.fd, packet, 0, r.sockAddr) } +// sendTo sends packet to dstIP, building the socket address per call. +func (r *rawSendFD) sendTo(packet []byte, dstIP netip.Addr) error { + if r.sendFn != nil { + return r.sendFn(packet, dstIP) + } + if !dstIP.Is4() { + return fmt.Errorf("rawpacket: IPv6 raw sender not yet supported") + } + r.mu.Lock() + defer r.mu.Unlock() + if r.closed { + return fmt.Errorf("rawpacket: raw sender closed") + } + sa := &unix.SockaddrInet4{} + sa.Addr = dstIP.As4() + return unix.Sendto(r.fd, packet, 0, sa) +} + func (r *rawSendFD) close() error { r.mu.Lock() defer r.mu.Unlock() diff --git a/transport/internet/finalmask/rawpacket/spoof_rawsend_stub.go b/transport/internet/finalmask/rawpacket/spoof_rawsend_stub.go index 1ba88f076198..1d59e4cfc6e6 100644 --- a/transport/internet/finalmask/rawpacket/spoof_rawsend_stub.go +++ b/transport/internet/finalmask/rawpacket/spoof_rawsend_stub.go @@ -13,10 +13,18 @@ func openRawSender(dstIP netip.Addr) (*rawSendFD, error) { return nil, fmt.Errorf("rawpacket: raw sockets not supported on this platform") } +func openRawSenderAny() (*rawSendFD, error) { + return nil, fmt.Errorf("rawpacket: raw sockets not supported on this platform") +} + func (r *rawSendFD) send(packet []byte) error { return fmt.Errorf("rawpacket: raw sockets not supported on this platform") } +func (r *rawSendFD) sendTo(packet []byte, dstIP netip.Addr) error { + return fmt.Errorf("rawpacket: raw sockets not supported on this platform") +} + func (r *rawSendFD) close() error { return nil } diff --git a/transport/internet/finalmask/rawpacket/spoof_rawsend_windows.go b/transport/internet/finalmask/rawpacket/spoof_rawsend_windows.go index fae1986264a6..c5a29e7e0db1 100644 --- a/transport/internet/finalmask/rawpacket/spoof_rawsend_windows.go +++ b/transport/internet/finalmask/rawpacket/spoof_rawsend_windows.go @@ -11,14 +11,21 @@ import ( ) type rawSendFD struct { - h *windivert.Handle + h *windivert.Handle + // sendFn, when set, replaces the real send path (used by tests). + sendFn func(pkt []byte, dstIP netip.Addr) error mu sync.Mutex closed bool } func openRawSender(dstIP netip.Addr) (*rawSendFD, error) { - filter := fmt.Sprintf("outbound and ip.DstAddr == %s", dstIP.String()) - h, err := windivert.Open(filter, windivert.LayerNetwork, windivert.PriorityLowest, uint64(windivert.FlagSendOnly)) + return openRawSenderAny() +} + +// openRawSenderAny opens a WinDivert send handle. WinDivert injects +// packets from the header contents, so no destination binding is needed. +func openRawSenderAny() (*rawSendFD, error) { + h, err := windivert.Open(windivert.AcceptAll(), windivert.LayerNetwork, windivert.PriorityLowest, windivert.FlagSendOnly) if err != nil { return nil, fmt.Errorf("rawpacket: WinDivert open: %w", err) } @@ -26,6 +33,15 @@ func openRawSender(dstIP netip.Addr) (*rawSendFD, error) { } func (r *rawSendFD) send(packet []byte) error { + return r.sendTo(packet, netip.Addr{}) +} + +// sendTo sends packet via WinDivert; the destination is read from the +// packet itself. +func (r *rawSendFD) sendTo(packet []byte, dstIP netip.Addr) error { + if r.sendFn != nil { + return r.sendFn(packet, dstIP) + } r.mu.Lock() defer r.mu.Unlock() if r.closed { diff --git a/transport/internet/finalmask/rawpacket/spoof_receiver.go b/transport/internet/finalmask/rawpacket/spoof_receiver.go index be547962e091..af3814b03092 100644 --- a/transport/internet/finalmask/rawpacket/spoof_receiver.go +++ b/transport/internet/finalmask/rawpacket/spoof_receiver.go @@ -1,80 +1,56 @@ package rawpacket import ( - "fmt" + "errors" "net/netip" - - "golang.org/x/sys/unix" ) -type rawRecvSocket struct { - fd int - buf []byte - closed bool - proto uint8 -} +var errReceiverClosed = errors.New("rawpacket: receiver closed") -func newRawRecvSocket(domain, proto int, bufSize int) (*rawRecvSocket, error) { - fd, err := unix.Socket(domain, unix.SOCK_RAW, proto) - if err != nil { - return nil, fmt.Errorf("rawpacket: socket: %w", err) - } - if bufSize > 0 { - _ = unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_RCVBUF, bufSize) - } - // 1-second timeout for clean shutdown - tv := unix.Timeval{Sec: 1, Usec: 0} - _ = unix.SetsockoptTimeval(fd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv) - return &rawRecvSocket{fd: fd, buf: make([]byte, 65536)}, nil -} - -func (r *rawRecvSocket) recv() ([]byte, bool) { - n, _, err := unix.Recvfrom(r.fd, r.buf, 0) - if err != nil { - return nil, false - } - if n == 0 { - return nil, false - } - out := make([]byte, n) - copy(out, r.buf[:n]) - return out, true -} - -func (r *rawRecvSocket) close() { - if !r.closed { - r.closed = true - unix.Close(r.fd) - } +// rawPktRecv is the platform-specific raw socket: unix SOCK_RAW on +// darwin/freebsd/linux, WinDivert on windows (amd64/386). +type rawPktRecv interface { + // recv returns a copy of one raw IP packet. ok is false when the + // socket is closed or a transient read failure occurred (callers + // must tolerate spurious false returns and re-try). + recv() (pkt []byte, ok bool) + // closed reports whether the socket has been closed (recv will no + // longer produce packets and errReceiverClosed is the permanent + // outcome). + closed() bool + close() } type tcpReceiver struct { - raw *rawRecvSocket + raw rawPktRecv cfg *SpoofReceiverConfig } func newTCPReceiver(cfg *SpoofReceiverConfig) (*tcpReceiver, error) { - raw, err := newRawRecvSocket(unix.AF_INET, unix.IPPROTO_TCP, cfg.BufferSize) + raw, err := newRawRecvSocket(ProtocolTCP, cfg.BufferSize) if err != nil { return nil, err } return &tcpReceiver{raw: raw, cfg: cfg}, nil } -func (r *tcpReceiver) Receive() ([]byte, netip.Addr, uint16, error) { +func (r *tcpReceiver) Receive() ([]byte, netip.Addr, uint16, *TCPMeta, error) { for { pkt, ok := r.raw.recv() if !ok { + if r.raw.closed() { + return nil, netip.Addr{}, 0, nil, errReceiverClosed + } continue } - _, flags, payload, srcIP, _, srcPort, dstPort, ok := ParseRawTCPPacket(pkt) - if !ok || dstPort != r.cfg.ListenPort || flags&TCPFlagSyn == 0 { + seq, flags, payload, srcIP, dstIP, srcPort, dstPort, ok := ParseRawTCPPacket(pkt) + if !ok || dstPort != r.cfg.ListenPort { continue } if r.cfg.PeerSpoofIP.IsValid() && srcIP != r.cfg.PeerSpoofIP { continue } - return payload, srcIP, srcPort, nil + return payload, srcIP, srcPort, &TCPMeta{Seq: seq, Flags: flags, DstIP: dstIP, DstPort: dstPort}, nil } } @@ -84,33 +60,36 @@ func (r *tcpReceiver) Close() error { } type udpReceiver struct { - raw *rawRecvSocket + raw rawPktRecv cfg *SpoofReceiverConfig } func newUDPReceiver(cfg *SpoofReceiverConfig) (*udpReceiver, error) { - raw, err := newRawRecvSocket(unix.AF_INET, unix.IPPROTO_UDP, cfg.BufferSize) + raw, err := newRawRecvSocket(ProtocolUDP, cfg.BufferSize) if err != nil { return nil, err } return &udpReceiver{raw: raw, cfg: cfg}, nil } -func (r *udpReceiver) Receive() ([]byte, netip.Addr, uint16, error) { +func (r *udpReceiver) Receive() ([]byte, netip.Addr, uint16, *TCPMeta, error) { for { pkt, ok := r.raw.recv() if !ok { + if r.raw.closed() { + return nil, netip.Addr{}, 0, nil, errReceiverClosed + } continue } payload, srcPort, dstPort, ok := ParseUDPPacket(pkt) if !ok || dstPort != r.cfg.ListenPort { continue } - srcIP, _, _ := ParseSrcIP(pkt, false) + srcIP, dstIP, _ := ParseSrcIP(pkt, false) if r.cfg.PeerSpoofIP.IsValid() && srcIP != r.cfg.PeerSpoofIP { continue } - return payload, srcIP, srcPort, nil + return payload, srcIP, srcPort, &TCPMeta{DstIP: dstIP, DstPort: dstPort}, nil } } @@ -120,22 +99,25 @@ func (r *udpReceiver) Close() error { } type icmpReceiver struct { - raw *rawRecvSocket + raw rawPktRecv cfg *SpoofReceiverConfig } func newICMPReceiver(cfg *SpoofReceiverConfig) (*icmpReceiver, error) { - raw, err := newRawRecvSocket(unix.AF_INET, unix.IPPROTO_ICMP, cfg.BufferSize) + raw, err := newRawRecvSocket(ProtocolICMP, cfg.BufferSize) if err != nil { return nil, err } return &icmpReceiver{raw: raw, cfg: cfg}, nil } -func (r *icmpReceiver) Receive() ([]byte, netip.Addr, uint16, error) { +func (r *icmpReceiver) Receive() ([]byte, netip.Addr, uint16, *TCPMeta, error) { for { pkt, ok := r.raw.recv() if !ok { + if r.raw.closed() { + return nil, netip.Addr{}, 0, nil, errReceiverClosed + } continue } id, _, payload, ok := ParseICMPv4Echo(pkt) @@ -146,7 +128,7 @@ func (r *icmpReceiver) Receive() ([]byte, netip.Addr, uint16, error) { if r.cfg.PeerSpoofIP.IsValid() && srcIP != r.cfg.PeerSpoofIP { continue } - return payload, srcIP, id, nil + return payload, srcIP, id, nil, nil } } @@ -156,23 +138,26 @@ func (r *icmpReceiver) Close() error { } type icmpv6Receiver struct { - raw *rawRecvSocket + raw rawPktRecv cfg *SpoofReceiverConfig } func newICMPv6Receiver(cfg *SpoofReceiverConfig) (*icmpv6Receiver, error) { // Non-standard: protocol 58 on IPv4 (same as reference) - raw, err := newRawRecvSocket(unix.AF_INET, int(ProtocolICMPv6), cfg.BufferSize) + raw, err := newRawRecvSocket(ProtocolICMPv6, cfg.BufferSize) if err != nil { return nil, err } return &icmpv6Receiver{raw: raw, cfg: cfg}, nil } -func (r *icmpv6Receiver) Receive() ([]byte, netip.Addr, uint16, error) { +func (r *icmpv6Receiver) Receive() ([]byte, netip.Addr, uint16, *TCPMeta, error) { for { pkt, ok := r.raw.recv() if !ok { + if r.raw.closed() { + return nil, netip.Addr{}, 0, nil, errReceiverClosed + } continue } id, _, payload, ok := ParseICMPv6Echo(pkt) @@ -183,7 +168,7 @@ func (r *icmpv6Receiver) Receive() ([]byte, netip.Addr, uint16, error) { if r.cfg.PeerSpoofIP.IsValid() && srcIP != r.cfg.PeerSpoofIP { continue } - return payload, srcIP, id, nil + return payload, srcIP, id, nil, nil } } diff --git a/transport/internet/finalmask/rawpacket/spoof_recv_stub.go b/transport/internet/finalmask/rawpacket/spoof_recv_stub.go new file mode 100644 index 000000000000..419237d0fab2 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_recv_stub.go @@ -0,0 +1,21 @@ +//go:build !darwin && !freebsd && !linux && !(windows && (amd64 || 386)) + +package rawpacket + +import "fmt" + +type rawRecvSocket struct{} + +func newRawRecvSocket(proto uint8, bufSize int) (*rawRecvSocket, error) { + return nil, fmt.Errorf("rawpacket: raw sockets not supported on this platform") +} + +func (r *rawRecvSocket) recv() ([]byte, bool) { + return nil, false +} + +func (r *rawRecvSocket) closed() bool { + return true +} + +func (r *rawRecvSocket) close() {} diff --git a/transport/internet/finalmask/rawpacket/spoof_recv_unix.go b/transport/internet/finalmask/rawpacket/spoof_recv_unix.go new file mode 100644 index 000000000000..1dda4aead7f3 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_recv_unix.go @@ -0,0 +1,63 @@ +//go:build darwin || freebsd || linux + +package rawpacket + +import ( + "fmt" + "runtime" + + "golang.org/x/sys/unix" +) + +type rawRecvSocket struct { + fd int + buf []byte + closedFlag bool + proto uint8 +} + +func newRawRecvSocket(proto uint8, bufSize int) (*rawRecvSocket, error) { + fd, err := unix.Socket(unix.AF_INET, unix.SOCK_RAW, int(proto)) + if err != nil { + return nil, fmt.Errorf("rawpacket: socket: %w", err) + } + if bufSize > 0 { + _ = unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_RCVBUF, bufSize) + } + // 1-second timeout for clean shutdown + tv := unix.Timeval{Sec: 1, Usec: 0} + _ = unix.SetsockoptTimeval(fd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv) + // Linux only: attach a classic-BPF filter so the kernel drops + // non-matching protocol traffic before userspace sees it. + if runtime.GOOS == "linux" { + attachBPFFilter(fd, proto) + } + return &rawRecvSocket{fd: fd, buf: make([]byte, 65536), proto: proto}, nil +} + +func (r *rawRecvSocket) recv() ([]byte, bool) { + if r.closedFlag { + return nil, false + } + n, _, err := unix.Recvfrom(r.fd, r.buf, 0) + if err != nil { + return nil, false + } + if n == 0 { + return nil, false + } + out := make([]byte, n) + copy(out, r.buf[:n]) + return out, true +} + +func (r *rawRecvSocket) closed() bool { + return r.closedFlag +} + +func (r *rawRecvSocket) close() { + if !r.closedFlag { + r.closedFlag = true + unix.Close(r.fd) + } +} diff --git a/transport/internet/finalmask/rawpacket/spoof_recv_windows.go b/transport/internet/finalmask/rawpacket/spoof_recv_windows.go new file mode 100644 index 000000000000..a5fec04068ae --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_recv_windows.go @@ -0,0 +1,59 @@ +//go:build windows && (amd64 || 386) + +package rawpacket + +import ( + "fmt" + "sync" + + "github.com/xtls/xray-core/transport/internet/finalmask/rawpacket/windivert" +) + +type rawRecvSocket struct { + h *windivert.Handle + buf []byte + mu sync.Mutex + closedFlag bool + proto uint8 +} + +func newRawRecvSocket(proto uint8, bufSize int) (*rawRecvSocket, error) { + h, err := windivert.Open(windivert.AcceptAll(), windivert.LayerNetwork, windivert.PriorityLowest, windivert.FlagSniff) + if err != nil { + return nil, fmt.Errorf("rawpacket: WinDivert open: %w", err) + } + return &rawRecvSocket{h: h, buf: make([]byte, windivert.MTUMax), proto: proto}, nil +} + +func (r *rawRecvSocket) recv() ([]byte, bool) { + r.mu.Lock() + defer r.mu.Unlock() + if r.closedFlag { + return nil, false + } + n, _, err := r.h.Recv(r.buf) + if err != nil { + return nil, false + } + if n == 0 { + return nil, false + } + out := make([]byte, n) + copy(out, r.buf[:n]) + return out, true +} + +func (r *rawRecvSocket) closed() bool { + r.mu.Lock() + defer r.mu.Unlock() + return r.closedFlag +} + +func (r *rawRecvSocket) close() { + r.mu.Lock() + defer r.mu.Unlock() + if !r.closedFlag { + r.closedFlag = true + _ = r.h.Close() + } +} diff --git a/transport/internet/finalmask/rawpacket/spoof_relay.go b/transport/internet/finalmask/rawpacket/spoof_relay.go index 4312e5f18cc8..fe82da0d0767 100644 --- a/transport/internet/finalmask/rawpacket/spoof_relay.go +++ b/transport/internet/finalmask/rawpacket/spoof_relay.go @@ -1,7 +1,7 @@ package rawpacket import ( - "io" + "errors" "net" "net/netip" "sync" @@ -10,20 +10,28 @@ import ( type Relay struct { cfg *RelayConfig + psk []byte recver SpoofReceiver sender SpoofSender done chan struct{} closeOnce sync.Once icmpSuppressed bool + maxPayload int + sessionTimeout time.Duration + rstManaged bool + masq *masquerade - // UDP forwarding (reference mode) - targetUDPConn *net.UDPConn - fwdUDPAddr *net.UDPAddr - - // TCP forwarding (Xray mode) man *SessionManager + + recentSIDs map[[8]byte]time.Time + recentMu sync.Mutex } +const ( + relaySessionTimeout = 120 * time.Second + relaySIDRemember = 10 * time.Minute +) + func NewRelay(cfg *RelayConfig) (*Relay, error) { if cfg.SendTransport == "" { cfg.SendTransport = "udp" @@ -31,6 +39,9 @@ func NewRelay(cfg *RelayConfig) (*Relay, error) { if cfg.RecvTransport == "" { cfg.RecvTransport = "tcp" } + if len(cfg.Auth) == 0 { + return nil, errors.New("rawpacket: auth (PSK) required in remote mode") + } if cfg.RecvTransport == "icmp" || cfg.RecvTransport == "icmpv6" { if suppressICMPEchoReply() { @@ -65,6 +76,7 @@ func NewRelay(cfg *RelayConfig) (*Relay, error) { SourceIPs: spoofIPs, SourcePort: cfg.SpoofPort, TTL: 64, + Server: true, }) if err != nil { recver.Close() @@ -76,52 +88,38 @@ func NewRelay(cfg *RelayConfig) (*Relay, error) { r := &Relay{ cfg: cfg, + psk: []byte(cfg.Auth), recver: recver, sender: sender, done: make(chan struct{}), icmpSuppressed: cfg.icmpSuppressed, + maxPayload: maxPayloadForMTU(cfg.Mtu), + sessionTimeout: relaySessionTimeout, + man: NewSessionManager(), + recentSIDs: make(map[[8]byte]time.Time), } - - if cfg.ForwardTransport == "tcp" { - // TCP mode: create session manager for Xray integration - r.man = NewSessionManager() - } else { - // UDP mode: match reference behavior - fwdAddr, err := net.ResolveUDPAddr("udp4", cfg.ForwardAddr) - if err != nil { - recver.Close() - sender.Close() - if cfg.icmpSuppressed { - restoreICMPEchoReply() - } - return nil, err - } - udpConn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) - if err != nil { - recver.Close() - sender.Close() - if cfg.icmpSuppressed { - restoreICMPEchoReply() - } - return nil, err - } - r.targetUDPConn = udpConn - r.fwdUDPAddr = fwdAddr + if cfg.SuppressRst { + r.rstManaged = suppressKernelRST(cfg.ListenPort) } - + masq, masqErr := newMasquerade(cfg.Masquerade, cfg.ListenPort) + if masqErr != nil { + r.Close() + return nil, masqErr + } + r.masq = masq return r, nil } func (r *Relay) Run() { go r.uplinkLoop() - if r.man != nil { - go r.forwardResponses() - } else { - go r.downlinkLoop() - } + go r.janitorLoop() <-r.done } +func (r *Relay) dialTarget() (net.Conn, error) { + return net.DialTimeout(r.cfg.ForwardTransport, r.cfg.ForwardAddr, 10*time.Second) +} + func (r *Relay) uplinkLoop() { for { select { @@ -130,94 +128,212 @@ func (r *Relay) uplinkLoop() { default: } - data, srcIP, srcPort, err := r.recver.Receive() + pkt, srcIP, srcPort, tcp, err := r.recver.Receive() if err != nil { return } - if len(data) == 0 { + if len(pkt) == 0 { + // A TCP segment without payload is never a tunnel frame: + // bare SYNs are handshake probes from scanners. + if r.masq != nil && tcp != nil && tcp.Flags&TCPFlagSyn != 0 && tcp.Flags&TCPFlagAck == 0 { + r.masq.onTCPSYN(tcp.DstIP, tcp.DstPort, srcIP, srcPort, tcp.Seq) + } continue } + r.handleFrame(pkt, srcIP, srcPort, tcp) + } +} - if r.man != nil { - // TCP mode: forward to target via TCP with session - r.handleTCPForward(data, srcIP, srcPort) - } else { - // UDP mode: forward to target via UDP (reference behavior) - if _, err := r.targetUDPConn.WriteToUDP(data, r.fwdUDPAddr); err != nil { - continue - } +func (r *Relay) handleFrame(pkt []byte, srcIP netip.Addr, srcPort uint16, tcp *TCPMeta) { + sid, ok := frameSessionID(pkt) + if !ok { + r.masqueradeProbe(pkt, srcIP, srcPort, tcp) + return + } + s := r.man.Get(sid) + if s == nil { + if !r.handleNewSession(sid, pkt, srcIP, srcPort) { + r.masqueradeProbe(pkt, srcIP, srcPort, tcp) } + return + } + if !r.deliverToSession(s, pkt, tcp) { + r.masqueradeProbe(pkt, srcIP, srcPort, tcp) } } -func (r *Relay) handleTCPForward(data []byte, srcIP netip.Addr, srcPort uint16) { - session := r.man.Get(srcIP, srcPort) - if session == nil { - targetConn, err := net.DialTimeout("tcp", r.cfg.ForwardAddr, 10*time.Second) - if err != nil { - return +// masqueradeProbe answers packets that do not belong to any tunnel +// session (failed frame parse or decryption) with fake service traffic. +func (r *Relay) masqueradeProbe(pkt []byte, srcIP netip.Addr, srcPort uint16, tcp *TCPMeta) { + if r.masq == nil || tcp == nil { + return + } + if tcp.Flags != 0 { + r.masq.onTCPData(tcp.DstIP, tcp.DstPort, srcIP, srcPort, tcp.Seq, pkt) + } else { + r.masq.onUDP(tcp.DstIP, tcp.DstPort, srcIP, srcPort, pkt) + } +} + +func (r *Relay) handleNewSession(sid [8]byte, pkt []byte, srcIP netip.Addr, srcPort uint16) bool { + if r.recentSIDSeen(sid) { + // Replay of a session that was already torn down: drop. + return false + } + crypto, err := newFrameCrypto(r.psk, sid) + if err != nil { + return false + } + payload, flags, err := crypto.open(pkt, false) + if err != nil { + return false + } + if flags&frameFlagKeepalive != 0 { + // A keepalive for a session that no longer exists: ignore. + return false + } + targetConn, err := r.dialTarget() + if err != nil { + return false + } + s := r.man.Add(sid, srcIP, srcPort, targetConn, crypto, newTCPSimState()) + r.rememberSID(sid) + go r.forwardSession(s) + if len(payload) > 0 { + writeTarget(s, payload) + } + return true +} + +func (r *Relay) deliverToSession(s *RelaySession, pkt []byte, tcp *TCPMeta) bool { + s.mu.Lock() + if s.closed { + s.mu.Unlock() + return false + } + s.mu.Unlock() + + payload, _, err := s.crypto.open(pkt, false) + if err != nil { + return false + } + if tcp != nil && tcp.Flags != 0 { + s.tcp.observeClientSeq(tcp.Seq) + } + s.mu.Lock() + s.LastSeen = time.Now() + s.mu.Unlock() + + if len(payload) > 0 { + if !writeTarget(s, payload) { + r.man.Remove(s.ID) + return false } - session = r.man.Add(srcIP, srcPort, targetConn, r.cfg.ClientIP) } - session.mu.Lock() - defer session.mu.Unlock() - if !session.closed { - session.TargetConn.SetWriteDeadline(time.Now().Add(10 * time.Second)) - _, _ = session.TargetConn.Write(data) + return true +} + +// writeTarget writes the whole payload to the target connection, handling +// partial writes. Returns false if the write failed (caller should remove +// the session). +func writeTarget(s *RelaySession, data []byte) bool { + if s.TargetConn == nil { + return false + } + _ = s.TargetConn.SetWriteDeadline(time.Now().Add(30 * time.Second)) + n := 0 + for n < len(data) { + m, err := s.TargetConn.Write(data[n:]) + if err != nil { + return false + } + if m == 0 { + return false + } + n += m } + return true } -func (r *Relay) downlinkLoop() { - buf := make([]byte, 65536) +// forwardSession pumps target->client data for one session. It exits on +// read error or EOF and removes the session. +func (r *Relay) forwardSession(s *RelaySession) { + buf := make([]byte, 64*1024) for { - select { - case <-r.done: + n, err := s.TargetConn.Read(buf) + if err != nil { + r.man.Remove(s.ID) return - default: } - - n, _, err := r.targetUDPConn.ReadFromUDP(buf) - if err != nil { + if n == 0 { continue } - if n == 0 { + frames, ferr := s.crypto.sealSplit(true, buf[:n], r.maxPayload, false) + if ferr != nil { continue } - _ = r.sender.Send(buf[:n], r.cfg.ClientIP, r.cfg.ClientPort) + ok := true + for _, f := range frames { + if serr := r.sender.Send(f, s.ClientIP, s.ClientPort, s.tcp); serr != nil { + ok = false + break + } + } + if ok { + s.mu.Lock() + s.LastSeen = time.Now() + s.mu.Unlock() + } else { + r.man.Remove(s.ID) + return + } } } -func (r *Relay) forwardResponses() { +// janitorLoop reaps sessions idle for longer than sessionTimeout and +// prunes the recent-session-ID anti-replay set. +func (r *Relay) janitorLoop() { + t := time.NewTicker(15 * time.Second) + defer t.Stop() for { select { case <-r.done: return - default: - } - - for _, s := range r.man.All() { - s.mu.Lock() - if s.closed { + case <-t.C: + now := time.Now() + for _, s := range r.man.All() { + s.mu.Lock() + idle := now.Sub(s.LastSeen) s.mu.Unlock() - continue - } - buf := make([]byte, 65536) - s.TargetConn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) - n, err := s.TargetConn.Read(buf) - if err != nil { - if err == io.EOF { - s.closed = true - s.TargetConn.Close() + if idle > r.sessionTimeout { + r.man.Remove(s.ID) } - s.mu.Unlock() - continue - } - s.mu.Unlock() - if n > 0 { - _ = r.sender.Send(buf[:n], s.ClientIP, s.ClientPort) } + r.pruneRecentSIDs(now) + } + } +} + +func (r *Relay) rememberSID(sid [8]byte) { + r.recentMu.Lock() + r.recentSIDs[sid] = time.Now() + r.recentMu.Unlock() +} + +func (r *Relay) recentSIDSeen(sid [8]byte) bool { + r.recentMu.Lock() + defer r.recentMu.Unlock() + _, ok := r.recentSIDs[sid] + return ok +} + +func (r *Relay) pruneRecentSIDs(now time.Time) { + r.recentMu.Lock() + defer r.recentMu.Unlock() + for sid, t := range r.recentSIDs { + if now.Sub(t) > relaySIDRemember { + delete(r.recentSIDs, sid) } - time.Sleep(50 * time.Millisecond) } } @@ -226,14 +342,12 @@ func (r *Relay) Close() { close(r.done) r.recver.Close() r.sender.Close() - if r.targetUDPConn != nil { - r.targetUDPConn.Close() - } - if r.man != nil { - r.man.Close() - } + r.man.Close() if r.icmpSuppressed { restoreICMPEchoReply() } + if r.rstManaged { + restoreKernelRST(r.cfg.ListenPort, true) + } }) } diff --git a/transport/internet/finalmask/rawpacket/spoof_rst_linux.go b/transport/internet/finalmask/rawpacket/spoof_rst_linux.go new file mode 100644 index 000000000000..a87d2bb82296 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_rst_linux.go @@ -0,0 +1,42 @@ +package rawpacket + +import ( + "log" + "os/exec" + "strconv" +) + +// suppressKernelRST installs an iptables rule that drops the kernel's +// RST replies for the relay listen port (the fake TCP handshake has no +// kernel socket behind it, so the kernel would answer our own SYN with +// RST). Only RSTs matching the relay port are dropped; the raw socket's +// spoofed packets are unaffected. Returns true when the rule was +// installed. +func suppressKernelRST(port uint16) bool { + if port == 0 { + return false + } + rule := []string{"-I", "OUTPUT", "-p", "tcp", "--tcp-flags", "RST", "RST", "--sport", strconv.Itoa(int(port)), "-j", "DROP"} + err := exec.Command("iptables", rule...).Run() + if err != nil { + log.Printf("[rawpacket] failed to suppress kernel RST on port %d: %v", port, err) + return false + } + log.Printf("[rawpacket] suppressed kernel RST on port %d", port) + return true +} + +// restoreKernelRST removes the rule installed by suppressKernelRST. +// managed must be the value returned by suppressKernelRST. +func restoreKernelRST(port uint16, managed bool) { + if !managed || port == 0 { + return + } + rule := []string{"-D", "OUTPUT", "-p", "tcp", "--tcp-flags", "RST", "RST", "--sport", strconv.Itoa(int(port)), "-j", "DROP"} + err := exec.Command("iptables", rule...).Run() + if err != nil { + log.Printf("[rawpacket] failed to restore kernel RST on port %d: %v", port, err) + return + } + log.Printf("[rawpacket] restored kernel RST on port %d", port) +} diff --git a/transport/internet/finalmask/rawpacket/spoof_rst_stub.go b/transport/internet/finalmask/rawpacket/spoof_rst_stub.go new file mode 100644 index 000000000000..337542156a84 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_rst_stub.go @@ -0,0 +1,17 @@ +//go:build !linux + +package rawpacket + +import "log" + +// suppressKernelRST is a no-op off Linux: the kernel only emits RST +// replies on the host that owns the port, and raw sockets are privileged +// there anyway. Return false so the caller knows no rule is managed. +func suppressKernelRST(port uint16) bool { + if port != 0 { + log.Printf("[rawpacket] kernel RST suppression only supported on linux (port %d left as-is)", port) + } + return false +} + +func restoreKernelRST(port uint16, managed bool) {} diff --git a/transport/internet/finalmask/rawpacket/spoof_sender.go b/transport/internet/finalmask/rawpacket/spoof_sender.go index c5b652f2485e..ea4066e35905 100644 --- a/transport/internet/finalmask/rawpacket/spoof_sender.go +++ b/transport/internet/finalmask/rawpacket/spoof_sender.go @@ -7,13 +7,13 @@ import ( ) type tcpSender struct { - srcIPs []netip.Addr - rotator *SourceIPRotator + srcIP netip.Addr srcPort uint16 ttl uint8 - seqNum uint32 - seqMu sync.Mutex + ipID uint16 + server bool fd *rawSendFD + mu sync.Mutex } func newTCPSender(cfg *SpoofSenderConfig) (*tcpSender, error) { @@ -26,23 +26,34 @@ func newTCPSender(cfg *SpoofSenderConfig) (*tcpSender, error) { return nil, err } return &tcpSender{ - srcIPs: ips, - rotator: NewSourceIPRotator(ips), + srcIP: ips[0], srcPort: cfg.SourcePort, ttl: cfg.TTL, - seqNum: uint32(rand.Int63n(1 << 31)), + ipID: uint16(rand.Intn(65535)), + server: cfg.Server, fd: fd, }, nil } -func (s *tcpSender) Send(payload []byte, dstIP netip.Addr, dstPort uint16) error { - s.seqMu.Lock() - seq := s.seqNum - s.seqNum += uint32(len(payload)) - s.seqMu.Unlock() +func (s *tcpSender) nextIPID() uint16 { + s.mu.Lock() + defer s.mu.Unlock() + s.ipID++ + return s.ipID +} - spoofIP := s.rotator.Next() - pkt := BuildTCPSYN(spoofIP, dstIP, s.srcPort, dstPort, seq, payload, s.ttl) +func (s *tcpSender) Send(payload []byte, dstIP netip.Addr, dstPort uint16, tcp *TCPSimState) error { + if tcp == nil { + return nil + } + seq := tcp.nextSeq(len(payload)) + var flags uint8 + if s.server { + flags = tcp.serverFlags() + } else { + flags = tcp.clientFlags() + } + pkt := BuildTCPPacket(s.srcIP, dstIP, s.srcPort, dstPort, seq, tcp.ack(), flags, payload, s.ttl, s.nextIPID(), flags&TCPFlagSyn != 0) return s.fd.send(pkt) } @@ -54,11 +65,12 @@ func (s *tcpSender) Close() error { } type udpSender struct { - srcIPs []netip.Addr - rotator *SourceIPRotator + srcIP netip.Addr srcPort uint16 ttl uint8 + ipID uint16 fd *rawSendFD + mu sync.Mutex } func newUDPSender(cfg *SpoofSenderConfig) (*udpSender, error) { @@ -71,17 +83,23 @@ func newUDPSender(cfg *SpoofSenderConfig) (*udpSender, error) { return nil, err } return &udpSender{ - srcIPs: ips, - rotator: NewSourceIPRotator(ips), + srcIP: ips[0], srcPort: cfg.SourcePort, ttl: cfg.TTL, + ipID: uint16(rand.Intn(65535)), fd: fd, }, nil } -func (s *udpSender) Send(payload []byte, dstIP netip.Addr, dstPort uint16) error { - spoofIP := s.rotator.Next() - pkt := BuildRawUDP(spoofIP, dstIP, s.srcPort, dstPort, payload, s.ttl) +func (s *udpSender) nextIPID() uint16 { + s.mu.Lock() + defer s.mu.Unlock() + s.ipID++ + return s.ipID +} + +func (s *udpSender) Send(payload []byte, dstIP netip.Addr, dstPort uint16, tcp *TCPSimState) error { + pkt := BuildRawUDP(s.srcIP, dstIP, s.srcPort, dstPort, payload, s.ttl, s.nextIPID()) return s.fd.send(pkt) } @@ -93,13 +111,13 @@ func (s *udpSender) Close() error { } type icmpSender struct { - srcIPs []netip.Addr - rotator *SourceIPRotator - id uint16 - seq uint16 - ttl uint8 - seqMu sync.Mutex - fd *rawSendFD + srcIP netip.Addr + id uint16 + seq uint16 + ttl uint8 + ipID uint16 + seqMu sync.Mutex + fd *rawSendFD } func newICMPSender(cfg *SpoofSenderConfig) (*icmpSender, error) { @@ -112,23 +130,29 @@ func newICMPSender(cfg *SpoofSenderConfig) (*icmpSender, error) { return nil, err } return &icmpSender{ - srcIPs: ips, - rotator: NewSourceIPRotator(ips), - id: cfg.SourcePort, - seq: 1, - ttl: cfg.TTL, - fd: fd, + srcIP: ips[0], + id: cfg.SourcePort, + seq: 1, + ttl: cfg.TTL, + ipID: uint16(rand.Intn(65535)), + fd: fd, }, nil } -func (s *icmpSender) Send(payload []byte, dstIP netip.Addr, dstPort uint16) error { +func (s *icmpSender) nextIPID() uint16 { + s.seqMu.Lock() + defer s.seqMu.Unlock() + s.ipID++ + return s.ipID +} + +func (s *icmpSender) Send(payload []byte, dstIP netip.Addr, dstPort uint16, tcp *TCPSimState) error { s.seqMu.Lock() seq := s.seq s.seq++ s.seqMu.Unlock() - spoofIP := s.rotator.Next() - pkt := BuildICMPv4Echo(spoofIP, dstIP, s.id, seq, payload, s.ttl) + pkt := BuildICMPv4Echo(s.srcIP, dstIP, s.id, seq, payload, s.ttl, s.nextIPID()) return s.fd.send(pkt) } @@ -140,13 +164,13 @@ func (s *icmpSender) Close() error { } type icmpv6Sender struct { - srcIPs []netip.Addr - rotator *SourceIPRotator - id uint16 - seq uint16 - ttl uint8 - seqMu sync.Mutex - fd *rawSendFD + srcIP netip.Addr + id uint16 + seq uint16 + ttl uint8 + ipID uint16 + seqMu sync.Mutex + fd *rawSendFD } func newICMPv6Sender(cfg *SpoofSenderConfig) (*icmpv6Sender, error) { @@ -159,23 +183,29 @@ func newICMPv6Sender(cfg *SpoofSenderConfig) (*icmpv6Sender, error) { return nil, err } return &icmpv6Sender{ - srcIPs: ips, - rotator: NewSourceIPRotator(ips), - id: cfg.SourcePort, - seq: 1, - ttl: cfg.TTL, - fd: fd, + srcIP: ips[0], + id: cfg.SourcePort, + seq: 1, + ttl: cfg.TTL, + ipID: uint16(rand.Intn(65535)), + fd: fd, }, nil } -func (s *icmpv6Sender) Send(payload []byte, dstIP netip.Addr, dstPort uint16) error { +func (s *icmpv6Sender) nextIPID() uint16 { + s.seqMu.Lock() + defer s.seqMu.Unlock() + s.ipID++ + return s.ipID +} + +func (s *icmpv6Sender) Send(payload []byte, dstIP netip.Addr, dstPort uint16, tcp *TCPSimState) error { s.seqMu.Lock() seq := s.seq s.seq++ s.seqMu.Unlock() - spoofIP := s.rotator.Next() - pkt := BuildICMPv6Echo(spoofIP, dstIP, s.id, seq, payload, s.ttl) + pkt := BuildICMPv6Echo(s.srcIP, dstIP, s.id, seq, payload, s.ttl, s.nextIPID()) return s.fd.send(pkt) } diff --git a/transport/internet/finalmask/rawpacket/spoof_session.go b/transport/internet/finalmask/rawpacket/spoof_session.go index 03349401def4..cbfdef817006 100644 --- a/transport/internet/finalmask/rawpacket/spoof_session.go +++ b/transport/internet/finalmask/rawpacket/spoof_session.go @@ -8,73 +8,59 @@ import ( ) type RelaySession struct { + ID [8]byte ClientIP netip.Addr ClientPort uint16 - ServerAddr netip.Addr TargetConn net.Conn + crypto *frameCrypto + tcp *TCPSimState LastSeen time.Time mu sync.Mutex closed bool } type SessionManager struct { - sessions map[sessionKey]*RelaySession + sessions map[[8]byte]*RelaySession mu sync.Mutex } -type sessionKey struct { - ip [16]byte - port uint16 -} - -func addrToKey(ip netip.Addr) [16]byte { - var out [16]byte - b := ip.As16() - copy(out[:], b[:]) - return out -} - func NewSessionManager() *SessionManager { return &SessionManager{ - sessions: make(map[sessionKey]*RelaySession), + sessions: make(map[[8]byte]*RelaySession), } } -func (sm *SessionManager) Add(clientIP netip.Addr, clientPort uint16, targetConn net.Conn, serverAddr netip.Addr) *RelaySession { - key := sessionKey{ip: addrToKey(clientIP), port: clientPort} +func (sm *SessionManager) Add(id [8]byte, clientIP netip.Addr, clientPort uint16, targetConn net.Conn, crypto *frameCrypto, tcp *TCPSimState) *RelaySession { sm.mu.Lock() defer sm.mu.Unlock() s := &RelaySession{ + ID: id, ClientIP: clientIP, ClientPort: clientPort, - ServerAddr: serverAddr, TargetConn: targetConn, + crypto: crypto, + tcp: tcp, LastSeen: time.Now(), } - sm.sessions[key] = s + sm.sessions[id] = s return s } -func (sm *SessionManager) Get(clientIP netip.Addr, clientPort uint16) *RelaySession { - key := sessionKey{ip: addrToKey(clientIP), port: clientPort} +func (sm *SessionManager) Get(id [8]byte) *RelaySession { sm.mu.Lock() defer sm.mu.Unlock() - s, ok := sm.sessions[key] - if !ok { - return nil - } - s.LastSeen = time.Now() - return s + return sm.sessions[id] } -func (sm *SessionManager) Remove(clientIP netip.Addr, clientPort uint16) { - key := sessionKey{ip: addrToKey(clientIP), port: clientPort} +// Remove is idempotent: it closes the target connection, marks the +// session closed and drops it from the map. +func (sm *SessionManager) Remove(id [8]byte) { sm.mu.Lock() defer sm.mu.Unlock() - if s, ok := sm.sessions[key]; ok { + if s, ok := sm.sessions[id]; ok { s.closed = true s.TargetConn.Close() - delete(sm.sessions, key) + delete(sm.sessions, id) } } diff --git a/transport/internet/finalmask/rawpacket/spoof_tcp.go b/transport/internet/finalmask/rawpacket/spoof_tcp.go index 8224e12e3111..2cfe21ef0b57 100644 --- a/transport/internet/finalmask/rawpacket/spoof_tcp.go +++ b/transport/internet/finalmask/rawpacket/spoof_tcp.go @@ -2,30 +2,60 @@ package rawpacket import ( "encoding/binary" - "math/rand" "net/netip" ) -func BuildTCPSYN(srcIP, dstIP netip.Addr, srcPort, dstPort uint16, seqNum uint32, payload []byte, ttl uint8) []byte { - ipHdrLen := 20 - tcpHdrLen := 20 - totalLen := ipHdrLen + tcpHdrLen + len(payload) +const ( + tcpMaxSegmentMSS = 1460 + tcpWindowScale = 7 +) + +// tcpOptions builds the TCP options for an outbound segment. SYN segments +// carry MSS/SACK-permitted/TS/WScale like Linux; data segments keep the +// timestamp option (RFC 7323 requires it on every segment once +// negotiated). Header size: 20 bytes for SYN, 12 for data. +func tcpOptions(syn bool, ts uint32, tsecr uint32) []byte { + if syn { + opt := make([]byte, 0, 20) + opt = append(opt, 2, 4, byte(tcpMaxSegmentMSS>>8), byte(tcpMaxSegmentMSS&0xff)) // MSS + opt = append(opt, 4, 2) // SACK permitted + opt = append(opt, 8, 10) // TS + opt = binary.BigEndian.AppendUint32(opt, ts) + opt = binary.BigEndian.AppendUint32(opt, tsecr) + opt = append(opt, 1) // NOP + opt = append(opt, 3, 3, tcpWindowScale) + return opt + } + opt := make([]byte, 0, 12) + opt = append(opt, 1, 1, 8, 10) + opt = binary.BigEndian.AppendUint32(opt, ts) + opt = binary.BigEndian.AppendUint32(opt, tsecr) + return opt +} + +// BuildTCPPacket builds a TCP segment over IPv4 with plausible options, +// DF set, and a caller-provided (monotonic) IP ID. +func BuildTCPPacket(srcIP, dstIP netip.Addr, srcPort, dstPort uint16, seqNum, ackNum uint32, flags uint8, payload []byte, ttl uint8, ipID uint16, syn bool) []byte { + opts := tcpOptions(syn, tsVal(), 0) + tcpHdrLen := 20 + len(opts) + totalLen := 20 + tcpHdrLen + len(payload) frame := make([]byte, totalLen) - ip := BuildIPv4Header(uint16(totalLen), uint16(rand.Intn(65535)), ttl, 6, srcIP, dstIP) + ip := BuildIPv4Header(uint16(totalLen), ipID, ttl, 6, srcIP, dstIP, true) copy(frame, ip) - tcp := frame[ipHdrLen:] + tcp := frame[20:] binary.BigEndian.PutUint16(tcp[0:], srcPort) binary.BigEndian.PutUint16(tcp[2:], dstPort) binary.BigEndian.PutUint32(tcp[4:], seqNum) - binary.BigEndian.PutUint32(tcp[8:], 0) + binary.BigEndian.PutUint32(tcp[8:], ackNum) tcp[12] = byte((tcpHdrLen / 4) << 4) - tcp[13] = TCPFlagSyn + tcp[13] = flags binary.BigEndian.PutUint16(tcp[14:], 65535) + copy(tcp[20:], opts) if len(payload) > 0 { - copy(frame[ipHdrLen+tcpHdrLen:], payload) + copy(tcp[tcpHdrLen:], payload) } pseudo := IPv4PseudoHeaderChecksum(srcIP, dstIP, 6, uint16(tcpHdrLen+len(payload))) @@ -35,14 +65,14 @@ func BuildTCPSYN(srcIP, dstIP netip.Addr, srcPort, dstPort uint16, seqNum uint32 return frame } -func BuildICMPv4Echo(srcIP, dstIP netip.Addr, id, seq uint16, payload []byte, ttl uint8) []byte { +func BuildICMPv4Echo(srcIP, dstIP netip.Addr, id, seq uint16, payload []byte, ttl uint8, ipID uint16) []byte { totalLen := 20 + 8 + len(payload) frame := make([]byte, totalLen) - ip := BuildIPv4Header(uint16(totalLen), uint16(rand.Intn(65535)), ttl, 1, srcIP, dstIP) + ip := BuildIPv4Header(uint16(totalLen), ipID, ttl, 1, srcIP, dstIP, false) copy(frame, ip) icmp := frame[20:] - icmp[0] = 8 // Echo Request + icmp[0] = 8 // Echo Request icmp[1] = 0 binary.BigEndian.PutUint16(icmp[4:], id) binary.BigEndian.PutUint16(icmp[6:], seq) @@ -54,12 +84,12 @@ func BuildICMPv4Echo(srcIP, dstIP netip.Addr, id, seq uint16, payload []byte, tt return frame } -func BuildICMPv6Echo(srcIP, dstIP netip.Addr, id, seq uint16, payload []byte, ttl uint8) []byte { +func BuildICMPv6Echo(srcIP, dstIP netip.Addr, id, seq uint16, payload []byte, ttl uint8, ipID uint16) []byte { // Non-standard: ICMPv6 Echo Request (type 128) over IPv4 header with protocol 58. icmpLen := 8 + len(payload) totalLen := 20 + icmpLen frame := make([]byte, totalLen) - ip := BuildIPv4Header(uint16(totalLen), uint16(rand.Intn(65535)), ttl, 58, srcIP, dstIP) + ip := BuildIPv4Header(uint16(totalLen), ipID, ttl, 58, srcIP, dstIP, false) copy(frame, ip) icmp := frame[20:] @@ -76,13 +106,13 @@ func BuildICMPv6Echo(srcIP, dstIP netip.Addr, id, seq uint16, payload []byte, tt return frame } -func BuildRawUDP(srcIP, dstIP netip.Addr, srcPort, dstPort uint16, payload []byte, ttl uint8) []byte { +func BuildRawUDP(srcIP, dstIP netip.Addr, srcPort, dstPort uint16, payload []byte, ttl uint8, ipID uint16) []byte { ipHdrLen := 20 udpHdrLen := 8 totalLen := ipHdrLen + udpHdrLen + len(payload) frame := make([]byte, totalLen) - ip := BuildIPv4Header(uint16(totalLen), uint16(rand.Intn(65535)), ttl, 17, srcIP, dstIP) + ip := BuildIPv4Header(uint16(totalLen), ipID, ttl, 17, srcIP, dstIP, false) copy(frame, ip) udp := frame[ipHdrLen:] diff --git a/transport/internet/finalmask/rawpacket/spoof_tcp_test.go b/transport/internet/finalmask/rawpacket/spoof_tcp_test.go new file mode 100644 index 000000000000..6a592ea12b29 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/spoof_tcp_test.go @@ -0,0 +1,155 @@ +package rawpacket + +import ( + "net/netip" + "testing" +) + +func TestTCPSimStateClient(t *testing.T) { + st := newTCPSimState() + if st.clientFlags() != TCPFlagSyn { + t.Fatalf("first client segment should be SYN, got %#x", st.clientFlags()) + } + seq := st.nextSeq(100) + if seq != st.isn { + t.Fatalf("first data seq should be ISN, got %d want %d", seq, st.isn) + } + if st.clientFlags() != TCPFlagAck { + t.Fatalf("second client segment should be ACK, got %#x", st.clientFlags()) + } + if got := st.ack(); got != 0 { + t.Fatalf("ack before observing peer should be 0, got %d", got) + } + // peer handshake: SYN consumes one byte, then 500 bytes of data + st.observePeer(1000, 0) + if st.ack() != 1001 { + t.Fatalf("ack after peer SYN should be seq+1, got %d want 1001", st.ack()) + } + st.observePeer(1001, 500) + if st.ack() != 1501 { + t.Fatalf("ack should be peer seq+len+1, got %d want 1501", st.ack()) + } + // zero-length segments (keepalives) must not advance the sequence + if st.nextSeq(0) != st.isn+100 { + t.Fatalf("zero-length segment advanced seq: got %d want %d", st.nextSeq(0), st.isn+100) + } + // nextSeq returns the segment's own seq (pre-advance) + if got := st.nextSeq(10); got != st.isn+100 { + t.Fatalf("data after keepalive should be isn+100, got %d", got) + } + if got := st.nextSeq(1); got != st.isn+110 { + t.Fatalf("next segment should be isn+110, got %d", got) + } +} + +func TestTCPSimStateServer(t *testing.T) { + st := newTCPSimState() + if st.serverFlags() != TCPFlagSyn|TCPFlagAck { + t.Fatalf("first server segment should be SYN|ACK, got %#x", st.serverFlags()) + } + if st.serverFlags() != TCPFlagAck { + t.Fatalf("second server segment should be ACK, got %#x", st.serverFlags()) + } + st.observeClientSeq(777) + if st.ack() != 778 { + t.Fatalf("server ack should be observed client seq+1, got %d want 778", st.ack()) + } + seq := st.nextSeq(50) + if seq != st.isn { + t.Fatalf("server data should start at its ISN, got %d", seq) + } + if got := st.nextSeq(30); got != st.isn+50 { + t.Fatalf("server second segment should be isn+50, got %d", got) + } +} + +func TestBuildTCPPacketOptionsAndFlags(t *testing.T) { + src := netip.MustParseAddr("1.2.3.4") + dst := netip.MustParseAddr("5.6.7.8") + st := newTCPSimState() + + // SYN segment: 20 ip + 20 tcp + 20 options + syn := BuildTCPPacket(src, dst, 12345, 443, st.isn, 0, TCPFlagSyn, nil, 64, 100, true) + if len(syn) != 20+40 { + t.Fatalf("SYN should be 60 bytes (20 ip + 40 tcp w/ 20 opts), got %d", len(syn)) + } + if syn[0]>>4 != 4 { + t.Fatalf("not IPv4: %#x", syn[0]) + } + // DF bit set + if syn[6]&0x40 == 0 { + t.Fatal("DF bit not set") + } + // IP ID + if id := uint16(syn[4])<<8 | uint16(syn[5]); id != 100 { + t.Fatalf("IP ID = %d, want 100", id) + } + // data offset: 40 bytes tcp => 10 words + if syn[32]>>4 != 10 { + t.Fatalf("TCP data offset = %d, want 10", syn[32]>>4) + } + // MSS option at offset 20 + if syn[40] != 2 || syn[41] != 4 { + t.Fatal("MSS option kind not found") + } + if mss := uint16(syn[42])<<8 | uint16(syn[43]); mss != tcpMaxSegmentMSS { + t.Fatalf("MSS = %d, want %d", mss, tcpMaxSegmentMSS) + } + // checksum sanity: valid checksum means header verifies + checkTCPChecksum(t, syn) + + // data segment: ACK only, 12-byte options + payload := make([]byte, 100) + data := BuildTCPPacket(src, dst, 12345, 443, st.isn, 1000, TCPFlagAck, payload, 64, 101, false) + if len(data) != 20+32+100 { + t.Fatalf("data seg should be 152 bytes, got %d", len(data)) + } + if data[32]>>4 != 8 { + t.Fatalf("data offset = %d, want 8", data[32]>>4) + } + if data[40] != 1 || data[41] != 1 || data[42] != 8 || data[43] != 10 { + t.Fatal("data segment should carry NOP NOP TS options") + } + checkTCPChecksum(t, data) +} + +func checkTCPChecksum(t *testing.T, pkt []byte) { + t.Helper() + pseudo := IPv4PseudoHeaderChecksum( + netip.AddrFrom4([4]byte{pkt[12], pkt[13], pkt[14], pkt[15]}), + netip.AddrFrom4([4]byte{pkt[16], pkt[17], pkt[18], pkt[19]}), + pkt[9], + uint16(len(pkt)-20), + ) + if csum := Checksum(pkt[20:], pseudo); csum != 0xffff { + t.Fatalf("TCP checksum invalid: %#x", csum) + } +} + +func TestMaxPayloadForMTU(t *testing.T) { + if got := maxPayloadForMTU(0); got != frameMaxPayload { + t.Fatalf("default should be frameMaxPayload, got %d", got) + } + if got := maxPayloadForMTU(1500); got != 1500-20-40-frameOverhead { + t.Fatalf("1500 MTU: got %d", got) + } + if got := maxPayloadForMTU(100); got < 1 { + t.Fatalf("tiny MTU should clamp to >= 1, got %d", got) + } + if got := maxPayloadForMTU(1 << 20); got != frameMaxPayload { + t.Fatalf("huge MTU should cap at frameMaxPayload, got %d", got) + } +} + +func TestBuildTCPPacketIPIDMonotonic(t *testing.T) { + src := netip.MustParseAddr("1.2.3.4") + dst := netip.MustParseAddr("5.6.7.8") + prev := uint16(65000) + for i := 0; i < 100; i++ { + prev++ + pkt := BuildTCPPacket(src, dst, 1, 443, 1, 1, TCPFlagAck, nil, 64, prev, false) + if id := uint16(pkt[4])<<8 | uint16(pkt[5]); id != prev { + t.Fatalf("IP ID = %d, want %d", id, prev) + } + } +} diff --git a/transport/internet/finalmask/rawpacket/spoof_transport.go b/transport/internet/finalmask/rawpacket/spoof_transport.go index 69a4298b81ed..116e07cbc4f5 100644 --- a/transport/internet/finalmask/rawpacket/spoof_transport.go +++ b/transport/internet/finalmask/rawpacket/spoof_transport.go @@ -3,12 +3,24 @@ package rawpacket import "net/netip" type SpoofSender interface { - Send(payload []byte, dstIP netip.Addr, dstPort uint16) error + Send(payload []byte, dstIP netip.Addr, dstPort uint16, tcp *TCPSimState) error Close() error } +// TCPMeta carries the header fields of a received raw packet so the +// caller can keep the fake conversation's sequence/ack state plausible, +// and the relay can masquerade replies from the address the probe dialed. +// DstIP/DstPort are always set; Seq/Flags are non-zero only for TCP. +// nil for transports the receiver does not describe (ICMP). +type TCPMeta struct { + Seq uint32 + Flags uint8 + DstIP netip.Addr + DstPort uint16 +} + type SpoofReceiver interface { - Receive() (payload []byte, srcIP netip.Addr, srcPort uint16, err error) + Receive() (payload []byte, srcIP netip.Addr, srcPort uint16, tcp *TCPMeta, err error) Close() error } @@ -45,6 +57,9 @@ type SpoofSenderConfig struct { SourceIPs []netip.Addr SourcePort uint16 TTL uint8 + // Server marks a relay-side sender: outbound TCP segments take the + // passive-opener role (SYN|ACK then ACK). + Server bool } type SpoofReceiverConfig struct { diff --git a/transport/internet/finalmask/rawpacket/tcp_state.go b/transport/internet/finalmask/rawpacket/tcp_state.go new file mode 100644 index 000000000000..e18b59cae942 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/tcp_state.go @@ -0,0 +1,111 @@ +package rawpacket + +import ( + "math/rand" + "sync" + "time" +) + +// TCPSimState simulates a plausible TCP conversation between the client +// and the relay so middleboxes see a normal connection: SYN with TCP +// options, then ACK segments with monotonically increasing sequence +// numbers and correct acknowledgements. +// +// The client and the relay each keep one state per tunnel session. +// The client is the "active opener" (sends SYN, then ACKs), the relay is +// the "passive opener" (replies SYN|ACK, then ACKs). Which role a state +// plays is decided by the first outbound segment: client role sends a +// bare SYN, server role sends SYN|ACK. +type TCPSimState struct { + mu sync.Mutex + isn uint32 // our initial sequence number + seq uint32 // next sequence number to send + synSent bool // our SYN (or SYN|ACK) has been sent + peerISN uint32 // peer's initial sequence number + peerSeq uint32 // peer's next expected sequence number (peer's last seq + 1) + peerSeen bool +} + +func newTCPSimState() *TCPSimState { + isn := uint32(rand.Int63n(1 << 31)) + return &TCPSimState{isn: isn, seq: isn} +} + +// nextSeq returns the sequence number to use for an outbound segment of +// the given payload length and advances the counter. Zero-length segments +// (keepalives) do not consume sequence numbers. +func (s *TCPSimState) nextSeq(payloadLen int) uint32 { + s.mu.Lock() + defer s.mu.Unlock() + seq := s.seq + if payloadLen > 0 { + s.seq += uint32(payloadLen) + } + return seq +} + +// flags returns the flags for the next outbound segment in client role. +func (s *TCPSimState) clientFlags() uint8 { + s.mu.Lock() + defer s.mu.Unlock() + if !s.synSent { + s.synSent = true + return TCPFlagSyn + } + return TCPFlagAck +} + +// flags returns the flags for the next outbound segment in server role. +func (s *TCPSimState) serverFlags() uint8 { + s.mu.Lock() + defer s.mu.Unlock() + if !s.synSent { + s.synSent = true + return TCPFlagSyn | TCPFlagAck + } + return TCPFlagAck +} + +// ack returns the acknowledgement number for the next outbound segment: +// the peer's last seen sequence number plus one, or zero before the peer +// is seen. +func (s *TCPSimState) ack() uint32 { + s.mu.Lock() + defer s.mu.Unlock() + if !s.peerSeen { + return 0 + } + return s.peerSeq +} + +// observePeer records the peer's latest segment (its wire sequence number +// and payload length) so outbound acknowledgements stay plausible. The +// first segment is the peer's SYN and consumes one byte; later segments +// advance by their payload length only. +func (s *TCPSimState) observePeer(seq uint32, payloadLen int) { + s.mu.Lock() + defer s.mu.Unlock() + if !s.peerSeen { + s.peerSeen = true + s.peerISN = seq + s.peerSeq = seq + 1 + uint32(payloadLen) + return + } + s.peerSeq = seq + uint32(payloadLen) +} + +// observeClientSeq records the client's latest wire sequence number so +// server-role acknowledgements are correct. Used on the relay, where the +// client's cumulative sequence is already encoded in the wire seq. +func (s *TCPSimState) observeClientSeq(seq uint32) { + s.mu.Lock() + defer s.mu.Unlock() + s.peerSeen = true + s.peerSeq = seq + 1 +} + +// tsVal returns a plausible TCP timestamp value: milliseconds since a +// fixed epoch, so it grows monotonically like Linux jiffies. +func tsVal() uint32 { + return uint32(time.Now().UnixMilli() & 0xFFFFFFFF) +} diff --git a/transport/internet/finalmask/rawpacket/tcpip.go b/transport/internet/finalmask/rawpacket/tcpip.go index 0bb18cc0aea0..bd00a28c0112 100644 --- a/transport/internet/finalmask/rawpacket/tcpip.go +++ b/transport/internet/finalmask/rawpacket/tcpip.go @@ -137,8 +137,8 @@ type IPv6 []byte func (b IPv6) PayloadLength() uint16 { return binary.BigEndian.Uint16(b[4:]) } func (b IPv6) TransportProtocol() uint8 { return b[6] } -func (b IPv6) Src() netip.Addr { return netip.AddrFrom16([16]byte(b[8:24])) } -func (b IPv6) Dst() netip.Addr { return netip.AddrFrom16([16]byte(b[24:40])) } +func (b IPv6) Src() netip.Addr { return netip.AddrFrom16([16]byte(b[8:24])) } +func (b IPv6) Dst() netip.Addr { return netip.AddrFrom16([16]byte(b[24:40])) } func (b IPv6) SetPayloadLength(n uint16) { binary.BigEndian.PutUint16(b[4:], n) diff --git a/transport/internet/finalmask/rawpacket/windivert/filter.go b/transport/internet/finalmask/rawpacket/windivert/filter.go index 6304de6c7f48..fbc58b9c0e08 100644 --- a/transport/internet/finalmask/rawpacket/windivert/filter.go +++ b/transport/internet/finalmask/rawpacket/windivert/filter.go @@ -70,6 +70,15 @@ func reject() *Filter { return &Filter{} } +// AcceptAll returns a filter that matches every network-layer packet. +// Suitable for sniffing handles that filter in userspace. All filter +// flags are set since the filter may match any packet class. +func AcceptAll() *Filter { + f := &Filter{flags: filterFlagOutbound | filterFlagIP | filterFlagIPv6} + f.add(fieldZero, testEQ, argUint32(0)) + return f +} + // OutboundTCP returns a filter matching outbound TCP packets on the given // 5-tuple. Both addresses must share an address family (IPv4 or IPv6). func OutboundTCP(src, dst netip.AddrPort) (*Filter, error) { diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index a54d7c02f877..1aa1518c6494 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -137,6 +137,15 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in if tlsConfig != nil { config := tlsConfig.GetTLSConfig(tls.WithDestination(dest)) + if config.ServerName == "" && address.Family().IsDomain() { + config.ServerName = address.Domain() + } + if spoofConn, err := tls.WrapWithSpoof(c, tlsConfig.Spoof, tlsConfig.SpoofMethod, tlsConfig.SpoofCount, config.ServerName); err != nil { + c.Close() + return nil, err + } else { + c = spoofConn + } if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil { return tls.UClient(c, config, fingerprint), nil } else { // Fallback to normal gRPC TLS diff --git a/transport/internet/httpupgrade/dialer.go b/transport/internet/httpupgrade/dialer.go index 571797f6172d..bb9df1c912fb 100644 --- a/transport/internet/httpupgrade/dialer.go +++ b/transport/internet/httpupgrade/dialer.go @@ -66,6 +66,12 @@ func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings * tConfig := tls.ConfigFromStreamSettings(streamSettings) if tConfig != nil { tlsConfig := tConfig.GetTLSConfig(tls.WithDestination(dest), tls.WithNextProto("http/1.1")) + if spoofConn, err := tls.WrapWithSpoof(pconn, tConfig.Spoof, tConfig.SpoofMethod, tConfig.SpoofCount, tlsConfig.ServerName); err != nil { + pconn.Close() + return nil, err + } else { + pconn = spoofConn + } if fingerprint := tls.GetFingerprint(tConfig.Fingerprint); fingerprint != nil { conn = tls.UClient(pconn, tlsConfig, fingerprint) if err := conn.(*tls.UConn).WebsocketHandshakeContext(ctx); err != nil { diff --git a/transport/internet/kcp/dialer.go b/transport/internet/kcp/dialer.go index 175998ec7dd3..e3ff0bdc9a19 100644 --- a/transport/internet/kcp/dialer.go +++ b/transport/internet/kcp/dialer.go @@ -97,7 +97,14 @@ func DialKCP(ctx context.Context, dest net.Destination, streamSettings *internet var iConn stat.Connection = session if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { - iConn = tls.Client(iConn, config.GetTLSConfig(tls.WithDestination(dest))) + tlsConfig := config.GetTLSConfig(tls.WithDestination(dest)) + if spoofConn, err := tls.WrapWithSpoof(iConn, config.Spoof, config.SpoofMethod, config.SpoofCount, tlsConfig.ServerName); err != nil { + iConn.Close() + return nil, err + } else { + iConn = spoofConn.(stat.Connection) + } + iConn = tls.Client(iConn, tlsConfig) } return iConn, nil diff --git a/transport/internet/splithttp/dialer.go b/transport/internet/splithttp/dialer.go index 817d935528c9..8d4c77f1d0a1 100644 --- a/transport/internet/splithttp/dialer.go +++ b/transport/internet/splithttp/dialer.go @@ -137,6 +137,12 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea } if gotlsConfig != nil { + if spoofConn, err := tls.WrapWithSpoof(conn, tlsConfig.Spoof, tlsConfig.SpoofMethod, tlsConfig.SpoofCount, gotlsConfig.ServerName); err != nil { + conn.Close() + return nil, err + } else { + conn = spoofConn + } if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil { conn = tls.UClient(conn, gotlsConfig, fingerprint) if err := conn.(*tls.UConn).HandshakeContext(ctxInner); err != nil { diff --git a/transport/internet/tcp/dialer.go b/transport/internet/tcp/dialer.go index 92fa7557f13a..e226a5657cb3 100644 --- a/transport/internet/tcp/dialer.go +++ b/transport/internet/tcp/dialer.go @@ -74,6 +74,11 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me } } if fingerprint := tls.GetFingerprint(config.Fingerprint); fingerprint != nil { + if spoofConn, err := tls.WrapWithSpoof(conn, config.Spoof, config.SpoofMethod, config.SpoofCount, tlsConfig.ServerName); err != nil { + return nil, err + } else { + conn = spoofConn + } conn = tls.UClient(conn, tlsConfig, fingerprint) if len(tlsConfig.NextProtos) == 1 && tlsConfig.NextProtos[0] == "http/1.1" { // allow manually specify err = conn.(*tls.UConn).WebsocketHandshakeContext(ctx) @@ -81,6 +86,11 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me err = conn.(*tls.UConn).HandshakeContext(ctx) } } else { + if spoofConn, err := tls.WrapWithSpoof(conn, config.Spoof, config.SpoofMethod, config.SpoofCount, tlsConfig.ServerName); err != nil { + return nil, err + } else { + conn = spoofConn + } conn = tls.Client(conn, tlsConfig) err = conn.(*tls.Conn).HandshakeContext(ctx) } diff --git a/transport/internet/tls/config.pb.go b/transport/internet/tls/config.pb.go index b622d5a61489..f3b08acd8788 100644 --- a/transport/internet/tls/config.pb.go +++ b/transport/internet/tls/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v6.33.5 +// protoc v7.35.1 // source: transport/internet/tls/config.proto package tls @@ -206,8 +206,17 @@ type Config struct { EchConfigList string `protobuf:"bytes,19,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"` EchSocketSettings *internet.SocketConfig `protobuf:"bytes,21,opt,name=ech_socket_settings,json=echSocketSettings,proto3" json:"ech_socket_settings,omitempty"` PinnedPeerCertSha256 [][]byte `protobuf:"bytes,22,rep,name=pinned_peer_cert_sha256,json=pinnedPeerCertSha256,proto3" json:"pinned_peer_cert_sha256,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // SNI to forge in the spoofed ClientHello sent via raw sockets before + // the real TLS handshake (sing-box style TLS spoofing). + Spoof string `protobuf:"bytes,23,opt,name=spoof,proto3" json:"spoof,omitempty"` + // How the spoofed ClientHello is corrupted to evade DPI: "wrong-sequence" + // (default), "wrong-checksum", "wrong-ack", "wrong-md5" or + // "wrong-timestamp". + SpoofMethod string `protobuf:"bytes,24,opt,name=spoof_method,json=spoofMethod,proto3" json:"spoof_method,omitempty"` + // Number of times to inject the fake ClientHello (0 or 1 = single-shot). + SpoofCount int32 `protobuf:"varint,25,opt,name=spoof_count,json=spoofCount,proto3" json:"spoof_count,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Config) Reset() { @@ -359,6 +368,27 @@ func (x *Config) GetPinnedPeerCertSha256() [][]byte { return nil } +func (x *Config) GetSpoof() string { + if x != nil { + return x.Spoof + } + return "" +} + +func (x *Config) GetSpoofMethod() string { + if x != nil { + return x.SpoofMethod + } + return "" +} + +func (x *Config) GetSpoofCount() int32 { + if x != nil { + return x.SpoofCount + } + return 0 +} + var File_transport_internet_tls_config_proto protoreflect.FileDescriptor const file_transport_internet_tls_config_proto_rawDesc = "" + @@ -377,7 +407,7 @@ const file_transport_internet_tls_config_proto_rawDesc = "" + "\x05Usage\x12\x10\n" + "\fENCIPHERMENT\x10\x00\x12\x14\n" + "\x10AUTHORITY_VERIFY\x10\x01\x12\x13\n" + - "\x0fAUTHORITY_ISSUE\x10\x02\"\xa6\x06\n" + + "\x0fAUTHORITY_ISSUE\x10\x02\"\x80\a\n" + "\x06Config\x12J\n" + "\vcertificate\x18\x02 \x03(\v2(.xray.transport.internet.tls.CertificateR\vcertificate\x12\x1f\n" + "\vserver_name\x18\x03 \x01(\tR\n" + @@ -398,7 +428,11 @@ const file_transport_internet_tls_config_proto_rawDesc = "" + "\x0fech_server_keys\x18\x12 \x01(\fR\rechServerKeys\x12&\n" + "\x0fech_config_list\x18\x13 \x01(\tR\rechConfigList\x12U\n" + "\x13ech_socket_settings\x18\x15 \x01(\v2%.xray.transport.internet.SocketConfigR\x11echSocketSettings\x125\n" + - "\x17pinned_peer_cert_sha256\x18\x16 \x03(\fR\x14pinnedPeerCertSha256Bs\n" + + "\x17pinned_peer_cert_sha256\x18\x16 \x03(\fR\x14pinnedPeerCertSha256\x12\x14\n" + + "\x05spoof\x18\x17 \x01(\tR\x05spoof\x12!\n" + + "\fspoof_method\x18\x18 \x01(\tR\vspoofMethod\x12\x1f\n" + + "\vspoof_count\x18\x19 \x01(\x05R\n" + + "spoofCountBs\n" + "\x1fcom.xray.transport.internet.tlsP\x01Z0github.com/xtls/xray-core/transport/internet/tls\xaa\x02\x1bXray.Transport.Internet.Tlsb\x06proto3" var ( diff --git a/transport/internet/tls/config.proto b/transport/internet/tls/config.proto index a05cc049427d..88c1773864dd 100644 --- a/transport/internet/tls/config.proto +++ b/transport/internet/tls/config.proto @@ -86,4 +86,16 @@ message Config { SocketConfig ech_socket_settings = 21; repeated bytes pinned_peer_cert_sha256 = 22; + + // SNI to forge in the spoofed ClientHello sent via raw sockets before + // the real TLS handshake (sing-box style TLS spoofing). + string spoof = 23; + + // How the spoofed ClientHello is corrupted to evade DPI: "wrong-sequence" + // (default), "wrong-checksum", "wrong-ack", "wrong-md5" or + // "wrong-timestamp". + string spoof_method = 24; + + // Number of times to inject the fake ClientHello (0 or 1 = single-shot). + int32 spoof_count = 25; } diff --git a/transport/internet/tls/tls.go b/transport/internet/tls/tls.go index df5d1cbd7cf0..69b2cf558877 100644 --- a/transport/internet/tls/tls.go +++ b/transport/internet/tls/tls.go @@ -5,13 +5,17 @@ import ( "crypto/rand" "crypto/tls" "math/big" + gonet "net" "slices" + "strings" "time" utls "github.com/refraction-networking/utls" "github.com/xtls/xray-core/common/buf" + "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/utils" + "github.com/xtls/xray-core/transport/internet/tls/tlsspoof" ) type Interface interface { @@ -66,6 +70,35 @@ func Client(c net.Conn, config *tls.Config) net.Conn { return &Conn{Conn: tlsConn} } +// WrapWithSpoof wraps a connection with TLS spoofing if the config has +// spoof settings. The spoofed ClientHello is injected via raw sockets +// before the real TLS handshake, causing DPI middleboxes to see the +// forged SNI while the actual connection proceeds normally. +// spoofCount controls how many Write() calls trigger injection (0 = single-shot). +func WrapWithSpoof(c net.Conn, spoofSNI string, spoofMethodStr string, spoofCount int32, serverName string) (net.Conn, error) { + spoofSNI, method, err := tlsspoof.ParseOptions(spoofSNI, spoofMethodStr) + if err != nil { + return nil, errors.New("tls_spoof: invalid options").Base(err) + } + if spoofSNI == "" { + return c, nil + } + if serverName == "" { + return nil, errors.New("tls_spoof: requires a TLS server name (SNI)") + } + if gonet.ParseIP(serverName) != nil { + return nil, errors.New("tls_spoof: cannot spoof when server name is an IP literal") + } + if strings.EqualFold(spoofSNI, serverName) { + return nil, errors.New("tls_spoof: spoof must differ from server_name") + } + wrapped, err := tlsspoof.NewConn(c, method, spoofSNI, int(spoofCount)) + if err != nil { + return nil, errors.New("tls_spoof: failed to create spoof conn").Base(err) + } + return wrapped, nil +} + // Server initiates a TLS server handshake on the given connection. func Server(c net.Conn, config *tls.Config) net.Conn { tlsConn := tls.Server(c, config) diff --git a/transport/internet/tls/tlsspoof/client_hello.go b/transport/internet/tls/tlsspoof/client_hello.go new file mode 100644 index 000000000000..b078697c97cc --- /dev/null +++ b/transport/internet/tls/tlsspoof/client_hello.go @@ -0,0 +1,75 @@ +package tlsspoof + +import ( + "bytes" + "context" + "crypto/tls" + + "errors" + "net" + "time" +) + +type writeOnlyConn struct { + net.Conn + w *bytes.Buffer +} + +func (c *writeOnlyConn) Write(b []byte) (int, error) { + return c.w.Write(b) +} + +func (c *writeOnlyConn) Read(b []byte) (int, error) { + return 0, errors.New("read from write-only conn") +} + +func (c *writeOnlyConn) Close() error { + return nil +} + +func (c *writeOnlyConn) LocalAddr() net.Addr { + return nil +} + +func (c *writeOnlyConn) RemoteAddr() net.Addr { + return nil +} + +func (c *writeOnlyConn) SetDeadline(t time.Time) error { + return nil +} + +func (c *writeOnlyConn) SetReadDeadline(t time.Time) error { + return nil +} + +func (c *writeOnlyConn) SetWriteDeadline(t time.Time) error { + return nil +} + +// buildFakeClientHello drives crypto/tls against a write-only in-memory conn +// to capture a generated ClientHello. CurvePreferences pins classical groups +// to suppress Go's default X25519MLKEM768 hybrid key share; without this the +// post-quantum public key alone (~1184 bytes) pushes the record past one MSS, +// and middleboxes do not reassemble fragmented ClientHellos. The handshake +// error is discarded because the stub conn's Read returns immediately. +func buildFakeClientHello(sni string) ([]byte, error) { + if sni == "" { + return nil, errors.New("empty sni") + } + var buf bytes.Buffer + tlsConn := tls.Client(&writeOnlyConn{w: &buf}, &tls.Config{ + ServerName: sni, + // Order matches what browsers advertised before post-quantum. + CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP384}, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS13, + NextProtos: []string{"h2", "http/1.1"}, + InsecureSkipVerify: true, + }) + _ = tlsConn.HandshakeContext(context.Background()) + if buf.Len() == 0 { + return nil, errors.New("tls ClientHello not produced") + } + return buf.Bytes(), nil +} diff --git a/transport/internet/tls/tlsspoof/endpoints.go b/transport/internet/tls/tlsspoof/endpoints.go new file mode 100644 index 000000000000..ac0c30484226 --- /dev/null +++ b/transport/internet/tls/tlsspoof/endpoints.go @@ -0,0 +1,27 @@ +package tlsspoof + +import ( + "net" + "net/netip" + + "errors" +) + +// The returned addresses are v4-unmapped and share the same family. +func tcpEndpoints(conn net.Conn) (*net.TCPConn, netip.AddrPort, netip.AddrPort, error) { + tcpConn, isTCP := conn.(*net.TCPConn) + if !isTCP { + return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("tls_spoof: underlying conn is not *net.TCPConn") + } + local := tcpConn.LocalAddr().(*net.TCPAddr).AddrPort() + remote := tcpConn.RemoteAddr().(*net.TCPAddr).AddrPort() + if !local.IsValid() || !remote.IsValid() { + return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("tls_spoof: invalid conn address") + } + local = netip.AddrPortFrom(local.Addr().Unmap(), local.Port()) + remote = netip.AddrPortFrom(remote.Addr().Unmap(), remote.Port()) + if local.Addr().Is4() != remote.Addr().Is4() { + return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("tls_spoof: local/remote address family mismatch") + } + return tcpConn, local, remote, nil +} diff --git a/transport/internet/tls/tlsspoof/packet.go b/transport/internet/tls/tlsspoof/packet.go new file mode 100644 index 000000000000..5c23c0631ab8 --- /dev/null +++ b/transport/internet/tls/tlsspoof/packet.go @@ -0,0 +1,163 @@ +package tlsspoof + +import ( + "encoding/binary" + "net/netip" + + "fmt" +) + +const ( + defaultTTL uint8 = 64 + defaultWindowSize uint16 = 0xFFFF + tcpHeaderLen = TCPMinimumSize + + tcpOptionMD5Signature = 19 + tcpOptionMD5SignatureLength = 18 + tcpTimestampBackdate = 3600000 +) + +type spoofPacketInfo struct { + seqNum uint32 + ackNum uint32 + corrupt bool + options []byte +} + +func buildTCPSegment( + src netip.AddrPort, + dst netip.AddrPort, + packetInfo spoofPacketInfo, + payload []byte, +) []byte { + if src.Addr().Is4() != dst.Addr().Is4() { + panic("tlsspoof: mixed IPv4/IPv6 address family") + } + var ( + frame []byte + ipHeaderLen int + ) + ipPayloadLen := tcpHeaderLen + len(packetInfo.options) + len(payload) + if src.Addr().Is4() { + ipHeaderLen = IPv4MinimumSize + frame = make([]byte, ipHeaderLen+ipPayloadLen) + ip := IPv4(frame[:ipHeaderLen]) + ip.Encode(uint16(len(frame)), 0, defaultTTL, TCPProtocolNumber, src.Addr(), dst.Addr()) + } else { + ipHeaderLen = IPv6MinimumSize + frame = make([]byte, ipHeaderLen+ipPayloadLen) + ip := IPv6(frame[:ipHeaderLen]) + ip.Encode(uint16(ipPayloadLen), TCPProtocolNumber, defaultTTL, src.Addr(), dst.Addr()) + } + encodeTCP(frame, ipHeaderLen, src, dst, packetInfo, payload) + return frame +} + +func encodeTCP(frame []byte, ipHeaderLen int, src, dst netip.AddrPort, packetInfo spoofPacketInfo, payload []byte) { + tcp := TCP(frame[ipHeaderLen:]) + copy(frame[ipHeaderLen+tcpHeaderLen:], packetInfo.options) + optionsLen := len(packetInfo.options) + copy(frame[ipHeaderLen+tcpHeaderLen+optionsLen:], payload) + tcp.Encode(src.Port(), dst.Port(), packetInfo.seqNum, packetInfo.ackNum, uint8(tcpHeaderLen+optionsLen), TCPFlagAck|TCPFlagPsh, defaultWindowSize) + applyTCPChecksum(tcp, src.Addr(), dst.Addr(), payload, packetInfo.corrupt) +} + +func buildSpoofFrame(method Method, src, dst netip.AddrPort, sendNext, receiveNext, timestamp uint32, tcpOptions, payload []byte) ([]byte, error) { + packetInfo, err := resolveSpoofPacketInfo(method, sendNext, receiveNext, timestamp, tcpOptions, payload) + if err != nil { + return nil, err + } + return buildTCPSegment(src, dst, packetInfo, payload), nil +} + +// buildSpoofTCPSegment returns a TCP segment without an IP header, for +// platforms where the kernel synthesises the IP header (darwin IPv6). +func buildSpoofTCPSegment(method Method, src, dst netip.AddrPort, sendNext, receiveNext, timestamp uint32, payload []byte) ([]byte, error) { + packetInfo, err := resolveSpoofPacketInfo(method, sendNext, receiveNext, timestamp, nil, payload) + if err != nil { + return nil, err + } + segment := make([]byte, tcpHeaderLen+len(packetInfo.options)+len(payload)) + encodeTCP(segment, 0, src, dst, packetInfo, payload) + return segment, nil +} + +func resolveSpoofPacketInfo(method Method, sendNext, receiveNext, timestamp uint32, tcpOptions, payload []byte) (spoofPacketInfo, error) { + packetInfo := spoofPacketInfo{seqNum: sendNext, ackNum: receiveNext} + switch method { + case MethodWrongSequence: + packetInfo.seqNum = sendNext - uint32(len(payload)) + case MethodWrongChecksum: + packetInfo.corrupt = true + case MethodWrongAcknowledgment: + packetInfo.ackNum = receiveNext - uint32(defaultWindowSize/2) + case MethodWrongMD5Sig: + packetInfo.options = buildMD5SignatureOptions() + case MethodWrongTimestamp: + packetInfo.options = buildWrongTimestampOptions(timestamp, tcpOptions) + default: + return packetInfo, fmt.Errorf("tls_spoof: unknown method %v", method) + } + return packetInfo, nil +} + +func buildMD5SignatureOptions() []byte { + options := make([]byte, tcpOptionMD5SignatureLength+2) + options[0] = tcpOptionMD5Signature + options[1] = tcpOptionMD5SignatureLength + return options +} + +func buildWrongTimestampOptions(timestamp uint32, tcpOptions []byte) []byte { + spoofedTimestamp := timestamp + if spoofedTimestamp > tcpTimestampBackdate { + spoofedTimestamp -= tcpTimestampBackdate + } else { + spoofedTimestamp = 0 + } + if rewriteTCPOptionTimestamp(tcpOptions, spoofedTimestamp) { + return tcpOptions + } + options := make([]byte, TCPOptionTSLength+2) + EncodeTSOption(spoofedTimestamp, 0, options) + return options +} + +// rewriteTCPOptionTimestamp finds the TS option in tcpOptions and writes +// timestamp into its TSVal field in place. The caller must own tcpOptions +// (parseTCPPacket already returns a private copy on Windows). +func rewriteTCPOptionTimestamp(tcpOptions []byte, timestamp uint32) bool { + for i := 0; i < len(tcpOptions); { + switch tcpOptions[i] { + case TCPOptionEOL: + return false + case TCPOptionNOP: + i++ + continue + } + if i+1 >= len(tcpOptions) { + return false + } + optionLen := int(tcpOptions[i+1]) + if optionLen < 2 || i+optionLen > len(tcpOptions) { + return false + } + if tcpOptions[i] == TCPOptionTS && optionLen == TCPOptionTSLength { + binary.BigEndian.PutUint32(tcpOptions[i+2:], timestamp) + return true + } + i += optionLen + } + return false +} + +func applyTCPChecksum(tcp TCP, srcAddr, dstAddr netip.Addr, payload []byte, corrupt bool) { + tcpLen := int(tcp.DataOffset()) + len(payload) + pseudo := PseudoHeaderChecksum(TCPProtocolNumber, srcAddr.AsSlice(), dstAddr.AsSlice(), uint16(tcpLen)) + payloadChecksum := Checksum(payload, 0) + tcpChecksum := ^tcp.CalculateChecksum(CombineChecksum(pseudo, payloadChecksum)) + if corrupt { + tcpChecksum ^= 0xFFFF + } + tcp.SetChecksum(tcpChecksum) +} diff --git a/transport/internet/tls/tlsspoof/raw_darwin.go b/transport/internet/tls/tlsspoof/raw_darwin.go new file mode 100644 index 000000000000..3b45d17023be --- /dev/null +++ b/transport/internet/tls/tlsspoof/raw_darwin.go @@ -0,0 +1,198 @@ +package tlsspoof + +import ( + "encoding/binary" + "net" + "net/netip" + "strconv" + "strings" + "sync" + "syscall" + + "errors" + "fmt" + + "golang.org/x/sys/unix" +) + +const PlatformSupported = true + +// Offsets into xinpcb_n within each net.inet.tcp.pcblist_n record, identical +// to the values used by common/process/searcher_darwin_shared.go. +const ( + darwinXinpgenSize = 24 + darwinXsocketOffset = 104 + darwinXinpcbForeignPort = 16 + darwinXinpcbLocalPort = 18 + darwinXinpcbVFlag = 44 + darwinXinpcbForeignAddr = 48 + darwinXinpcbLocalAddr = 64 + darwinXinpcbIPv4Offset = 12 + + darwinTCPExtraSize = 208 + + darwinXtcpcbSndNxtOffset = 56 + darwinXtcpcbRcvNxtOffset = 80 +) + +// darwinStructSize returns the size of xinpcb_n for the running Darwin kernel. +// Darwin 22 (macOS 13 Ventura) grew the struct from 384 to 408 bytes; there is +// no ABI-stable way to read it, so we key off the kernel version. +var darwinStructSize = sync.OnceValues(func() (int, error) { + value, err := syscall.Sysctl("kern.osrelease") + if err != nil { + return 0, func(err error, m string) error { return err }(err, "sysctl kern.osrelease") + } + major, _, ok := strings.Cut(value, ".") + if !ok { + return 0, fmt.Errorf("unexpected kern.osrelease format: %s", value) + } + n, err := strconv.ParseInt(major, 10, 64) + if err != nil { + return 0, func(err error, m string) error { return err }(err, "parse kern.osrelease major version: ") + } + if n >= 22 { + return 408, nil + } + return 384, nil +}) + +type darwinSpoofer struct { + method Method + src netip.AddrPort + dst netip.AddrPort + rawFD int + rawSockAddr unix.Sockaddr + sendNext uint32 + receiveNext uint32 +} + +func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { + if method == MethodWrongTimestamp { + return nil, errors.New("tls_spoof: wrong-timestamp is not supported on macOS") + } + _, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + fd, sockaddr, err := openDarwinRawSocket(src, dst) + if err != nil { + return nil, err + } + sendNext, receiveNext, err := readDarwinTCPSequence(src, dst) + if err != nil { + unix.Close(fd) + return nil, err + } + return &darwinSpoofer{ + method: method, + src: src, + dst: dst, + rawFD: fd, + rawSockAddr: sockaddr, + sendNext: sendNext, + receiveNext: receiveNext, + }, nil +} + +// readDarwinTCPSequence scans net.inet.tcp.pcblist_n for the PCB that matches +// src -> dst and returns (snd_nxt, rcv_nxt). These live in xtcpcb_n at the end +// of each record; see darwin-xnu bsd/netinet/in_pcblist.c:get_pcblist_n. +func readDarwinTCPSequence(src, dst netip.AddrPort) (uint32, uint32, error) { + buffer, err := unix.SysctlRaw("net.inet.tcp.pcblist_n") + if err != nil { + return 0, 0, func(err error, m string) error { return err }(err, "sysctl net.inet.tcp.pcblist_n") + } + structSize, err := darwinStructSize() + if err != nil { + return 0, 0, err + } + itemSize := structSize + darwinTCPExtraSize + for i := darwinXinpgenSize; i+itemSize <= len(buffer); i += itemSize { + inpcb := buffer[i : i+darwinXsocketOffset] + xtcpcb := buffer[i+structSize : i+itemSize] + localPort := binary.BigEndian.Uint16(inpcb[darwinXinpcbLocalPort : darwinXinpcbLocalPort+2]) + remotePort := binary.BigEndian.Uint16(inpcb[darwinXinpcbForeignPort : darwinXinpcbForeignPort+2]) + if localPort != src.Port() || remotePort != dst.Port() { + continue + } + versionFlag := inpcb[darwinXinpcbVFlag] + var localAddr, remoteAddr netip.Addr + switch { + case versionFlag&0x1 != 0: + localAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset : darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset+4])) + remoteAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset : darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset+4])) + case versionFlag&0x2 != 0: + localAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbLocalAddr : darwinXinpcbLocalAddr+16])) + remoteAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbForeignAddr : darwinXinpcbForeignAddr+16])) + default: + continue + } + if localAddr.Unmap() != src.Addr() || remoteAddr.Unmap() != dst.Addr() { + continue + } + sendNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbSndNxtOffset : darwinXtcpcbSndNxtOffset+4]) + receiveNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbRcvNxtOffset : darwinXtcpcbRcvNxtOffset+4]) + return sendNext, receiveNext, nil + } + return 0, 0, fmt.Errorf("tls_spoof: connection %v->%v not found in pcblist_n", src, dst) +} + +func openDarwinRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) { + if dst.Addr().Is4() { + return openIPv4RawSocket(dst) + } + // macOS does not accept IPV6_HDRINCL on AF_INET6 SOCK_RAW IPPROTO_TCP + // sockets, so the kernel builds the IPv6 header itself. Bind to the real + // connection's source address so in6_selectsrc returns it, and rely on + // in6p_cksum defaulting to -1 so the user-supplied TCP checksum is + // preserved (including deliberately corrupted ones). + fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, func(err error, m string) error { return err }(err, "open AF_INET6 SOCK_RAW") + } + err = unix.Bind(fd, &unix.SockaddrInet6{Addr: src.Addr().As16()}) + if err != nil { + unix.Close(fd) + return -1, nil, func(err error, m string) error { return err }(err, "bind AF_INET6 SOCK_RAW") + } + sockaddr := &unix.SockaddrInet6{Port: int(dst.Port()), Addr: dst.Addr().As16()} + return fd, sockaddr, nil +} + +func (s *darwinSpoofer) Inject(payload []byte) error { + if !s.src.Addr().Is4() { + segment, err := buildSpoofTCPSegment(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, payload) + if err != nil { + return err + } + err = unix.Sendto(s.rawFD, segment, 0, s.rawSockAddr) + if err != nil { + return func(err error, m string) error { return err }(err, "sendto raw socket") + } + return nil + } + frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, nil, payload) + if err != nil { + return err + } + // Darwin inherits the historical BSD quirk: with IP_HDRINCL the kernel + // expects ip_len and ip_off in host byte order, not network byte order. + ip := IPv4(frame) + binary.NativeEndian.PutUint16(ip[2:4], ip.TotalLength()) + binary.NativeEndian.PutUint16(ip[6:8], uint16(ip.Flags())<<13|ip.FragmentOffset()) + err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) + if err != nil { + return func(err error, m string) error { return err }(err, "sendto raw socket") + } + return nil +} + +func (s *darwinSpoofer) Close() error { + if s.rawFD < 0 { + return nil + } + err := unix.Close(s.rawFD) + s.rawFD = -1 + return err +} diff --git a/transport/internet/tls/tlsspoof/raw_freebsd.go b/transport/internet/tls/tlsspoof/raw_freebsd.go new file mode 100644 index 000000000000..0f39a0ee5c39 --- /dev/null +++ b/transport/internet/tls/tlsspoof/raw_freebsd.go @@ -0,0 +1,172 @@ +package tlsspoof + +import ( + "encoding/binary" + "errors" + "fmt" + "net" + "net/netip" + "syscall" + "unsafe" + + "golang.org/x/sys/unix" +) + +const PlatformSupported = true + +// FreeBSD tcp_info offsets for snd_nxt and rcv_nxt. +// Derived from FreeBSD sys/netinet/tcp.h struct tcp_info layout. +// +// struct tcp_info { +// u8 state, __ca, __retrans, __probes, __backoff, opts, wscale = 8 bytes (with pad) +// u32 rto, __ato, snd_mss, rcv_mss = 16 bytes (offset 8) +// u32 __unacked, __sacked, __lost, __retrans, __fackets = 20 bytes (offset 24) +// u32 __last_data_sent, __last_ack_sent, last_data_recv, __last_ack_recv = 16 bytes (offset 44) +// u32 __pmtu, __rcv_ssthresh, rtt, rttvar, snd_ssthresh, snd_cwnd, __advmss, __reordering = 32 bytes (offset 60) +// u32 __rcv_rtt, rcv_space = 8 bytes (offset 92) +// u32 snd_wnd, snd_bwnd = 8 bytes (offset 100) +// u32 snd_nxt, rcv_nxt = 8 bytes (offset 108) +// ... remaining fields +// } +const ( + freebsdTCPInfoSndNxtOffset = 108 + freebsdTCPInfoRcvNxtOffset = 112 + freebsdTCPInfoMinSize = 116 // must read at least through rcv_nxt +) + +type freebsdSpoofer struct { + method Method + src netip.AddrPort + dst netip.AddrPort + rawFD int + rawSockAddr unix.Sockaddr + sendNext uint32 + receiveNext uint32 +} + +func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { + if method == MethodWrongTimestamp { + return nil, errors.New("tls_spoof: wrong-timestamp is not supported on FreeBSD") + } + tcpConn, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + fd, sockaddr, err := openFreeBSDRawSocket(src, dst) + if err != nil { + return nil, err + } + sendNext, receiveNext, err := readFreeBSDTCPSequence(tcpConn) + if err != nil { + unix.Close(fd) + return nil, err + } + return &freebsdSpoofer{ + method: method, + src: src, + dst: dst, + rawFD: fd, + rawSockAddr: sockaddr, + sendNext: sendNext, + receiveNext: receiveNext, + }, nil +} + +// readFreeBSDTCPSequence retrieves snd_nxt and rcv_nxt via TCP_INFO getsockopt. +func readFreeBSDTCPSequence(conn *net.TCPConn) (uint32, uint32, error) { + raw, err := conn.SyscallConn() + if err != nil { + return 0, 0, fmt.Errorf("tls_spoof: SyscallConn: %w", err) + } + var sendNext, receiveNext uint32 + var sockErr error + err = raw.Control(func(fd uintptr) { + buf := make([]byte, 256) // generous buffer for tcp_info + bufLen := uint32(len(buf)) + _, _, errno := syscall.Syscall6( + syscall.SYS_GETSOCKOPT, + fd, + uintptr(syscall.IPPROTO_TCP), + uintptr(0x20), // TCP_INFO = 0x20 + uintptr(unsafe.Pointer(&buf[0])), + uintptr(unsafe.Pointer(&bufLen)), + 0, + ) + if errno != 0 { + sockErr = fmt.Errorf("tls_spoof: getsockopt TCP_INFO: %w", errno) + return + } + if bufLen < freebsdTCPInfoMinSize { + sockErr = fmt.Errorf("tls_spoof: TCP_INFO too short: %d < %d", bufLen, freebsdTCPInfoMinSize) + return + } + sendNext = binary.NativeEndian.Uint32(buf[freebsdTCPInfoSndNxtOffset : freebsdTCPInfoSndNxtOffset+4]) + receiveNext = binary.NativeEndian.Uint32(buf[freebsdTCPInfoRcvNxtOffset : freebsdTCPInfoRcvNxtOffset+4]) + }) + if err != nil { + return 0, 0, err + } + if sockErr != nil { + return 0, 0, sockErr + } + return sendNext, receiveNext, nil +} + +func openFreeBSDRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) { + if dst.Addr().Is4() { + return openIPv4RawSocket(dst) + } + // FreeBSD, like macOS, does not support IPV6_HDRINCL on SOCK_RAW/IPPROTO_TCP. + // The kernel constructs the IPv6 header. Bind to the source address + // and let the kernel fill in the IPv6 header automatically. + fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, fmt.Errorf("tls_spoof: open AF_INET6 SOCK_RAW: %w", err) + } + err = unix.Bind(fd, &unix.SockaddrInet6{Addr: src.Addr().As16()}) + if err != nil { + unix.Close(fd) + return -1, nil, fmt.Errorf("tls_spoof: bind AF_INET6 SOCK_RAW: %w", err) + } + sockaddr := &unix.SockaddrInet6{Port: int(dst.Port()), Addr: dst.Addr().As16()} + return fd, sockaddr, nil +} + +func (s *freebsdSpoofer) Inject(payload []byte) error { + if !s.src.Addr().Is4() { + // IPv6: kernel builds the IP header, we supply TCP segment only. + segment, err := buildSpoofTCPSegment(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, payload) + if err != nil { + return err + } + err = unix.Sendto(s.rawFD, segment, 0, s.rawSockAddr) + if err != nil { + return fmt.Errorf("tls_spoof: sendto raw socket: %w", err) + } + return nil + } + // IPv4: we build the full IP+TCP frame with IP_HDRINCL. + frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, nil, payload) + if err != nil { + return err + } + // FreeBSD inherits the historical BSD quirk: with IP_HDRINCL the kernel + // expects ip_len and ip_off in host byte order, not network byte order. + ip := IPv4(frame) + binary.NativeEndian.PutUint16(ip[2:4], ip.TotalLength()) + binary.NativeEndian.PutUint16(ip[6:8], uint16(ip.Flags())<<13|ip.FragmentOffset()) + err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) + if err != nil { + return fmt.Errorf("tls_spoof: sendto raw socket: %w", err) + } + return nil +} + +func (s *freebsdSpoofer) Close() error { + if s.rawFD < 0 { + return nil + } + err := unix.Close(s.rawFD) + s.rawFD = -1 + return err +} diff --git a/transport/internet/tls/tlsspoof/raw_linux.go b/transport/internet/tls/tlsspoof/raw_linux.go new file mode 100644 index 000000000000..dc5c7311869c --- /dev/null +++ b/transport/internet/tls/tlsspoof/raw_linux.go @@ -0,0 +1,166 @@ +package tlsspoof + +import ( + "fmt" + "net" + "net/netip" + + "golang.org/x/sys/unix" +) + +const PlatformSupported = true + +const ( + // Values of enum { TCP_NO_QUEUE, TCP_RECV_QUEUE, TCP_SEND_QUEUE } from + // include/net/tcp.h; not exported by golang.org/x/sys/unix. + tcpRecvQueue = 1 + tcpSendQueue = 2 +) + +type linuxSpoofer struct { + method Method + src netip.AddrPort + dst netip.AddrPort + rawFD int + rawSockAddr unix.Sockaddr + sendNext uint32 + receiveNext uint32 + timestamp uint32 +} + +func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { + tcpConn, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + fd, sockaddr, err := openLinuxRawSocket(dst) + if err != nil { + return nil, err + } + spoofer := &linuxSpoofer{ + method: method, + src: src, + dst: dst, + rawFD: fd, + rawSockAddr: sockaddr, + } + err = spoofer.loadSequenceNumbers(tcpConn) + if err != nil { + unix.Close(fd) + return nil, err + } + return spoofer, nil +} + +func openLinuxRawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) { + if dst.Addr().Is4() { + return openIPv4RawSocket(dst) + } + fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, func(err error, m string) error { return err }(err, "open AF_INET6 SOCK_RAW") + } + err = unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_HDRINCL, 1) + if err != nil { + unix.Close(fd) + return -1, nil, func(err error, m string) error { return err }(err, "set IPV6_HDRINCL") + } + // Linux raw IPv6 sockets interpret sin6_port as a nexthdr protocol number + // (see raw(7)); any value other than 0 or the socket's IPPROTO_TCP causes + // sendto to fail with EINVAL. The destination is already encoded in the + // user-supplied IPv6 header under IPV6_HDRINCL. + sockaddr := &unix.SockaddrInet6{Addr: dst.Addr().As16()} + return fd, sockaddr, nil +} + +// loadSequenceNumbers puts the socket briefly into TCP_REPAIR mode to read +// snd_nxt and rcv_nxt from the kernel. TCP_REPAIR requires CAP_NET_ADMIN; +// callers must run as root or grant both CAP_NET_RAW and CAP_NET_ADMIN. +// +// If the TCP_REPAIR_OFF revert fails, the socket would stay in TCP_REPAIR +// state and subsequent Write() calls would silently buffer instead of sending. +// Surface that error so callers can abort. +func (s *linuxSpoofer) loadSequenceNumbers(tcpConn *net.TCPConn) error { + rawConn, err := tcpConn.SyscallConn() + if err != nil { + return err + } + var ctrlErr error + err = rawConn.Control(func(raw uintptr) { + fd := int(raw) + + if s.method == MethodWrongTimestamp { + timestamp, tsErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_TIMESTAMP) + if tsErr != nil { + ctrlErr = fmt.Errorf("tls_spoof: read timestamp: %w", tsErr) + return + } + s.timestamp = uint32(timestamp) + } + + ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR, unix.TCP_REPAIR_ON) + if ctrlErr != nil { + ctrlErr = fmt.Errorf("tls_spoof: enter TCP_REPAIR (need CAP_NET_ADMIN): %w", ctrlErr) + return + } + defer func() { + offErr := unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR, unix.TCP_REPAIR_OFF) + if offErr != nil { + offErr = fmt.Errorf("tls_spoof: leave TCP_REPAIR: %w", offErr) + if ctrlErr == nil { + ctrlErr = offErr + } else { + ctrlErr = fmt.Errorf("%v; also %w", ctrlErr, offErr) + } + } + }() + + ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR_QUEUE, tcpSendQueue) + if ctrlErr != nil { + ctrlErr = fmt.Errorf("tls_spoof: select TCP_SEND_QUEUE: %w", ctrlErr) + return + } + sendSequence, seqErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_QUEUE_SEQ) + if seqErr != nil { + ctrlErr = fmt.Errorf("tls_spoof: read send queue sequence: %w", seqErr) + return + } + ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR_QUEUE, tcpRecvQueue) + if ctrlErr != nil { + ctrlErr = fmt.Errorf("tls_spoof: select TCP_RECV_QUEUE: %w", ctrlErr) + return + } + receiveSequence, seqErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_QUEUE_SEQ) + if seqErr != nil { + ctrlErr = fmt.Errorf("tls_spoof: read recv queue sequence: %w", seqErr) + return + } + s.sendNext = uint32(sendSequence) + s.receiveNext = uint32(receiveSequence) + }) + if err != nil { + return err + } + return ctrlErr +} + +func (s *linuxSpoofer) Inject(payload []byte) error { + frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, s.timestamp, nil, payload) + if err != nil { + return err + } + err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) + if err != nil { + return func(err error, m string) error { return err }(err, "sendto raw socket") + } + return nil +} + +func (s *linuxSpoofer) Close() error { + if s.rawFD < 0 { + return nil + } + err := unix.Close(s.rawFD) + s.rawFD = -1 + return err +} diff --git a/transport/internet/tls/tlsspoof/raw_stub.go b/transport/internet/tls/tlsspoof/raw_stub.go new file mode 100644 index 000000000000..78be3c23391d --- /dev/null +++ b/transport/internet/tls/tlsspoof/raw_stub.go @@ -0,0 +1,15 @@ +//go:build !linux && !darwin && !freebsd && !(windows && (amd64 || 386)) + +package tlsspoof + +import ( + "net" + + "errors" +) + +const PlatformSupported = false + +func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { + return nil, errors.New("tls_spoof: unsupported platform") +} diff --git a/transport/internet/tls/tlsspoof/raw_unix.go b/transport/internet/tls/tlsspoof/raw_unix.go new file mode 100644 index 000000000000..7d5c32e3b5ef --- /dev/null +++ b/transport/internet/tls/tlsspoof/raw_unix.go @@ -0,0 +1,24 @@ +//go:build linux || darwin || freebsd + +package tlsspoof + +import ( + "net/netip" + + "golang.org/x/sys/unix" +) + +func openIPv4RawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) { + fd, err := unix.Socket(unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, func(err error, m string) error { return err }(err, "open AF_INET SOCK_RAW") + } + err = unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_HDRINCL, 1) + if err != nil { + unix.Close(fd) + return -1, nil, func(err error, m string) error { return err }(err, "set IP_HDRINCL") + } + sockaddr := &unix.SockaddrInet4{Port: int(dst.Port())} + sockaddr.Addr = dst.Addr().As4() + return fd, sockaddr, nil +} diff --git a/transport/internet/tls/tlsspoof/raw_windows.go b/transport/internet/tls/tlsspoof/raw_windows.go new file mode 100644 index 000000000000..17878ffce3dd --- /dev/null +++ b/transport/internet/tls/tlsspoof/raw_windows.go @@ -0,0 +1,234 @@ +//go:build windows && (amd64 || 386) + +package tlsspoof + +import ( + "errors" + "net" + "net/netip" + "slices" + "sync" + "sync/atomic" + "time" + + "github.com/xtls/xray-core/transport/internet/tls/tlsspoof/windivert" + "golang.org/x/sys/windows" +) + +const PlatformSupported = true + +// closeGracePeriod caps how long Close() waits for the divert goroutine to +// observe the kernel-emitted real ClientHello and perform the reorder +// (fake → real). In practice this completes in microseconds; the cap +// bounds the pathological case where the kernel buffers the packet. +const closeGracePeriod = 2 * time.Second + +// windowsSpoofer uses a single WinDivert handle for both capture and +// injection. Sequential Send() calls on one handle traverse one driver queue, +// so the fake provably precedes the released real on the wire — a guarantee +// two separate handles cannot make because cross-handle order depends on the +// scheduler. +type windowsSpoofer struct { + method Method + src, dst netip.AddrPort + divertH *windivert.Handle + + fakeReady chan []byte // buffered(1): staged by Inject + done chan struct{} // closed by run() on exit + closeOnce sync.Once + runErr atomic.Pointer[error] +} + +func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { + _, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + filter, err := windivert.OutboundTCP(src, dst) + if err != nil { + return nil, err + } + divertH, err := windivert.Open(filter, windivert.LayerNetwork, 0, 0) + if err != nil { + return nil, err + } + s := &windowsSpoofer{ + method: method, + src: src, + dst: dst, + divertH: divertH, + fakeReady: make(chan []byte, 1), + done: make(chan struct{}), + } + go s.run() + return s, nil +} + +func (s *windowsSpoofer) Inject(payload []byte) error { + select { + case s.fakeReady <- payload: + return nil + case <-s.done: + if p := s.runErr.Load(); p != nil { + return *p + } + return errors.New("tls_spoof: spoofer closed before Inject") + } +} + +func (s *windowsSpoofer) Close() error { + s.closeOnce.Do(func() { + // Give run() a grace window to finish handling the real packet. + select { + case <-s.done: + case <-time.After(closeGracePeriod): + // Force Recv() to return by closing the divert handle. + s.divertH.Close() + <-s.done + } + }) + if p := s.runErr.Load(); p != nil { + return *p + } + return nil +} + +func (s *windowsSpoofer) recordErr(err error) { s.runErr.Store(&err) } + +func (s *windowsSpoofer) run() { + defer close(s.done) + defer s.divertH.Close() + + buf := make([]byte, windivert.MTUMax) + for { + n, addr, err := s.divertH.Recv(buf) + if err != nil { + if errors.Is(err, windows.ERROR_OPERATION_ABORTED) || + errors.Is(err, windows.ERROR_NO_DATA) { + return + } + s.recordErr(err) + return + } + pkt := buf[:n] + seq, ack, tcpOptions, payloadLen, ok := parseTCPPacket(pkt, addr.IPv6()) + if !ok { + // Our filter is OutboundTCP(src, dst); a non-TCP or truncated + // match means driver state is suspect. Re-inject so the kernel + // still sees the byte stream, then abort — continuing would risk + // reordering against an unknown reference point. + _, sendErr := s.divertH.Send(pkt, &addr) + if sendErr != nil { + s.recordErr(sendErr) + return + } + s.recordErr(errors.New("windivert received malformed packet matching spoof filter")) + return + } + if payloadLen == 0 { + // Handshake ACK, keepalive, FIN — pass through unchanged. + _, err := s.divertH.Send(pkt, &addr) + if err != nil { + s.recordErr(err) + return + } + continue + } + + // Non-empty outbound TCP payload = the real ClientHello. + var fake []byte + select { + case fake = <-s.fakeReady: + default: + // Inject() not yet called — pass through and keep observing. + _, err := s.divertH.Send(pkt, &addr) + if err != nil { + s.recordErr(err) + return + } + continue + } + + var timestamp uint32 + if tsVal, hasTS := ParseTCPOptions(tcpOptions); hasTS { + timestamp = tsVal + } + frame, err := buildSpoofFrame(s.method, s.src, s.dst, seq, ack, timestamp, tcpOptions, fake) + if err != nil { + s.recordErr(err) + return + } + fakeAddr := addr // inherit Outbound, IfIdx + // buildSpoofFrame emits ready-to-wire bytes. The driver recomputes + // checksums on Send when TCPChecksum/IPChecksum are 0 — which would + // overwrite the intentionally corrupt checksum in WrongChecksum mode. + // Force both to 1 to keep our bytes intact. + fakeAddr.SetIPChecksum(true) + fakeAddr.SetTCPChecksum(true) + _, err = s.divertH.Send(frame, &fakeAddr) + if err != nil { + s.recordErr(err) + return + } + _, err = s.divertH.Send(pkt, &addr) + if err != nil { + s.recordErr(err) + return + } + return // single-shot reorder complete + } +} + +func parseTCPPacket(pkt []byte, isV6 bool) (seq, ack uint32, options []byte, payloadLen int, ok bool) { + if isV6 { + if len(pkt) < IPv6MinimumSize+TCPMinimumSize { + return 0, 0, nil, 0, false + } + ip := IPv6(pkt) + if ip.TransportProtocol() != TCPProtocolNumber { + return 0, 0, nil, 0, false + } + tcp := TCP(pkt[IPv6MinimumSize:]) + tcpHdr := int(tcp.DataOffset()) + if tcpHdr < TCPMinimumSize || IPv6MinimumSize+tcpHdr > len(pkt) { + return 0, 0, nil, 0, false + } + total := IPv6MinimumSize + int(ip.PayloadLength()) + if total == IPv6MinimumSize || total > len(pkt) { + total = len(pkt) + } + if total < IPv6MinimumSize+tcpHdr { + return 0, 0, nil, 0, false + } + return tcp.SequenceNumber(), tcp.AckNumber(), slices.Clone(tcp.Options()), + total - IPv6MinimumSize - tcpHdr, true + } + if len(pkt) < IPv4MinimumSize+TCPMinimumSize { + return 0, 0, nil, 0, false + } + ip := IPv4(pkt) + if ip.Protocol() != TCPProtocolNumber { + return 0, 0, nil, 0, false + } + ihl := int(ip.HeaderLength()) + // ihl+TCPMinimumSize guards the TCP-header field reads below; without + // this, an IPv4 packet with options (ihl>20) against a 40-byte buffer + // reads past the TCP slice when calling DataOffset. + if ihl < IPv4MinimumSize || ihl+TCPMinimumSize > len(pkt) { + return 0, 0, nil, 0, false + } + tcp := TCP(pkt[ihl:]) + tcpHdr := int(tcp.DataOffset()) + if tcpHdr < TCPMinimumSize || ihl+tcpHdr > len(pkt) { + return 0, 0, nil, 0, false + } + total := int(ip.TotalLength()) + if total == 0 || total > len(pkt) { + total = len(pkt) + } + if total < ihl+tcpHdr { + return 0, 0, nil, 0, false + } + return tcp.SequenceNumber(), tcp.AckNumber(), slices.Clone(tcp.Options()), + total - ihl - tcpHdr, true +} diff --git a/transport/internet/tls/tlsspoof/spoof.go b/transport/internet/tls/tlsspoof/spoof.go new file mode 100644 index 000000000000..6a9eae93a45b --- /dev/null +++ b/transport/internet/tls/tlsspoof/spoof.go @@ -0,0 +1,182 @@ +package tlsspoof + +import ( + "errors" + "fmt" + "net" + "runtime" + "syscall" +) + +type Method int + +const ( + MethodWrongSequence Method = iota + MethodWrongChecksum + MethodWrongAcknowledgment + MethodWrongMD5Sig + MethodWrongTimestamp +) + +const ( + MethodNameWrongSequence = "wrong-sequence" + MethodNameWrongChecksum = "wrong-checksum" + MethodNameWrongAcknowledgment = "wrong-ack" + MethodNameWrongMD5Sig = "wrong-md5" + MethodNameWrongTimestamp = "wrong-timestamp" +) + +func ParseOptions(spoof, method string) (string, Method, error) { + if spoof == "" { + if method != "" { + return "", 0, errors.New("spoof_method requires spoof") + } + return "", 0, nil + } + if net.ParseIP(spoof) != nil { + return "", 0, errors.New("tls_spoof: IP-literal server names are not allowed") + } + if !PlatformSupported { + return "", 0, errors.New("tls_spoof is not supported on this platform") + } + parsedMethod, err := ParseMethod(method) + if err != nil { + return "", 0, err + } + return spoof, parsedMethod, nil +} + +func ParseMethod(s string) (Method, error) { + switch s { + case "", MethodNameWrongSequence: + return MethodWrongSequence, nil + case MethodNameWrongChecksum: + return MethodWrongChecksum, nil + case MethodNameWrongAcknowledgment: + return MethodWrongAcknowledgment, nil + case MethodNameWrongMD5Sig: + return MethodWrongMD5Sig, nil + case MethodNameWrongTimestamp: + return MethodWrongTimestamp, nil + default: + return 0, fmt.Errorf("tls_spoof: unknown method: %s", s) + } +} + +func (m Method) String() string { + switch m { + case MethodWrongSequence: + return MethodNameWrongSequence + case MethodWrongChecksum: + return MethodNameWrongChecksum + case MethodWrongAcknowledgment: + return MethodNameWrongAcknowledgment + case MethodWrongMD5Sig: + return MethodNameWrongMD5Sig + case MethodWrongTimestamp: + return MethodNameWrongTimestamp + default: + return "unknown" + } +} + +type rawSpoofer interface { + Inject(payload []byte) error + Close() error +} + +type Conn struct { + net.Conn + spoofer rawSpoofer + fakeHello []byte + injectionCount int + maxInjections int // how many times to inject; default 1 +} + +// NewConn wraps a connection with TLS spoofing. maxInjections controls how +// many Write() calls will trigger a fake ClientHello injection (0 or 1 = single-shot). +func NewConn(conn net.Conn, method Method, fakeSNI string, maxInjections int) (*Conn, error) { + spoofer, err := newRawSpoofer(conn, method) + if err != nil { + return nil, wrapPermissionError(err) + } + result, err := newConn(conn, spoofer, fakeSNI, maxInjections) + if err != nil { + spoofer.Close() + return nil, err + } + return result, nil +} + +func newConn(conn net.Conn, spoofer rawSpoofer, fakeSNI string, maxInjections int) (*Conn, error) { + fakeHello, err := buildFakeClientHello(fakeSNI) + if err != nil { + return nil, func(err error, m string) error { return err }(err, "tls_spoof: build fake ClientHello") + } + if maxInjections <= 0 { + maxInjections = 1 + } + return &Conn{ + Conn: conn, + spoofer: spoofer, + fakeHello: fakeHello, + maxInjections: maxInjections, + }, nil +} + +func (c *Conn) Write(b []byte) (n int, err error) { + if c.injectionCount >= c.maxInjections { + return c.Conn.Write(b) + } + err = c.spoofer.Inject(c.fakeHello) + if err != nil { + return 0, func(err error, m string) error { return err }(err, "tls_spoof: inject") + } + c.injectionCount++ + if c.injectionCount >= c.maxInjections { + closeErr := c.spoofer.Close() + if closeErr != nil { + return 0, func(err error, m string) error { return err }(closeErr, "tls_spoof: close spoofer") + } + } + return c.Conn.Write(b) +} + +func (c *Conn) Close() error { + return func(e1, e2 error) error { + if e1 != nil { + return e1 + } + return e2 + }(c.Conn.Close(), c.spoofer.Close()) +} + +func (c *Conn) ReaderReplaceable() bool { + return true +} + +func (c *Conn) WriterReplaceable() bool { + return c.injectionCount >= c.maxInjections +} + +func (c *Conn) Upstream() any { + return c.Conn +} + +// wrapPermissionError adds platform-specific hints when the spoofer fails +// due to insufficient privileges. +func wrapPermissionError(err error) error { + if !errors.Is(err, syscall.EPERM) && !errors.Is(err, syscall.EACCES) { + return err + } + switch runtime.GOOS { + case "linux": + return fmt.Errorf("%w\n Hint: run as root, or grant capabilities:\n sudo setcap cap_net_raw,cap_net_admin+ep /path/to/xray", err) + case "darwin": + return fmt.Errorf("%w\n Hint: TLS spoofing requires root on macOS. Run with: sudo ./xray", err) + case "freebsd": + return fmt.Errorf("%w\n Hint: TLS spoofing requires root on FreeBSD. Run with: sudo ./xray", err) + default: + return err + } +} diff --git a/transport/internet/tls/tlsspoof/spoof_freebsd_test.go b/transport/internet/tls/tlsspoof/spoof_freebsd_test.go new file mode 100644 index 000000000000..a8ab2ccae823 --- /dev/null +++ b/transport/internet/tls/tlsspoof/spoof_freebsd_test.go @@ -0,0 +1,82 @@ +package tlsspoof + +import ( + "net" + "net/netip" + "os/user" + "testing" + + "golang.org/x/sys/unix" +) + +func TestFreeBSDTCPSequence(t *testing.T) { + u, err := user.Current() + if err == nil && u.Uid != "0" { + t.Skip("skipping test; must be root to use raw sockets / TCP_INFO on FreeBSD") + } + + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen failed: %v", err) + } + defer ln.Close() + + serverDone := make(chan struct{}) + go func() { + conn, err := ln.Accept() + if err == nil { + conn.Write([]byte("hello")) + conn.Close() + } + close(serverDone) + }() + + client, err := net.Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dial failed: %v", err) + } + defer client.Close() + + tcpConn, ok := client.(*net.TCPConn) + if !ok { + t.Fatalf("expected *net.TCPConn, got %T", client) + } + + sndNxt, rcvNxt, err := readFreeBSDTCPSequence(tcpConn) + if err != nil { + t.Fatalf("readFreeBSDTCPSequence failed: %v", err) + } + + if sndNxt == 0 && rcvNxt == 0 { + t.Errorf("expected non-zero sequence numbers, got sndNxt=%d rcvNxt=%d", sndNxt, rcvNxt) + } + t.Logf("FreeBSD TCP sequence retrieved: snd_nxt=%d, rcv_nxt=%d", sndNxt, rcvNxt) + <-serverDone +} + +func TestFreeBSDRawSocket(t *testing.T) { + u, err := user.Current() + if err == nil && u.Uid != "0" { + t.Skip("skipping test; must be root to open raw sockets") + } + + dst := netip.MustParseAddrPort("8.8.8.8:443") + src := netip.MustParseAddrPort("127.0.0.1:12345") + + fd, sockaddr, err := openFreeBSDRawSocket(src, dst) + if err != nil { + t.Fatalf("openFreeBSDRawSocket failed: %v", err) + } + defer func() { + if fd >= 0 { + unix.Close(fd) + } + }() + + if fd < 0 { + t.Errorf("expected valid fd, got %d", fd) + } + if sockaddr == nil { + t.Error("expected valid sockaddr, got nil") + } +} diff --git a/transport/internet/tls/tlsspoof/spoof_test.go b/transport/internet/tls/tlsspoof/spoof_test.go new file mode 100644 index 000000000000..c51e4fddceb0 --- /dev/null +++ b/transport/internet/tls/tlsspoof/spoof_test.go @@ -0,0 +1,111 @@ +package tlsspoof + +import ( + "testing" +) + +func TestBuildFakeClientHello(t *testing.T) { + hello, err := buildFakeClientHello("www.example.com") + if err != nil { + t.Fatal("buildFakeClientHello returned error:", err) + } + if len(hello) == 0 { + t.Fatal("buildFakeClientHello returned empty payload") + } + // TLS record header: content type 0x16 (handshake) + if hello[0] != 0x16 { + t.Fatalf("expected TLS handshake record type 0x16, got 0x%02x", hello[0]) + } + // TLS version: 0x0301 (TLS 1.0 record layer) + if hello[1] != 0x03 || hello[2] != 0x01 { + t.Fatalf("unexpected TLS record version: 0x%02x%02x", hello[1], hello[2]) + } + t.Logf("ClientHello payload length: %d bytes", len(hello)) +} + +func TestBuildFakeClientHelloEmptySNI(t *testing.T) { + _, err := buildFakeClientHello("") + if err == nil { + t.Fatal("expected error for empty SNI") + } +} + +func TestParseMethod(t *testing.T) { + tests := []struct { + input string + expected Method + hasErr bool + }{ + {"", MethodWrongSequence, false}, + {"wrong-sequence", MethodWrongSequence, false}, + {"wrong-checksum", MethodWrongChecksum, false}, + {"wrong-ack", MethodWrongAcknowledgment, false}, + {"wrong-md5", MethodWrongMD5Sig, false}, + {"wrong-timestamp", MethodWrongTimestamp, false}, + {"invalid", 0, true}, + } + for _, tt := range tests { + m, err := ParseMethod(tt.input) + if tt.hasErr { + if err == nil { + t.Errorf("ParseMethod(%q): expected error, got nil", tt.input) + } + continue + } + if err != nil { + t.Errorf("ParseMethod(%q): unexpected error: %v", tt.input, err) + continue + } + if m != tt.expected { + t.Errorf("ParseMethod(%q) = %v, want %v", tt.input, m, tt.expected) + } + } +} + +func TestParseOptions(t *testing.T) { + // Empty spoof should be a no-op + sni, _, err := ParseOptions("", "") + if err != nil { + t.Fatal("ParseOptions(\"\", \"\"): unexpected error:", err) + } + if sni != "" { + t.Fatalf("expected empty SNI, got %q", sni) + } + + // spoof_method without spoof should error + _, _, err = ParseOptions("", "wrong-checksum") + if err == nil { + t.Fatal("expected error when spoof_method set without spoof") + } + + // Valid combo + sni, method, err := ParseOptions("fake.example.com", "wrong-checksum") + if err != nil { + t.Fatal("ParseOptions: unexpected error:", err) + } + if sni != "fake.example.com" { + t.Fatalf("expected SNI 'fake.example.com', got %q", sni) + } + if method != MethodWrongChecksum { + t.Fatalf("expected MethodWrongChecksum, got %v", method) + } + + // IP-literal should be rejected + _, _, err = ParseOptions("1.2.3.4", "wrong-checksum") + if err == nil { + t.Fatal("expected error for IP-literal spoof") + } + _, _, err = ParseOptions("::1", "wrong-checksum") + if err == nil { + t.Fatal("expected error for IP-literal spoof") + } +} + +func TestMethodString(t *testing.T) { + if MethodWrongSequence.String() != "wrong-sequence" { + t.Fatalf("unexpected method string: %s", MethodWrongSequence.String()) + } + if MethodWrongChecksum.String() != "wrong-checksum" { + t.Fatalf("unexpected method string: %s", MethodWrongChecksum.String()) + } +} diff --git a/transport/internet/tls/tlsspoof/tcpip.go b/transport/internet/tls/tlsspoof/tcpip.go new file mode 100644 index 000000000000..3392704c405a --- /dev/null +++ b/transport/internet/tls/tlsspoof/tcpip.go @@ -0,0 +1,155 @@ +package tlsspoof + +import ( + "encoding/binary" + "net/netip" +) + +const ( + IPv4MinimumSize = 20 + IPv6MinimumSize = 40 + TCPMinimumSize = 20 + TCPProtocolNumber = 6 + + TCPOptionEOL = 0 + TCPOptionNOP = 1 + TCPOptionTS = 8 + TCPOptionTSLength = 10 + + TCPFlagFin = 0x01 + TCPFlagSyn = 0x02 + TCPFlagRst = 0x04 + TCPFlagPsh = 0x08 + TCPFlagAck = 0x10 +) + +func Checksum(data []byte, initial uint16) uint16 { + var csum uint32 = uint32(initial) + for i := 0; i < len(data)-1; i += 2 { + csum += uint32(binary.BigEndian.Uint16(data[i:])) + } + if len(data)%2 == 1 { + csum += uint32(data[len(data)-1]) << 8 + } + for csum > 0xffff { + csum = (csum >> 16) + (csum & 0xffff) + } + return uint16(csum) +} + +func PseudoHeaderChecksum(protocol uint8, srcAddr, dstAddr []byte, totalLen uint16) uint16 { + var csum uint32 + for i := 0; i < len(srcAddr); i += 2 { + csum += uint32(binary.BigEndian.Uint16(srcAddr[i:])) + } + for i := 0; i < len(dstAddr); i += 2 { + csum += uint32(binary.BigEndian.Uint16(dstAddr[i:])) + } + csum += uint32(protocol) + csum += uint32(totalLen) + for csum > 0xffff { + csum = (csum >> 16) + (csum & 0xffff) + } + return uint16(csum) +} + +func CombineChecksum(c1, c2 uint16) uint16 { + csum := uint32(c1) + uint32(c2) + for csum > 0xffff { + csum = (csum >> 16) + (csum & 0xffff) + } + return uint16(csum) +} + +func EncodeTSOption(val uint32, ecr uint32, b []byte) { + b[0] = TCPOptionTS + b[1] = TCPOptionTSLength + binary.BigEndian.PutUint32(b[2:], val) + binary.BigEndian.PutUint32(b[6:], ecr) +} + +func ParseTCPOptions(b []byte) (tsVal uint32, hasTS bool) { + for i := 0; i < len(b); { + if b[i] == TCPOptionEOL { + break + } + if b[i] == TCPOptionNOP { + i++ + continue + } + if i+1 >= len(b) { + break + } + optLen := int(b[i+1]) + if optLen < 2 || i+optLen > len(b) { + break + } + if b[i] == TCPOptionTS && optLen == TCPOptionTSLength { + return binary.BigEndian.Uint32(b[i+2:]), true + } + i += optLen + } + return 0, false +} + +// IPv4 header representation +type IPv4 []byte + +func (b IPv4) TotalLength() uint16 { return binary.BigEndian.Uint16(b[2:]) } +func (b IPv4) Flags() uint8 { return uint8(binary.BigEndian.Uint16(b[6:]) >> 13) } +func (b IPv4) FragmentOffset() uint16 { return binary.BigEndian.Uint16(b[6:]) & 0x1fff } +func (b IPv4) Protocol() uint8 { return b[9] } +func (b IPv4) HeaderLength() uint8 { return (b[0] & 0x0f) * 4 } + +func (b IPv4) Encode(totalLength uint16, id uint16, ttl uint8, protocol uint8, src, dst netip.Addr) { + b[0] = (4 << 4) | 5 // IPv4, Header Length = 20 + b[1] = 0 // TOS + binary.BigEndian.PutUint16(b[2:], totalLength) + binary.BigEndian.PutUint16(b[4:], id) + binary.BigEndian.PutUint16(b[6:], 0) // Flags and Fragment Offset + b[8] = ttl + b[9] = protocol + b[10] = 0 // Checksum (0 for calculation) + copy(b[12:16], src.AsSlice()) + copy(b[16:20], dst.AsSlice()) + csum := Checksum(b[:20], 0) + binary.BigEndian.PutUint16(b[10:], ^csum) +} + +type IPv6 []byte + +func (b IPv6) PayloadLength() uint16 { return binary.BigEndian.Uint16(b[4:]) } +func (b IPv6) TransportProtocol() uint8 { return b[6] } + +func (b IPv6) Encode(payloadLength uint16, transportProtocol uint8, hopLimit uint8, src, dst netip.Addr) { + binary.BigEndian.PutUint32(b[0:], 6<<28) // Version 6, Traffic Class 0, Flow Label 0 + binary.BigEndian.PutUint16(b[4:], payloadLength) + b[6] = transportProtocol + b[7] = hopLimit + copy(b[8:24], src.AsSlice()) + copy(b[24:40], dst.AsSlice()) +} + +type TCP []byte + +func (b TCP) DataOffset() uint8 { return (b[12] >> 4) * 4 } +func (b TCP) SequenceNumber() uint32 { return binary.BigEndian.Uint32(b[4:]) } +func (b TCP) AckNumber() uint32 { return binary.BigEndian.Uint32(b[8:]) } +func (b TCP) Options() []byte { return b[TCPMinimumSize:b.DataOffset()] } +func (b TCP) SetChecksum(csum uint16) { binary.BigEndian.PutUint16(b[16:], csum) } + +func (b TCP) Encode(srcPort, dstPort uint16, seqNum, ackNum uint32, dataOffset uint8, flags uint8, windowSize uint16) { + binary.BigEndian.PutUint16(b[0:], srcPort) + binary.BigEndian.PutUint16(b[2:], dstPort) + binary.BigEndian.PutUint32(b[4:], seqNum) + binary.BigEndian.PutUint32(b[8:], ackNum) + b[12] = (dataOffset / 4) << 4 + b[13] = flags + binary.BigEndian.PutUint16(b[14:], windowSize) + b[16] = 0 // Checksum + binary.BigEndian.PutUint16(b[18:], 0) // Urgent pointer +} + +func (b TCP) CalculateChecksum(initial uint16) uint16 { + return Checksum(b, initial) +} diff --git a/transport/internet/tls/tlsspoof/windivert/assets/LICENSE.txt b/transport/internet/tls/tlsspoof/windivert/assets/LICENSE.txt new file mode 100644 index 000000000000..8489a8e773c3 --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/assets/LICENSE.txt @@ -0,0 +1,1191 @@ +WinDivert is dual-licensed under your choice of the GNU Lesser General Public +License (LGPL) Version 3 or the GNU General Public License (GPL) Version 2. +Copies of the LGPLv3, GPLv3 and GPLv2 are provided below. + +============================================================================== + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + +============================================================================== + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + +============================================================================== + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + diff --git a/transport/internet/tls/tlsspoof/windivert/assets/WinDivert32.sys b/transport/internet/tls/tlsspoof/windivert/assets/WinDivert32.sys new file mode 100644 index 0000000000000000000000000000000000000000..d06738cbb78351cc57754fd484b77fac0df52cea GIT binary patch literal 79792 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%0DB0KXuLbP(03=gskd! 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�fDFaN&0OjG}#mY093SN>I=&rkPX=@)ne0>Ce<3;wQg-e3KWzfbe5=9r)Ee=ZMy)^}b&;%D=Wr5V{xe~-hn)XvY3e=ZNxJf5%r|GGR( zQ@aBA4&ET=7~Zk=1bu~97g1mVY>3bR8Q+Rvmsk`0n0))z1li)wAm{E^xIU->M-lM| zB0?ABYzB6j2)J_#o9{3Tb!hvb!E*yG{Xru8nejf!Wk{o(-Ph$>mKXP1_FI-yrX=NW z@Ewusy|&@uA|ccAnq!}@xuC`StqUEv)?T+PwA=DQbJ_9K)&RQ;4kpJ|9QZi#dYqcK zQPebZW3!lsS(Q|h3O>0#Rlz%={LteK#x=1$t>WD-ocmPD53?Mg8CS>NeBPKO9~Xaf zaJ1#Mpyf98b3*nPSU28u#na1L7VFm5S7&2OV}+j`Xzi~U4b#u8b5=9mm}1Rk5`QYw zLC_+5kKO*@!6@I_+rd{`Wn#?=E-cb_-Qv3Hw$qM=B36t=npNDzH%}KNo^6*2Ir1n- z_teXEpFTcYFr`VNnIAx1G)!(dZ~x$IWx4-}vxmKLsiwOZy|+vhQt&!?z;#LTHk{sJ z)Lw3T+5N4hZWp%PjZo0f3w^|;bN5m#9D}G4Q1XJmnZWN#@SD~Tew$&)Y#A@OE<=L! zW>D4#%CZ=x|Jon;0Mg8d1I~f{4}NUz4DBAd_hV6&F{WjzvX|rdvsnPy2m=k*=~F)7 z$d4*o4g9!LgTm+@0RgTn?HmnUsTd-Dwgv{R7UJ#soh&{Cf)6l94c^fJKt2HkqIk!* zd%3_MxL)-E1mZIIX4H`byB=jAmm4&!M)mffd&uG7Ivl6rBX
L0&$-01vp2hrm4z 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%0A~=3F;g8l!;0U`QAv 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 E0GFf)F8}}l literal 0 HcmV?d00001 diff --git a/transport/internet/tls/tlsspoof/windivert/assets/WinDivert64.sys b/transport/internet/tls/tlsspoof/windivert/assets/WinDivert64.sys new file mode 100644 index 0000000000000000000000000000000000000000..218ccaf423ef0a67696226f9ef3a09149e4441d0 GIT binary patch literal 94144 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 Mok_m={2+h)7u^}>0RR91 literal 0 HcmV?d00001 diff --git a/transport/internet/tls/tlsspoof/windivert/assets_386.go b/transport/internet/tls/tlsspoof/windivert/assets_386.go new file mode 100644 index 000000000000..0cbf35ed5cbf --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/assets_386.go @@ -0,0 +1,14 @@ +//go:build windows && 386 + +package windivert + +import _ "embed" + +//go:embed assets/WinDivert32.sys +var sysBytes []byte + +func assetFiles() []assetFile { + return []assetFile{{"WinDivert32.sys", sysBytes}} +} + +func driverSysName() string { return "WinDivert32.sys" } diff --git a/transport/internet/tls/tlsspoof/windivert/assets_amd64.go b/transport/internet/tls/tlsspoof/windivert/assets_amd64.go new file mode 100644 index 000000000000..2c9fb6c6ad19 --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/assets_amd64.go @@ -0,0 +1,14 @@ +//go:build windows && amd64 + +package windivert + +import _ "embed" + +//go:embed assets/WinDivert64.sys +var sysBytes []byte + +func assetFiles() []assetFile { + return []assetFile{{"WinDivert64.sys", sysBytes}} +} + +func driverSysName() string { return "WinDivert64.sys" } diff --git a/transport/internet/tls/tlsspoof/windivert/assets_unsupported.go b/transport/internet/tls/tlsspoof/windivert/assets_unsupported.go new file mode 100644 index 000000000000..04698953fa6b --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/assets_unsupported.go @@ -0,0 +1,7 @@ +//go:build windows && !amd64 && !386 + +package windivert + +func assetFiles() []assetFile { return nil } + +func driverSysName() string { return "" } diff --git a/transport/internet/tls/tlsspoof/windivert/driver_windows.go b/transport/internet/tls/tlsspoof/windivert/driver_windows.go new file mode 100644 index 000000000000..50e94c578422 --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/driver_windows.go @@ -0,0 +1,211 @@ +//go:build windows + +package windivert + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "strconv" + "sync" + + "golang.org/x/sys/windows" +) + +const ( + driverServiceName = "WinDivert" + driverDeviceName = `\\.\WinDivert` +) + +var ( + driverOnce sync.Once + driverErr error + // driverDevName is ASCII-safe and must be available before ensureDriver + // so Open can try CreateFile first and only install on FILE_NOT_FOUND. + driverDevName, _ = windows.UTF16PtrFromString(driverDeviceName) +) + +// Requires SeLoadDriverPrivilege (Administrator). Running the 386 build +// under WOW64 on a 64-bit kernel is rejected — use the amd64 build. +func ensureDriver() error { + driverOnce.Do(func() { + driverErr = installDriver() + }) + return driverErr +} + +func installDriver() error { + if runtime.GOARCH == "386" { + var isWow64 bool + err := windows.IsWow64Process(windows.CurrentProcess(), &isWow64) + if err == nil && isWow64 { + return errors.New("windivert: 386 build detected running under WOW64 on a 64-bit kernel; use the amd64 build") + } + } + + dir, err := ensureExtracted() + if err != nil { + return err + } + sysPath := filepath.Join(dir, driverSysName()) + sysPathW, err := windows.UTF16PtrFromString(sysPath) + if err != nil { + return fmt.Errorf("windivert: utf16 driver path: %w", err) + } + + // Serialize driver install across concurrent processes. + mutexName, _ := windows.UTF16PtrFromString("WinDivertDriverInstallMutex") + mutex, err := windows.CreateMutex(nil, false, mutexName) + if err != nil { + return fmt.Errorf("windivert: create install mutex: %w", err) + } + defer windows.CloseHandle(mutex) + _, err = windows.WaitForSingleObject(mutex, windows.INFINITE) + if err != nil { + return fmt.Errorf("windivert: wait install mutex: %w", err) + } + defer windows.ReleaseMutex(mutex) + + manager, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_ALL_ACCESS) + if err != nil { + return fmt.Errorf("windivert: open SCM: %w", err) + } + defer windows.CloseServiceHandle(manager) + + serviceNameW, _ := windows.UTF16PtrFromString(driverServiceName) + service, err := windows.OpenService(manager, serviceNameW, windows.SERVICE_ALL_ACCESS) + if err != nil { + service, err = windows.CreateService( + manager, + serviceNameW, + serviceNameW, + windows.SERVICE_ALL_ACCESS, + windows.SERVICE_KERNEL_DRIVER, + windows.SERVICE_DEMAND_START, + windows.SERVICE_ERROR_NORMAL, + sysPathW, + nil, nil, nil, nil, nil, + ) + if err != nil { + if errors.Is(err, windows.ERROR_SERVICE_EXISTS) { + service, err = windows.OpenService(manager, serviceNameW, windows.SERVICE_ALL_ACCESS) + } + if err != nil { + return wrapDriverInstallError(err) + } + } + } + defer windows.CloseServiceHandle(service) + + err = windows.StartService(service, 0, nil) + if err != nil && errors.Is(err, windows.ERROR_SERVICE_DISABLED) { + // A prior process called DeleteService on a still-running kernel + // driver: SCM marks the record for deletion and flips START_TYPE + // to DISABLED until the last handle closes. Re-enable so we can + // start it instead of waiting for a reboot. + err = windows.ChangeServiceConfig( + service, + windows.SERVICE_NO_CHANGE, + windows.SERVICE_DEMAND_START, + windows.SERVICE_NO_CHANGE, + nil, nil, nil, nil, nil, nil, nil, + ) + if err != nil { + return fmt.Errorf("windivert: re-enable disabled service: %w", err) + } + err = windows.StartService(service, 0, nil) + } + if err == nil { + // Mark for deletion so the driver unregisters when the last handle + // closes or on next reboot. Matches the upstream DLL's behavior: + // only the process that actually started the service takes on the + // cleanup responsibility. If another process already started it, + // we leave DeleteService to them. + _ = windows.DeleteService(service) + } else if !errors.Is(err, windows.ERROR_SERVICE_ALREADY_RUNNING) { + return fmt.Errorf("windivert: start service: %w", err) + } + return nil +} + +func wrapDriverInstallError(err error) error { + if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + return fmt.Errorf("windivert: installing the kernel driver requires Administrator privileges: %w", err) + } + return fmt.Errorf("windivert: create service: %w", err) +} + +type assetFile struct { + name string + data []byte +} + +var ( + extractOnce sync.Once + extractErr error + extractDir string +) + +// The on-disk copy is protected by Windows Authenticode signature +// enforcement, which rejects any tampered .sys at StartService time. +func ensureExtracted() (string, error) { + extractOnce.Do(func() { + extractDir, extractErr = extractImpl() + }) + return extractDir, extractErr +} + +func extractImpl() (string, error) { + files := assetFiles() + if len(files) == 0 { + return "", fmt.Errorf("windivert: unsupported architecture %s", runtime.GOARCH) + } + + base, err := os.UserCacheDir() + if err != nil { + return "", fmt.Errorf("windivert: locate user cache dir: %w", err) + } + dir := filepath.Join(base, "xray-core", "windivert", "v"+AssetVersion) + err = os.MkdirAll(dir, 0o755) + if err != nil { + return "", fmt.Errorf("windivert: mkdir %s: %w", dir, err) + } + + for _, asset := range files { + err = ensureAsset(dir, asset) + if err != nil { + return "", err + } + } + return dir, nil +} + +// Concurrent sing-box processes race on os.Rename (atomic on NTFS); +// whichever wins creates the final file. Writers that lose the race +// silently discard their temp copy. +func ensureAsset(dir string, asset assetFile) error { + target := filepath.Join(dir, asset.name) + _, err := os.Stat(target) + if err == nil { + return nil + } + if !os.IsNotExist(err) { + return fmt.Errorf("windivert: stat %s: %w", asset.name, err) + } + tmp := target + ".tmp-" + strconv.Itoa(os.Getpid()) + err = os.WriteFile(tmp, asset.data, 0o644) + if err != nil { + return fmt.Errorf("windivert: write %s: %w", asset.name, err) + } + err = os.Rename(tmp, target) + if err != nil { + os.Remove(tmp) + if _, statErr := os.Stat(target); statErr == nil { + return nil + } + return fmt.Errorf("windivert: rename %s: %w", asset.name, err) + } + return nil +} diff --git a/transport/internet/tls/tlsspoof/windivert/filter.go b/transport/internet/tls/tlsspoof/windivert/filter.go new file mode 100644 index 000000000000..d63adae2b630 --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/filter.go @@ -0,0 +1,181 @@ +package windivert + +import ( + "encoding/binary" + "errors" + "net/netip" +) + +// WINDIVERT_FILTER VM instruction layout (24 bytes, #pragma pack(1)): +// +// word 0 (LE): field:11 | test:5 | success:16 +// word 1 (LE): failure:16 | neg:1 | reserved:15 +// words 2..5: arg[4] (native-endian uint32 each) +// +// The driver walks this as a decision tree: evaluate the test at inst i; +// on success jump to success; on failure jump to failure. Continuations +// 0x7FFE and 0x7FFF are ACCEPT and REJECT terminals. +const ( + filterInstBytes = 24 + filterMaxInsts = 256 + + fieldZero = 0 + fieldOutbound = 2 + fieldIP = 5 + fieldIPv6 = 6 + fieldTCP = 8 + fieldIPSrcAddr = 21 + fieldIPDstAddr = 22 + fieldIPv6SrcAddr = 28 + fieldIPv6DstAddr = 29 + fieldTCPSrcPort = 38 + fieldTCPDstPort = 39 + + testEQ = 0 + + resultAccept uint16 = 0x7FFE + resultReject uint16 = 0x7FFF +) + +// Filter flags passed to IOCTL_WINDIVERT_STARTUP alongside the compiled +// filter. These tell the driver what *kinds* of packets the filter might +// match, used as a kernel-side fast-reject. +const ( + filterFlagOutbound uint64 = 0x0020 + filterFlagIP uint64 = 0x0040 + filterFlagIPv6 uint64 = 0x0080 +) + +type filterInst struct { + field uint16 // 11 bits used + test uint8 // 5 bits used + success uint16 + failure uint16 + neg bool + arg [4]uint32 +} + +// Filter is a typed specification of packets to capture. It replaces +// WinDivert's filter string language. +// +// Zero value = "reject all" (match nothing), suitable for send-only handles. +type Filter struct { + insts []filterInst + flags uint64 // filter flags for STARTUP ioctl +} + +// reject returns a filter that matches no packet. The empty insts slice +// is encoded as a single rejecting instruction by encode(). +func reject() *Filter { + return &Filter{} +} + +// OutboundTCP returns a filter matching outbound TCP packets on the given +// 5-tuple. Both addresses must share an address family (IPv4 or IPv6). +func OutboundTCP(src, dst netip.AddrPort) (*Filter, error) { + if !src.IsValid() || !dst.IsValid() { + return nil, errors.New("windivert: filter: invalid address port") + } + if src.Addr().Is4() != dst.Addr().Is4() { + return nil, errors.New("windivert: filter: mixed IPv4/IPv6") + } + f := &Filter{ + flags: filterFlagOutbound, + } + // Insts chain as AND: each test's failure = REJECT, success = next inst. + // The final inst's success = ACCEPT. + f.add(fieldOutbound, testEQ, argUint32(1)) + if src.Addr().Is4() { + f.flags |= filterFlagIP + f.add(fieldIP, testEQ, argUint32(1)) + f.add(fieldTCP, testEQ, argUint32(1)) + f.add(fieldIPSrcAddr, testEQ, argIPv4(src.Addr())) + f.add(fieldIPDstAddr, testEQ, argIPv4(dst.Addr())) + } else { + f.flags |= filterFlagIPv6 + f.add(fieldIPv6, testEQ, argUint32(1)) + f.add(fieldTCP, testEQ, argUint32(1)) + f.add(fieldIPv6SrcAddr, testEQ, argIPv6(src.Addr())) + f.add(fieldIPv6DstAddr, testEQ, argIPv6(dst.Addr())) + } + f.add(fieldTCPSrcPort, testEQ, argUint32(uint32(src.Port()))) + f.add(fieldTCPDstPort, testEQ, argUint32(uint32(dst.Port()))) + return f, nil +} + +func (f *Filter) add(field uint16, test uint8, arg [4]uint32) { + f.insts = append(f.insts, filterInst{field: field, test: test, arg: arg}) +} + +func argUint32(v uint32) [4]uint32 { return [4]uint32{v, 0, 0, 0} } + +// argIPv4 encodes an IPv4 address for IP_SRCADDR/IP_DSTADDR. The driver +// compares against an IPv4-mapped-IPv6 form: {host_order_u32, 0x0000FFFF, +// 0, 0} (see sys/windivert.c windivert_get_ipv4_addr and the IPv4_SRCADDR +// val-word construction). Omitting the 0x0000FFFF marker causes the EQ +// test to fail for every packet. +func argIPv4(addr netip.Addr) [4]uint32 { + b := addr.As4() + return [4]uint32{binary.BigEndian.Uint32(b[:]), 0x0000FFFF, 0, 0} +} + +// argIPv6 encodes an IPv6 address for IPV6_SRCADDR/IPV6_DSTADDR. The +// driver stores the address as four host-order uint32s in REVERSED word +// order: val[0]=low (bytes 12..15), val[3]=high (bytes 0..3). See +// sys/windivert.c windivert_outbound_network_v6_classify val-word +// construction. +func argIPv6(addr netip.Addr) [4]uint32 { + b := addr.As16() + return [4]uint32{ + binary.BigEndian.Uint32(b[12:16]), + binary.BigEndian.Uint32(b[8:12]), + binary.BigEndian.Uint32(b[4:8]), + binary.BigEndian.Uint32(b[0:4]), + } +} + +// encode serializes the Filter to the on-wire WINDIVERT_FILTER[] format +// plus the filter_flags for STARTUP ioctl. +func (f *Filter) encode() ([]byte, uint64, error) { + if len(f.insts) == 0 { + // "Reject all" — one instruction, ZERO == 0 is always true, but we + // invert by setting both success and failure to REJECT. + return encodeInst(filterInst{ + field: fieldZero, + test: testEQ, + success: resultReject, + failure: resultReject, + }), 0, nil + } + if len(f.insts) > filterMaxInsts-1 { + return nil, 0, errors.New("windivert: filter too long") + } + buf := make([]byte, 0, filterInstBytes*len(f.insts)) + for i, inst := range f.insts { + if i == len(f.insts)-1 { + inst.success = resultAccept + } else { + inst.success = uint16(i + 1) + } + inst.failure = resultReject + buf = append(buf, encodeInst(inst)...) + } + return buf, f.flags, nil +} + +func encodeInst(inst filterInst) []byte { + out := make([]byte, filterInstBytes) + word0 := uint32(inst.field&0x7FF) | uint32(inst.test&0x1F)<<11 | + uint32(inst.success)<<16 + word1 := uint32(inst.failure) + if inst.neg { + word1 |= 1 << 16 + } + binary.LittleEndian.PutUint32(out[0:4], word0) + binary.LittleEndian.PutUint32(out[4:8], word1) + binary.LittleEndian.PutUint32(out[8:12], inst.arg[0]) + binary.LittleEndian.PutUint32(out[12:16], inst.arg[1]) + binary.LittleEndian.PutUint32(out[16:20], inst.arg[2]) + binary.LittleEndian.PutUint32(out[20:24], inst.arg[3]) + return out +} diff --git a/transport/internet/tls/tlsspoof/windivert/handle_windows.go b/transport/internet/tls/tlsspoof/windivert/handle_windows.go new file mode 100644 index 000000000000..c48e6214c11b --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/handle_windows.go @@ -0,0 +1,323 @@ +//go:build windows + +package windivert + +import ( + "encoding/binary" + "errors" + "fmt" + "runtime" + "sync" + "unsafe" + + "golang.org/x/sys/windows" +) + +// Handle owns a WinDivert kernel device handle plus a private event for +// overlapped I/O. Methods on *Handle are not safe for concurrent use +// across goroutines (there is a single shared event per Handle). +// +// addr is a per-Handle Address buffer the IOCTL struct embeds a pointer +// to. It lives on the heap (as a field of a heap-allocated Handle) so +// the pointer value stored as bytes in the ioctl buffer remains valid +// across stack growth between buildIoctl* and the DeviceIoControl +// syscall — stack-local Address values are not safe for this pattern +// because Go's escape analysis does not see the pointer through the +// unsafe.Pointer → uintptr → bytes conversion. +type Handle struct { + device windows.Handle + event windows.Handle + closing sync.Once + closeErr error + addr Address +} + +// Filter may be nil for "reject all", suitable for send-only handles. +// Requires Administrator on first call per process (installs the kernel +// driver via SCM); subsequent calls reuse the running driver. +func Open(filter *Filter, layer Layer, priority int16, flags Flag) (*Handle, error) { + err := validateOpenArgs(layer, priority, flags) + if err != nil { + return nil, err + } + if filter == nil { + filter = reject() + } + filterBin, filterFlags, err := filter.encode() + if err != nil { + return nil, err + } + device, err := openDevice() + if err != nil { + if !errors.Is(err, windows.ERROR_FILE_NOT_FOUND) && + !errors.Is(err, windows.ERROR_PATH_NOT_FOUND) { + if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + return nil, fmt.Errorf("windivert: open device (administrator required): %w", err) + } + return nil, fmt.Errorf("windivert: open device: %w", err) + } + // Device node missing: kernel driver not loaded. Install + retry. + // Matches WinDivertOpen's lazy-install path; avoids racing StartService + // against a still-loaded driver whose SCM record is marked for deletion. + err = ensureDriver() + if err != nil { + return nil, err + } + device, err = openDevice() + if err != nil { + if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + return nil, fmt.Errorf("windivert: open device (administrator required): %w", err) + } + return nil, fmt.Errorf("windivert: open device: %w", err) + } + } + event, err := windows.CreateEvent(nil, 1, 0, nil) // manual reset, unsignaled + if err != nil { + windows.CloseHandle(device) + return nil, fmt.Errorf("windivert: create event: %w", err) + } + h := &Handle{device: device, event: event} + + err = h.initialize(layer, priority, flags) + if err != nil { + h.Close() + return nil, err + } + err = h.startup(filterBin, filterFlags) + if err != nil { + h.Close() + return nil, err + } + return h, nil +} + +func openDevice() (windows.Handle, error) { + return windows.CreateFile( + driverDevName, + windows.GENERIC_READ|windows.GENERIC_WRITE, + 0, nil, + windows.OPEN_EXISTING, + windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_OVERLAPPED, + 0, + ) +} + +func validateOpenArgs(layer Layer, priority int16, flags Flag) error { + if layer != LayerNetwork { + return fmt.Errorf("windivert: invalid layer %d", uint32(layer)) + } + if priority < PriorityLowest || priority > PriorityHighest { + return errors.New("windivert: priority out of range") + } + const supportedFlags = FlagSniff | FlagSendOnly + if flags&^supportedFlags != 0 { + return errors.New("windivert: unknown flag bits") + } + if flags&FlagSniff != 0 && flags&FlagSendOnly != 0 { + return errors.New("windivert: FlagSniff and FlagSendOnly are mutually exclusive") + } + return nil +} + +func (h *Handle) initialize(layer Layer, priority int16, flags Flag) error { + in := buildIoctlInitialize(layer, priority, flags) + // WINDIVERT_VERSION is a 64-byte packed struct; only the first 20 + // bytes (magic, major, minor, bits) carry data, the rest is reserved. + var outBuf [versionStructSize]byte + binary.LittleEndian.PutUint64(outBuf[0:8], magicDLL) + binary.LittleEndian.PutUint32(outBuf[8:12], versionMajor) + binary.LittleEndian.PutUint32(outBuf[12:16], versionMinor) + binary.LittleEndian.PutUint32(outBuf[16:20], uint32(unsafe.Sizeof(uintptr(0))*8)) + _, err := doIoctl(h.device, ioctlInitialize, in[:], outBuf[:], h.event) + if err != nil { + return fmt.Errorf("windivert: initialize ioctl: %w", err) + } + gotMagic := binary.LittleEndian.Uint64(outBuf[0:8]) + if gotMagic != magicSYS { + return fmt.Errorf("windivert: driver magic mismatch (got %d)", gotMagic) + } + gotMajor := binary.LittleEndian.Uint32(outBuf[8:12]) + if gotMajor < versionMajor { + gotMinor := binary.LittleEndian.Uint32(outBuf[12:16]) + return fmt.Errorf("windivert: driver version too old: %d.%d", gotMajor, gotMinor) + } + return nil +} + +func (h *Handle) startup(filterBin []byte, filterFlags uint64) error { + in := buildIoctlStartup(filterFlags) + _, err := doIoctl(h.device, ioctlStartup, in[:], filterBin, h.event) + if err != nil { + return fmt.Errorf("windivert: startup ioctl: %w", err) + } + return nil +} + +// If the handle is closed mid-Recv the error wraps ERROR_OPERATION_ABORTED. +func (h *Handle) Recv(buf []byte) (int, Address, error) { + if len(buf) == 0 { + return 0, Address{}, errors.New("windivert: recv: zero-length buffer") + } + h.addr = Address{} + in := buildIoctlRecv(&h.addr) + n, err := doIoctl(h.device, ioctlRecv, in[:], buf, h.event) + runtime.KeepAlive(h) + if err != nil { + return 0, Address{}, err + } + return int(n), h.addr, nil +} + +// The address's Outbound flag controls whether the packet is sent toward +// the wire (outbound=true) or delivered up the stack (outbound=false). +// IfIdx and SubIfIdx can stay zero — the driver uses the routing table +// when IfIdx=0. +func (h *Handle) Send(packet []byte, addr *Address) (int, error) { + if len(packet) == 0 { + return 0, errors.New("windivert: send: empty packet") + } + if addr == nil { + return 0, errors.New("windivert: send: nil address") + } + h.addr = *addr + in := buildIoctlSend(&h.addr) + n, err := doIoctl(h.device, ioctlSend, in[:], packet, h.event) + runtime.KeepAlive(h) + if err != nil { + return 0, err + } + return int(n), nil +} + +// Idempotent. Aborts any in-flight I/O on the handle. +func (h *Handle) Close() error { + h.closing.Do(func() { + var errs []error + if h.device != 0 { + err := windows.CloseHandle(h.device) + if err != nil { + errs = append(errs, err) + } + h.device = 0 + } + if h.event != 0 { + err := windows.CloseHandle(h.event) + if err != nil { + errs = append(errs, err) + } + h.event = 0 + } + h.closeErr = errors.Join(errs...) + }) + return h.closeErr +} + +// IOCTL codes from windivert_device.h. CTL_CODE macro layout: +// +// (DeviceType << 16) | (Access << 14) | (Function << 2) | Method +const ( + fileDeviceNetwork uint32 = 0x12 + accessReadWrite uint32 = 3 // FILE_READ_DATA | FILE_WRITE_DATA + accessRead uint32 = 1 + + methodInDirect uint32 = 1 + methodOutDirect uint32 = 2 +) + +func ctlCode(deviceType, access, function, method uint32) uint32 { + return (deviceType << 16) | (access << 14) | (function << 2) | method +} + +var ( + ioctlInitialize = ctlCode(fileDeviceNetwork, accessReadWrite, 0x921, methodOutDirect) + ioctlStartup = ctlCode(fileDeviceNetwork, accessReadWrite, 0x922, methodInDirect) + ioctlRecv = ctlCode(fileDeviceNetwork, accessRead, 0x923, methodOutDirect) + ioctlSend = ctlCode(fileDeviceNetwork, accessReadWrite, 0x924, methodInDirect) +) + +// Magic numbers exchanged during INITIALIZE. DLL sends magicDLL in the +// version struct; driver returns magicSYS on success. +const ( + magicDLL uint64 = 0x4C4C447669645724 // "$WdivDLL" in LE bytes + magicSYS uint64 = 0x5359537669645723 // "#WdivSYS" in LE bytes +) + +const ( + versionMajor uint32 = 2 + versionMinor uint32 = 2 +) + +// Size of the WINDIVERT_IOCTL union on wire (packed). +const ioctlSize = 16 + +// Size of WINDIVERT_VERSION on wire (packed). Only the first 20 bytes +// carry data; the rest is reserved zero padding. +const versionStructSize = 64 + +// doIoctl performs a single synchronous (blocking) overlapped +// DeviceIoControl. The handle is opened with FILE_FLAG_OVERLAPPED so +// DeviceIoControl returns ERROR_IO_PENDING; we then wait for completion +// via GetOverlappedResult. Event is passed in so callers can reuse it +// across calls on the same handle (avoids per-call CreateEvent). +func doIoctl(handle windows.Handle, code uint32, in []byte, out []byte, event windows.Handle) (uint32, error) { + var overlapped windows.Overlapped + overlapped.HEvent = event + _ = windows.ResetEvent(event) + + var inPtr *byte + var inLen uint32 + if len(in) > 0 { + inPtr = &in[0] + inLen = uint32(len(in)) + } + var outPtr *byte + var outLen uint32 + if len(out) > 0 { + outPtr = &out[0] + outLen = uint32(len(out)) + } + var returned uint32 + err := windows.DeviceIoControl(handle, code, inPtr, inLen, outPtr, outLen, &returned, &overlapped) + if err != nil && !errors.Is(err, windows.ERROR_IO_PENDING) { + return 0, err + } + err = windows.GetOverlappedResult(handle, &overlapped, &returned, true) + if err != nil { + return 0, err + } + return returned, nil +} + +func buildIoctlInitialize(layer Layer, priority int16, flags Flag) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint32(buf[0:4], uint32(layer)) + // The driver expects priority + WINDIVERT_PRIORITY_HIGHEST (30000) so + // the low range maps to non-negative integers. + binary.LittleEndian.PutUint32(buf[4:8], uint32(int32(priority)+int32(PriorityHighest))) + binary.LittleEndian.PutUint64(buf[8:16], uint64(flags)) + return buf +} + +func buildIoctlStartup(filterFlags uint64) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint64(buf[0:8], filterFlags) + return buf +} + +// buildIoctlRecv packs a user-space pointer to a WINDIVERT_ADDRESS into +// the ioctl struct. The driver dereferences it to write the address for +// the received packet. Caller must keep the Address alive via +// runtime.KeepAlive. +func buildIoctlRecv(addr *Address) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint64(buf[0:8], uint64(uintptr(unsafe.Pointer(addr)))) + binary.LittleEndian.PutUint64(buf[8:16], 0) + return buf +} + +func buildIoctlSend(addr *Address) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint64(buf[0:8], uint64(uintptr(unsafe.Pointer(addr)))) + binary.LittleEndian.PutUint64(buf[8:16], uint64(unsafe.Sizeof(Address{}))) + return buf +} diff --git a/transport/internet/tls/tlsspoof/windivert/windivert.go b/transport/internet/tls/tlsspoof/windivert/windivert.go new file mode 100644 index 000000000000..9d309886cbe3 --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/windivert.go @@ -0,0 +1,78 @@ +// Package windivert provides a pure-Go binding to the WinDivert kernel +// driver on Windows (amd64 and 386). User-mode WinDivert calls are +// reimplemented in Go; only the signed kernel driver is embedded as an +// asset, since SCM-installed drivers must live on disk and their +// Authenticode signature forbids modification. +// +// Administrator is required for the first Open in a process so SCM can +// load the driver. Upstream: https://github.com/basil00/WinDivert v2.2.2, +// redistributed under its LGPL v3 option; see assets/LICENSE.txt. +package windivert + +import "unsafe" + +const AssetVersion = "2.2.2" + +// MTUMax is WINDIVERT_MTU_MAX from windivert.h (40 + 0xFFFF). Suitable as +// a single-packet receive buffer size. +const MTUMax = 40 + 0xFFFF + +type Layer uint32 + +const LayerNetwork Layer = 0 + +type Flag uint64 + +const ( + // FlagSniff opens a passive observer: the driver copies matching packets + // to userspace without removing them from the network stack. Send is not + // required (and not allowed) on a sniffing handle. + FlagSniff Flag = 0x0001 + // FlagSendOnly opens a write-only injection handle; Recv is not allowed. + FlagSendOnly Flag = 0x0008 +) + +const ( + PriorityHighest int16 = 30000 + PriorityLowest int16 = -30000 +) + +// Address mirrors WINDIVERT_ADDRESS from windivert.h (80 bytes, +// little-endian on both amd64 and 386): +// +// 0: INT64 Timestamp +// 8: UINT32 bitfield: Layer:8 | Event:8 | flags | Reserved1:8 +// 12: UINT32 Reserved2 +// 16: 64 bytes union (WINDIVERT_DATA_NETWORK / FLOW / SOCKET / REFLECT) +type Address struct { + Timestamp int64 + bits uint32 + Reserved2 uint32 + union [64]byte +} + +var _ [80]byte = [unsafe.Sizeof(Address{})]byte{} + +// Bit positions inside the Address's packed flags word. +const ( + addrBitIPv6 = 20 + addrBitIPChecksum = 21 + addrBitTCPChecksum = 22 +) + +func getFlagBit(bits uint32, pos uint) bool { return bits&(1< Date: Wed, 19 Aug 2026 10:37:34 +0600 Subject: [PATCH 40/42] rawpacket: fix inverted BPF filter jumps --- transport/internet/finalmask/rawpacket/spoof_bpf_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transport/internet/finalmask/rawpacket/spoof_bpf_linux.go b/transport/internet/finalmask/rawpacket/spoof_bpf_linux.go index 00cfa484ac43..4669618468ed 100644 --- a/transport/internet/finalmask/rawpacket/spoof_bpf_linux.go +++ b/transport/internet/finalmask/rawpacket/spoof_bpf_linux.go @@ -40,7 +40,7 @@ const ( func attachBPFFilter(fd int, proto uint8) { filter := []sockFilter{ {Code: bpfLdAbs, K: 9}, // A = ip.proto - {Code: bpfJeq, Jt: 0, Jf: 1, K: uint32(proto)}, // if A == proto keep + {Code: bpfJeq, Jt: 1, Jf: 0, K: uint32(proto)}, // if A == proto skip the drop {Code: bpfRet, K: bpfDrop}, // else drop {Code: bpfRet, K: bpfKeepAll}, // keep } From 40a2f3d7c6c2ab5f9fbd50f711633d8ab93bcf49 Mon Sep 17 00:00:00 2001 From: Tamim Hossain Date: Wed, 19 Aug 2026 10:45:53 +0600 Subject: [PATCH 41/42] rawpacket: fix server-role ACK for data segments --- .../internet/finalmask/rawpacket/masquerade.go | 4 ++-- .../internet/finalmask/rawpacket/spoof_relay.go | 2 +- .../internet/finalmask/rawpacket/tcp_state.go | 15 +++++++++++---- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/transport/internet/finalmask/rawpacket/masquerade.go b/transport/internet/finalmask/rawpacket/masquerade.go index d0cf539eab05..4d3c497787d5 100644 --- a/transport/internet/finalmask/rawpacket/masquerade.go +++ b/transport/internet/finalmask/rawpacket/masquerade.go @@ -169,7 +169,7 @@ func (m *masquerade) onTCPSYN(relayIP netip.Addr, relayPort uint16, probeIP neti } } f.lastSeen = time.Now() - f.tcp.observeClientSeq(seq) + f.tcp.observeClientSeq(seq, 0) m.mu.Unlock() m.sendTCP(relayIP, relayPort, probeIP, probePort, f, nil, true) } @@ -189,7 +189,7 @@ func (m *masquerade) onTCPData(relayIP netip.Addr, relayPort uint16, probeIP net return } f.lastSeen = time.Now() - f.tcp.observeClientSeq(seq) + f.tcp.observeClientSeq(seq, len(payload)) chunk := f.resp[f.sent:] if len(chunk) > masqMaxChunkLen { chunk = chunk[:masqMaxChunkLen] diff --git a/transport/internet/finalmask/rawpacket/spoof_relay.go b/transport/internet/finalmask/rawpacket/spoof_relay.go index fe82da0d0767..760cfe67bd3c 100644 --- a/transport/internet/finalmask/rawpacket/spoof_relay.go +++ b/transport/internet/finalmask/rawpacket/spoof_relay.go @@ -218,7 +218,7 @@ func (r *Relay) deliverToSession(s *RelaySession, pkt []byte, tcp *TCPMeta) bool return false } if tcp != nil && tcp.Flags != 0 { - s.tcp.observeClientSeq(tcp.Seq) + s.tcp.observeClientSeq(tcp.Seq, len(pkt)) } s.mu.Lock() s.LastSeen = time.Now() diff --git a/transport/internet/finalmask/rawpacket/tcp_state.go b/transport/internet/finalmask/rawpacket/tcp_state.go index e18b59cae942..a8ad3014363f 100644 --- a/transport/internet/finalmask/rawpacket/tcp_state.go +++ b/transport/internet/finalmask/rawpacket/tcp_state.go @@ -96,12 +96,19 @@ func (s *TCPSimState) observePeer(seq uint32, payloadLen int) { // observeClientSeq records the client's latest wire sequence number so // server-role acknowledgements are correct. Used on the relay, where the -// client's cumulative sequence is already encoded in the wire seq. -func (s *TCPSimState) observeClientSeq(seq uint32) { +// client's cumulative sequence is already encoded in the wire seq. The +// first segment (the client's SYN) consumes one byte; later segments +// advance by their payload length only. +func (s *TCPSimState) observeClientSeq(seq uint32, payloadLen int) { s.mu.Lock() defer s.mu.Unlock() - s.peerSeen = true - s.peerSeq = seq + 1 + if !s.peerSeen { + s.peerSeen = true + s.peerISN = seq + s.peerSeq = seq + 1 + uint32(payloadLen) + return + } + s.peerSeq = seq + uint32(payloadLen) } // tsVal returns a plausible TCP timestamp value: milliseconds since a From b7dfde3769950d299029aa9d1fb8b9f472ac4a26 Mon Sep 17 00:00:00 2001 From: Tamim Hossain Date: Wed, 19 Aug 2026 10:46:22 +0600 Subject: [PATCH 42/42] rawpacket: cover server ACK advance in tests --- transport/internet/finalmask/rawpacket/spoof_tcp_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/transport/internet/finalmask/rawpacket/spoof_tcp_test.go b/transport/internet/finalmask/rawpacket/spoof_tcp_test.go index 6a592ea12b29..613426462b93 100644 --- a/transport/internet/finalmask/rawpacket/spoof_tcp_test.go +++ b/transport/internet/finalmask/rawpacket/spoof_tcp_test.go @@ -50,10 +50,14 @@ func TestTCPSimStateServer(t *testing.T) { if st.serverFlags() != TCPFlagAck { t.Fatalf("second server segment should be ACK, got %#x", st.serverFlags()) } - st.observeClientSeq(777) + st.observeClientSeq(777, 0) if st.ack() != 778 { t.Fatalf("server ack should be observed client seq+1, got %d want 778", st.ack()) } + st.observeClientSeq(778, 20) + if st.ack() != 798 { + t.Fatalf("server ack should advance by payload length, got %d want 798", st.ack()) + } seq := st.nextSeq(50) if seq != st.isn { t.Fatalf("server data should start at its ISN, got %d", seq)

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 Mok_m={2+h)7u^}>0RR91 literal 0 HcmV?d00001 diff --git a/transport/internet/tls/tlsspoof/windivert/assets_386.go b/transport/internet/tls/tlsspoof/windivert/assets_386.go new file mode 100644 index 000000000000..0cbf35ed5cbf --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/assets_386.go @@ -0,0 +1,14 @@ +//go:build windows && 386 + +package windivert + +import _ "embed" + +//go:embed assets/WinDivert32.sys +var sysBytes []byte + +func assetFiles() []assetFile { + return []assetFile{{"WinDivert32.sys", sysBytes}} +} + +func driverSysName() string { return "WinDivert32.sys" } diff --git a/transport/internet/tls/tlsspoof/windivert/assets_amd64.go b/transport/internet/tls/tlsspoof/windivert/assets_amd64.go new file mode 100644 index 000000000000..2c9fb6c6ad19 --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/assets_amd64.go @@ -0,0 +1,14 @@ +//go:build windows && amd64 + +package windivert + +import _ "embed" + +//go:embed assets/WinDivert64.sys +var sysBytes []byte + +func assetFiles() []assetFile { + return []assetFile{{"WinDivert64.sys", sysBytes}} +} + +func driverSysName() string { return "WinDivert64.sys" } diff --git a/transport/internet/tls/tlsspoof/windivert/assets_unsupported.go b/transport/internet/tls/tlsspoof/windivert/assets_unsupported.go new file mode 100644 index 000000000000..04698953fa6b --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/assets_unsupported.go @@ -0,0 +1,7 @@ +//go:build windows && !amd64 && !386 + +package windivert + +func assetFiles() []assetFile { return nil } + +func driverSysName() string { return "" } diff --git a/transport/internet/tls/tlsspoof/windivert/driver_windows.go b/transport/internet/tls/tlsspoof/windivert/driver_windows.go new file mode 100644 index 000000000000..50e94c578422 --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/driver_windows.go @@ -0,0 +1,211 @@ +//go:build windows + +package windivert + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "strconv" + "sync" + + "golang.org/x/sys/windows" +) + +const ( + driverServiceName = "WinDivert" + driverDeviceName = `\\.\WinDivert` +) + +var ( + driverOnce sync.Once + driverErr error + // driverDevName is ASCII-safe and must be available before ensureDriver + // so Open can try CreateFile first and only install on FILE_NOT_FOUND. + driverDevName, _ = windows.UTF16PtrFromString(driverDeviceName) +) + +// Requires SeLoadDriverPrivilege (Administrator). Running the 386 build +// under WOW64 on a 64-bit kernel is rejected — use the amd64 build. +func ensureDriver() error { + driverOnce.Do(func() { + driverErr = installDriver() + }) + return driverErr +} + +func installDriver() error { + if runtime.GOARCH == "386" { + var isWow64 bool + err := windows.IsWow64Process(windows.CurrentProcess(), &isWow64) + if err == nil && isWow64 { + return errors.New("windivert: 386 build detected running under WOW64 on a 64-bit kernel; use the amd64 build") + } + } + + dir, err := ensureExtracted() + if err != nil { + return err + } + sysPath := filepath.Join(dir, driverSysName()) + sysPathW, err := windows.UTF16PtrFromString(sysPath) + if err != nil { + return fmt.Errorf("windivert: utf16 driver path: %w", err) + } + + // Serialize driver install across concurrent processes. + mutexName, _ := windows.UTF16PtrFromString("WinDivertDriverInstallMutex") + mutex, err := windows.CreateMutex(nil, false, mutexName) + if err != nil { + return fmt.Errorf("windivert: create install mutex: %w", err) + } + defer windows.CloseHandle(mutex) + _, err = windows.WaitForSingleObject(mutex, windows.INFINITE) + if err != nil { + return fmt.Errorf("windivert: wait install mutex: %w", err) + } + defer windows.ReleaseMutex(mutex) + + manager, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_ALL_ACCESS) + if err != nil { + return fmt.Errorf("windivert: open SCM: %w", err) + } + defer windows.CloseServiceHandle(manager) + + serviceNameW, _ := windows.UTF16PtrFromString(driverServiceName) + service, err := windows.OpenService(manager, serviceNameW, windows.SERVICE_ALL_ACCESS) + if err != nil { + service, err = windows.CreateService( + manager, + serviceNameW, + serviceNameW, + windows.SERVICE_ALL_ACCESS, + windows.SERVICE_KERNEL_DRIVER, + windows.SERVICE_DEMAND_START, + windows.SERVICE_ERROR_NORMAL, + sysPathW, + nil, nil, nil, nil, nil, + ) + if err != nil { + if errors.Is(err, windows.ERROR_SERVICE_EXISTS) { + service, err = windows.OpenService(manager, serviceNameW, windows.SERVICE_ALL_ACCESS) + } + if err != nil { + return wrapDriverInstallError(err) + } + } + } + defer windows.CloseServiceHandle(service) + + err = windows.StartService(service, 0, nil) + if err != nil && errors.Is(err, windows.ERROR_SERVICE_DISABLED) { + // A prior process called DeleteService on a still-running kernel + // driver: SCM marks the record for deletion and flips START_TYPE + // to DISABLED until the last handle closes. Re-enable so we can + // start it instead of waiting for a reboot. + err = windows.ChangeServiceConfig( + service, + windows.SERVICE_NO_CHANGE, + windows.SERVICE_DEMAND_START, + windows.SERVICE_NO_CHANGE, + nil, nil, nil, nil, nil, nil, nil, + ) + if err != nil { + return fmt.Errorf("windivert: re-enable disabled service: %w", err) + } + err = windows.StartService(service, 0, nil) + } + if err == nil { + // Mark for deletion so the driver unregisters when the last handle + // closes or on next reboot. Matches the upstream DLL's behavior: + // only the process that actually started the service takes on the + // cleanup responsibility. If another process already started it, + // we leave DeleteService to them. + _ = windows.DeleteService(service) + } else if !errors.Is(err, windows.ERROR_SERVICE_ALREADY_RUNNING) { + return fmt.Errorf("windivert: start service: %w", err) + } + return nil +} + +func wrapDriverInstallError(err error) error { + if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + return fmt.Errorf("windivert: installing the kernel driver requires Administrator privileges: %w", err) + } + return fmt.Errorf("windivert: create service: %w", err) +} + +type assetFile struct { + name string + data []byte +} + +var ( + extractOnce sync.Once + extractErr error + extractDir string +) + +// The on-disk copy is protected by Windows Authenticode signature +// enforcement, which rejects any tampered .sys at StartService time. +func ensureExtracted() (string, error) { + extractOnce.Do(func() { + extractDir, extractErr = extractImpl() + }) + return extractDir, extractErr +} + +func extractImpl() (string, error) { + files := assetFiles() + if len(files) == 0 { + return "", fmt.Errorf("windivert: unsupported architecture %s", runtime.GOARCH) + } + + base, err := os.UserCacheDir() + if err != nil { + return "", fmt.Errorf("windivert: locate user cache dir: %w", err) + } + dir := filepath.Join(base, "xray-core", "windivert", "v"+AssetVersion) + err = os.MkdirAll(dir, 0o755) + if err != nil { + return "", fmt.Errorf("windivert: mkdir %s: %w", dir, err) + } + + for _, asset := range files { + err = ensureAsset(dir, asset) + if err != nil { + return "", err + } + } + return dir, nil +} + +// Concurrent sing-box processes race on os.Rename (atomic on NTFS); +// whichever wins creates the final file. Writers that lose the race +// silently discard their temp copy. +func ensureAsset(dir string, asset assetFile) error { + target := filepath.Join(dir, asset.name) + _, err := os.Stat(target) + if err == nil { + return nil + } + if !os.IsNotExist(err) { + return fmt.Errorf("windivert: stat %s: %w", asset.name, err) + } + tmp := target + ".tmp-" + strconv.Itoa(os.Getpid()) + err = os.WriteFile(tmp, asset.data, 0o644) + if err != nil { + return fmt.Errorf("windivert: write %s: %w", asset.name, err) + } + err = os.Rename(tmp, target) + if err != nil { + os.Remove(tmp) + if _, statErr := os.Stat(target); statErr == nil { + return nil + } + return fmt.Errorf("windivert: rename %s: %w", asset.name, err) + } + return nil +} diff --git a/transport/internet/tls/tlsspoof/windivert/filter.go b/transport/internet/tls/tlsspoof/windivert/filter.go new file mode 100644 index 000000000000..d63adae2b630 --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/filter.go @@ -0,0 +1,181 @@ +package windivert + +import ( + "encoding/binary" + "errors" + "net/netip" +) + +// WINDIVERT_FILTER VM instruction layout (24 bytes, #pragma pack(1)): +// +// word 0 (LE): field:11 | test:5 | success:16 +// word 1 (LE): failure:16 | neg:1 | reserved:15 +// words 2..5: arg[4] (native-endian uint32 each) +// +// The driver walks this as a decision tree: evaluate the test at inst i; +// on success jump to success; on failure jump to failure. Continuations +// 0x7FFE and 0x7FFF are ACCEPT and REJECT terminals. +const ( + filterInstBytes = 24 + filterMaxInsts = 256 + + fieldZero = 0 + fieldOutbound = 2 + fieldIP = 5 + fieldIPv6 = 6 + fieldTCP = 8 + fieldIPSrcAddr = 21 + fieldIPDstAddr = 22 + fieldIPv6SrcAddr = 28 + fieldIPv6DstAddr = 29 + fieldTCPSrcPort = 38 + fieldTCPDstPort = 39 + + testEQ = 0 + + resultAccept uint16 = 0x7FFE + resultReject uint16 = 0x7FFF +) + +// Filter flags passed to IOCTL_WINDIVERT_STARTUP alongside the compiled +// filter. These tell the driver what *kinds* of packets the filter might +// match, used as a kernel-side fast-reject. +const ( + filterFlagOutbound uint64 = 0x0020 + filterFlagIP uint64 = 0x0040 + filterFlagIPv6 uint64 = 0x0080 +) + +type filterInst struct { + field uint16 // 11 bits used + test uint8 // 5 bits used + success uint16 + failure uint16 + neg bool + arg [4]uint32 +} + +// Filter is a typed specification of packets to capture. It replaces +// WinDivert's filter string language. +// +// Zero value = "reject all" (match nothing), suitable for send-only handles. +type Filter struct { + insts []filterInst + flags uint64 // filter flags for STARTUP ioctl +} + +// reject returns a filter that matches no packet. The empty insts slice +// is encoded as a single rejecting instruction by encode(). +func reject() *Filter { + return &Filter{} +} + +// OutboundTCP returns a filter matching outbound TCP packets on the given +// 5-tuple. Both addresses must share an address family (IPv4 or IPv6). +func OutboundTCP(src, dst netip.AddrPort) (*Filter, error) { + if !src.IsValid() || !dst.IsValid() { + return nil, errors.New("windivert: filter: invalid address port") + } + if src.Addr().Is4() != dst.Addr().Is4() { + return nil, errors.New("windivert: filter: mixed IPv4/IPv6") + } + f := &Filter{ + flags: filterFlagOutbound, + } + // Insts chain as AND: each test's failure = REJECT, success = next inst. + // The final inst's success = ACCEPT. + f.add(fieldOutbound, testEQ, argUint32(1)) + if src.Addr().Is4() { + f.flags |= filterFlagIP + f.add(fieldIP, testEQ, argUint32(1)) + f.add(fieldTCP, testEQ, argUint32(1)) + f.add(fieldIPSrcAddr, testEQ, argIPv4(src.Addr())) + f.add(fieldIPDstAddr, testEQ, argIPv4(dst.Addr())) + } else { + f.flags |= filterFlagIPv6 + f.add(fieldIPv6, testEQ, argUint32(1)) + f.add(fieldTCP, testEQ, argUint32(1)) + f.add(fieldIPv6SrcAddr, testEQ, argIPv6(src.Addr())) + f.add(fieldIPv6DstAddr, testEQ, argIPv6(dst.Addr())) + } + f.add(fieldTCPSrcPort, testEQ, argUint32(uint32(src.Port()))) + f.add(fieldTCPDstPort, testEQ, argUint32(uint32(dst.Port()))) + return f, nil +} + +func (f *Filter) add(field uint16, test uint8, arg [4]uint32) { + f.insts = append(f.insts, filterInst{field: field, test: test, arg: arg}) +} + +func argUint32(v uint32) [4]uint32 { return [4]uint32{v, 0, 0, 0} } + +// argIPv4 encodes an IPv4 address for IP_SRCADDR/IP_DSTADDR. The driver +// compares against an IPv4-mapped-IPv6 form: {host_order_u32, 0x0000FFFF, +// 0, 0} (see sys/windivert.c windivert_get_ipv4_addr and the IPv4_SRCADDR +// val-word construction). Omitting the 0x0000FFFF marker causes the EQ +// test to fail for every packet. +func argIPv4(addr netip.Addr) [4]uint32 { + b := addr.As4() + return [4]uint32{binary.BigEndian.Uint32(b[:]), 0x0000FFFF, 0, 0} +} + +// argIPv6 encodes an IPv6 address for IPV6_SRCADDR/IPV6_DSTADDR. The +// driver stores the address as four host-order uint32s in REVERSED word +// order: val[0]=low (bytes 12..15), val[3]=high (bytes 0..3). See +// sys/windivert.c windivert_outbound_network_v6_classify val-word +// construction. +func argIPv6(addr netip.Addr) [4]uint32 { + b := addr.As16() + return [4]uint32{ + binary.BigEndian.Uint32(b[12:16]), + binary.BigEndian.Uint32(b[8:12]), + binary.BigEndian.Uint32(b[4:8]), + binary.BigEndian.Uint32(b[0:4]), + } +} + +// encode serializes the Filter to the on-wire WINDIVERT_FILTER[] format +// plus the filter_flags for STARTUP ioctl. +func (f *Filter) encode() ([]byte, uint64, error) { + if len(f.insts) == 0 { + // "Reject all" — one instruction, ZERO == 0 is always true, but we + // invert by setting both success and failure to REJECT. + return encodeInst(filterInst{ + field: fieldZero, + test: testEQ, + success: resultReject, + failure: resultReject, + }), 0, nil + } + if len(f.insts) > filterMaxInsts-1 { + return nil, 0, errors.New("windivert: filter too long") + } + buf := make([]byte, 0, filterInstBytes*len(f.insts)) + for i, inst := range f.insts { + if i == len(f.insts)-1 { + inst.success = resultAccept + } else { + inst.success = uint16(i + 1) + } + inst.failure = resultReject + buf = append(buf, encodeInst(inst)...) + } + return buf, f.flags, nil +} + +func encodeInst(inst filterInst) []byte { + out := make([]byte, filterInstBytes) + word0 := uint32(inst.field&0x7FF) | uint32(inst.test&0x1F)<<11 | + uint32(inst.success)<<16 + word1 := uint32(inst.failure) + if inst.neg { + word1 |= 1 << 16 + } + binary.LittleEndian.PutUint32(out[0:4], word0) + binary.LittleEndian.PutUint32(out[4:8], word1) + binary.LittleEndian.PutUint32(out[8:12], inst.arg[0]) + binary.LittleEndian.PutUint32(out[12:16], inst.arg[1]) + binary.LittleEndian.PutUint32(out[16:20], inst.arg[2]) + binary.LittleEndian.PutUint32(out[20:24], inst.arg[3]) + return out +} diff --git a/transport/internet/tls/tlsspoof/windivert/handle_windows.go b/transport/internet/tls/tlsspoof/windivert/handle_windows.go new file mode 100644 index 000000000000..c48e6214c11b --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/handle_windows.go @@ -0,0 +1,323 @@ +//go:build windows + +package windivert + +import ( + "encoding/binary" + "errors" + "fmt" + "runtime" + "sync" + "unsafe" + + "golang.org/x/sys/windows" +) + +// Handle owns a WinDivert kernel device handle plus a private event for +// overlapped I/O. Methods on *Handle are not safe for concurrent use +// across goroutines (there is a single shared event per Handle). +// +// addr is a per-Handle Address buffer the IOCTL struct embeds a pointer +// to. It lives on the heap (as a field of a heap-allocated Handle) so +// the pointer value stored as bytes in the ioctl buffer remains valid +// across stack growth between buildIoctl* and the DeviceIoControl +// syscall — stack-local Address values are not safe for this pattern +// because Go's escape analysis does not see the pointer through the +// unsafe.Pointer → uintptr → bytes conversion. +type Handle struct { + device windows.Handle + event windows.Handle + closing sync.Once + closeErr error + addr Address +} + +// Filter may be nil for "reject all", suitable for send-only handles. +// Requires Administrator on first call per process (installs the kernel +// driver via SCM); subsequent calls reuse the running driver. +func Open(filter *Filter, layer Layer, priority int16, flags Flag) (*Handle, error) { + err := validateOpenArgs(layer, priority, flags) + if err != nil { + return nil, err + } + if filter == nil { + filter = reject() + } + filterBin, filterFlags, err := filter.encode() + if err != nil { + return nil, err + } + device, err := openDevice() + if err != nil { + if !errors.Is(err, windows.ERROR_FILE_NOT_FOUND) && + !errors.Is(err, windows.ERROR_PATH_NOT_FOUND) { + if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + return nil, fmt.Errorf("windivert: open device (administrator required): %w", err) + } + return nil, fmt.Errorf("windivert: open device: %w", err) + } + // Device node missing: kernel driver not loaded. Install + retry. + // Matches WinDivertOpen's lazy-install path; avoids racing StartService + // against a still-loaded driver whose SCM record is marked for deletion. + err = ensureDriver() + if err != nil { + return nil, err + } + device, err = openDevice() + if err != nil { + if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + return nil, fmt.Errorf("windivert: open device (administrator required): %w", err) + } + return nil, fmt.Errorf("windivert: open device: %w", err) + } + } + event, err := windows.CreateEvent(nil, 1, 0, nil) // manual reset, unsignaled + if err != nil { + windows.CloseHandle(device) + return nil, fmt.Errorf("windivert: create event: %w", err) + } + h := &Handle{device: device, event: event} + + err = h.initialize(layer, priority, flags) + if err != nil { + h.Close() + return nil, err + } + err = h.startup(filterBin, filterFlags) + if err != nil { + h.Close() + return nil, err + } + return h, nil +} + +func openDevice() (windows.Handle, error) { + return windows.CreateFile( + driverDevName, + windows.GENERIC_READ|windows.GENERIC_WRITE, + 0, nil, + windows.OPEN_EXISTING, + windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_OVERLAPPED, + 0, + ) +} + +func validateOpenArgs(layer Layer, priority int16, flags Flag) error { + if layer != LayerNetwork { + return fmt.Errorf("windivert: invalid layer %d", uint32(layer)) + } + if priority < PriorityLowest || priority > PriorityHighest { + return errors.New("windivert: priority out of range") + } + const supportedFlags = FlagSniff | FlagSendOnly + if flags&^supportedFlags != 0 { + return errors.New("windivert: unknown flag bits") + } + if flags&FlagSniff != 0 && flags&FlagSendOnly != 0 { + return errors.New("windivert: FlagSniff and FlagSendOnly are mutually exclusive") + } + return nil +} + +func (h *Handle) initialize(layer Layer, priority int16, flags Flag) error { + in := buildIoctlInitialize(layer, priority, flags) + // WINDIVERT_VERSION is a 64-byte packed struct; only the first 20 + // bytes (magic, major, minor, bits) carry data, the rest is reserved. + var outBuf [versionStructSize]byte + binary.LittleEndian.PutUint64(outBuf[0:8], magicDLL) + binary.LittleEndian.PutUint32(outBuf[8:12], versionMajor) + binary.LittleEndian.PutUint32(outBuf[12:16], versionMinor) + binary.LittleEndian.PutUint32(outBuf[16:20], uint32(unsafe.Sizeof(uintptr(0))*8)) + _, err := doIoctl(h.device, ioctlInitialize, in[:], outBuf[:], h.event) + if err != nil { + return fmt.Errorf("windivert: initialize ioctl: %w", err) + } + gotMagic := binary.LittleEndian.Uint64(outBuf[0:8]) + if gotMagic != magicSYS { + return fmt.Errorf("windivert: driver magic mismatch (got %d)", gotMagic) + } + gotMajor := binary.LittleEndian.Uint32(outBuf[8:12]) + if gotMajor < versionMajor { + gotMinor := binary.LittleEndian.Uint32(outBuf[12:16]) + return fmt.Errorf("windivert: driver version too old: %d.%d", gotMajor, gotMinor) + } + return nil +} + +func (h *Handle) startup(filterBin []byte, filterFlags uint64) error { + in := buildIoctlStartup(filterFlags) + _, err := doIoctl(h.device, ioctlStartup, in[:], filterBin, h.event) + if err != nil { + return fmt.Errorf("windivert: startup ioctl: %w", err) + } + return nil +} + +// If the handle is closed mid-Recv the error wraps ERROR_OPERATION_ABORTED. +func (h *Handle) Recv(buf []byte) (int, Address, error) { + if len(buf) == 0 { + return 0, Address{}, errors.New("windivert: recv: zero-length buffer") + } + h.addr = Address{} + in := buildIoctlRecv(&h.addr) + n, err := doIoctl(h.device, ioctlRecv, in[:], buf, h.event) + runtime.KeepAlive(h) + if err != nil { + return 0, Address{}, err + } + return int(n), h.addr, nil +} + +// The address's Outbound flag controls whether the packet is sent toward +// the wire (outbound=true) or delivered up the stack (outbound=false). +// IfIdx and SubIfIdx can stay zero — the driver uses the routing table +// when IfIdx=0. +func (h *Handle) Send(packet []byte, addr *Address) (int, error) { + if len(packet) == 0 { + return 0, errors.New("windivert: send: empty packet") + } + if addr == nil { + return 0, errors.New("windivert: send: nil address") + } + h.addr = *addr + in := buildIoctlSend(&h.addr) + n, err := doIoctl(h.device, ioctlSend, in[:], packet, h.event) + runtime.KeepAlive(h) + if err != nil { + return 0, err + } + return int(n), nil +} + +// Idempotent. Aborts any in-flight I/O on the handle. +func (h *Handle) Close() error { + h.closing.Do(func() { + var errs []error + if h.device != 0 { + err := windows.CloseHandle(h.device) + if err != nil { + errs = append(errs, err) + } + h.device = 0 + } + if h.event != 0 { + err := windows.CloseHandle(h.event) + if err != nil { + errs = append(errs, err) + } + h.event = 0 + } + h.closeErr = errors.Join(errs...) + }) + return h.closeErr +} + +// IOCTL codes from windivert_device.h. CTL_CODE macro layout: +// +// (DeviceType << 16) | (Access << 14) | (Function << 2) | Method +const ( + fileDeviceNetwork uint32 = 0x12 + accessReadWrite uint32 = 3 // FILE_READ_DATA | FILE_WRITE_DATA + accessRead uint32 = 1 + + methodInDirect uint32 = 1 + methodOutDirect uint32 = 2 +) + +func ctlCode(deviceType, access, function, method uint32) uint32 { + return (deviceType << 16) | (access << 14) | (function << 2) | method +} + +var ( + ioctlInitialize = ctlCode(fileDeviceNetwork, accessReadWrite, 0x921, methodOutDirect) + ioctlStartup = ctlCode(fileDeviceNetwork, accessReadWrite, 0x922, methodInDirect) + ioctlRecv = ctlCode(fileDeviceNetwork, accessRead, 0x923, methodOutDirect) + ioctlSend = ctlCode(fileDeviceNetwork, accessReadWrite, 0x924, methodInDirect) +) + +// Magic numbers exchanged during INITIALIZE. DLL sends magicDLL in the +// version struct; driver returns magicSYS on success. +const ( + magicDLL uint64 = 0x4C4C447669645724 // "$WdivDLL" in LE bytes + magicSYS uint64 = 0x5359537669645723 // "#WdivSYS" in LE bytes +) + +const ( + versionMajor uint32 = 2 + versionMinor uint32 = 2 +) + +// Size of the WINDIVERT_IOCTL union on wire (packed). +const ioctlSize = 16 + +// Size of WINDIVERT_VERSION on wire (packed). Only the first 20 bytes +// carry data; the rest is reserved zero padding. +const versionStructSize = 64 + +// doIoctl performs a single synchronous (blocking) overlapped +// DeviceIoControl. The handle is opened with FILE_FLAG_OVERLAPPED so +// DeviceIoControl returns ERROR_IO_PENDING; we then wait for completion +// via GetOverlappedResult. Event is passed in so callers can reuse it +// across calls on the same handle (avoids per-call CreateEvent). +func doIoctl(handle windows.Handle, code uint32, in []byte, out []byte, event windows.Handle) (uint32, error) { + var overlapped windows.Overlapped + overlapped.HEvent = event + _ = windows.ResetEvent(event) + + var inPtr *byte + var inLen uint32 + if len(in) > 0 { + inPtr = &in[0] + inLen = uint32(len(in)) + } + var outPtr *byte + var outLen uint32 + if len(out) > 0 { + outPtr = &out[0] + outLen = uint32(len(out)) + } + var returned uint32 + err := windows.DeviceIoControl(handle, code, inPtr, inLen, outPtr, outLen, &returned, &overlapped) + if err != nil && !errors.Is(err, windows.ERROR_IO_PENDING) { + return 0, err + } + err = windows.GetOverlappedResult(handle, &overlapped, &returned, true) + if err != nil { + return 0, err + } + return returned, nil +} + +func buildIoctlInitialize(layer Layer, priority int16, flags Flag) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint32(buf[0:4], uint32(layer)) + // The driver expects priority + WINDIVERT_PRIORITY_HIGHEST (30000) so + // the low range maps to non-negative integers. + binary.LittleEndian.PutUint32(buf[4:8], uint32(int32(priority)+int32(PriorityHighest))) + binary.LittleEndian.PutUint64(buf[8:16], uint64(flags)) + return buf +} + +func buildIoctlStartup(filterFlags uint64) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint64(buf[0:8], filterFlags) + return buf +} + +// buildIoctlRecv packs a user-space pointer to a WINDIVERT_ADDRESS into +// the ioctl struct. The driver dereferences it to write the address for +// the received packet. Caller must keep the Address alive via +// runtime.KeepAlive. +func buildIoctlRecv(addr *Address) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint64(buf[0:8], uint64(uintptr(unsafe.Pointer(addr)))) + binary.LittleEndian.PutUint64(buf[8:16], 0) + return buf +} + +func buildIoctlSend(addr *Address) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint64(buf[0:8], uint64(uintptr(unsafe.Pointer(addr)))) + binary.LittleEndian.PutUint64(buf[8:16], uint64(unsafe.Sizeof(Address{}))) + return buf +} diff --git a/transport/internet/tls/tlsspoof/windivert/windivert.go b/transport/internet/tls/tlsspoof/windivert/windivert.go new file mode 100644 index 000000000000..9d309886cbe3 --- /dev/null +++ b/transport/internet/tls/tlsspoof/windivert/windivert.go @@ -0,0 +1,78 @@ +// Package windivert provides a pure-Go binding to the WinDivert kernel +// driver on Windows (amd64 and 386). User-mode WinDivert calls are +// reimplemented in Go; only the signed kernel driver is embedded as an +// asset, since SCM-installed drivers must live on disk and their +// Authenticode signature forbids modification. +// +// Administrator is required for the first Open in a process so SCM can +// load the driver. Upstream: https://github.com/basil00/WinDivert v2.2.2, +// redistributed under its LGPL v3 option; see assets/LICENSE.txt. +package windivert + +import "unsafe" + +const AssetVersion = "2.2.2" + +// MTUMax is WINDIVERT_MTU_MAX from windivert.h (40 + 0xFFFF). Suitable as +// a single-packet receive buffer size. +const MTUMax = 40 + 0xFFFF + +type Layer uint32 + +const LayerNetwork Layer = 0 + +type Flag uint64 + +const ( + // FlagSniff opens a passive observer: the driver copies matching packets + // to userspace without removing them from the network stack. Send is not + // required (and not allowed) on a sniffing handle. + FlagSniff Flag = 0x0001 + // FlagSendOnly opens a write-only injection handle; Recv is not allowed. + FlagSendOnly Flag = 0x0008 +) + +const ( + PriorityHighest int16 = 30000 + PriorityLowest int16 = -30000 +) + +// Address mirrors WINDIVERT_ADDRESS from windivert.h (80 bytes, +// little-endian on both amd64 and 386): +// +// 0: INT64 Timestamp +// 8: UINT32 bitfield: Layer:8 | Event:8 | flags | Reserved1:8 +// 12: UINT32 Reserved2 +// 16: 64 bytes union (WINDIVERT_DATA_NETWORK / FLOW / SOCKET / REFLECT) +type Address struct { + Timestamp int64 + bits uint32 + Reserved2 uint32 + union [64]byte +} + +var _ [80]byte = [unsafe.Sizeof(Address{})]byte{} + +// Bit positions inside the Address's packed flags word. +const ( + addrBitIPv6 = 20 + addrBitIPChecksum = 21 + addrBitTCPChecksum = 22 +) + +func getFlagBit(bits uint32, pos uint) bool { return bits&(1< Date: Mon, 4 May 2026 13:00:00 +0600 Subject: [PATCH 04/42] TLS: Add spoof, spoof_method and spoof_count options --- transport/internet/tls/config.pb.go | 48 +++++++++++++++++++++++------ transport/internet/tls/config.proto | 7 +++++ transport/internet/tls/tls.go | 33 ++++++++++++++++++++ 3 files changed, 79 insertions(+), 9 deletions(-) diff --git a/transport/internet/tls/config.pb.go b/transport/internet/tls/config.pb.go index 37628755eb4f..2d0e60a19448 100644 --- a/transport/internet/tls/config.pb.go +++ b/transport/internet/tls/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v6.33.5 +// protoc v7.34.1 // source: transport/internet/tls/config.proto package tls @@ -201,15 +201,20 @@ type Config struct { RejectUnknownSni bool `protobuf:"varint,12,opt,name=reject_unknown_sni,json=rejectUnknownSni,proto3" json:"reject_unknown_sni,omitempty"` MasterKeyLog string `protobuf:"bytes,15,opt,name=master_key_log,json=masterKeyLog,proto3" json:"master_key_log,omitempty"` // Lists of string as CurvePreferences values. - CurvePreferences []string `protobuf:"bytes,16,rep,name=curve_preferences,json=curvePreferences,proto3" json:"curve_preferences,omitempty"` - VerifyPeerCertByName []string `protobuf:"bytes,17,rep,name=verify_peer_cert_by_name,json=verifyPeerCertByName,proto3" json:"verify_peer_cert_by_name,omitempty"` - EchServerKeys []byte `protobuf:"bytes,18,opt,name=ech_server_keys,json=echServerKeys,proto3" json:"ech_server_keys,omitempty"` - EchConfigList string `protobuf:"bytes,19,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"` + CurvePreferences []string `protobuf:"bytes,16,rep,name=curve_preferences,json=curvePreferences,proto3" json:"curve_preferences,omitempty"` + VerifyPeerCertByName []string `protobuf:"bytes,17,rep,name=verify_peer_cert_by_name,json=verifyPeerCertByName,proto3" json:"verify_peer_cert_by_name,omitempty"` + EchServerKeys []byte `protobuf:"bytes,18,opt,name=ech_server_keys,json=echServerKeys,proto3" json:"ech_server_keys,omitempty"` + EchConfigList string `protobuf:"bytes,19,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"` + // Deprecated EchForceQuery string `protobuf:"bytes,20,opt,name=ech_force_query,json=echForceQuery,proto3" json:"ech_force_query,omitempty"` EchSocketSettings *internet.SocketConfig `protobuf:"bytes,21,opt,name=ech_socket_settings,json=echSocketSettings,proto3" json:"ech_socket_settings,omitempty"` PinnedPeerCertSha256 [][]byte `protobuf:"bytes,22,rep,name=pinned_peer_cert_sha256,json=pinnedPeerCertSha256,proto3" json:"pinned_peer_cert_sha256,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Spoof string `protobuf:"bytes,23,opt,name=spoof,proto3" json:"spoof,omitempty"` + SpoofMethod string `protobuf:"bytes,24,opt,name=spoof_method,json=spoofMethod,proto3" json:"spoof_method,omitempty"` + // Number of times to inject the fake ClientHello (0 or 1 = single-shot). + SpoofCount int32 `protobuf:"varint,25,opt,name=spoof_count,json=spoofCount,proto3" json:"spoof_count,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Config) Reset() { @@ -375,6 +380,27 @@ func (x *Config) GetPinnedPeerCertSha256() [][]byte { return nil } +func (x *Config) GetSpoof() string { + if x != nil { + return x.Spoof + } + return "" +} + +func (x *Config) GetSpoofMethod() string { + if x != nil { + return x.SpoofMethod + } + return "" +} + +func (x *Config) GetSpoofCount() int32 { + if x != nil { + return x.SpoofCount + } + return 0 +} + var File_transport_internet_tls_config_proto protoreflect.FileDescriptor const file_transport_internet_tls_config_proto_rawDesc = "" + @@ -393,7 +419,7 @@ const file_transport_internet_tls_config_proto_rawDesc = "" + "\x05Usage\x12\x10\n" + "\fENCIPHERMENT\x10\x00\x12\x14\n" + "\x10AUTHORITY_VERIFY\x10\x01\x12\x13\n" + - "\x0fAUTHORITY_ISSUE\x10\x02\"\xf5\x06\n" + + "\x0fAUTHORITY_ISSUE\x10\x02\"\xcf\a\n" + "\x06Config\x12%\n" + "\x0eallow_insecure\x18\x01 \x01(\bR\rallowInsecure\x12J\n" + "\vcertificate\x18\x02 \x03(\v2(.xray.transport.internet.tls.CertificateR\vcertificate\x12\x1f\n" + @@ -416,7 +442,11 @@ const file_transport_internet_tls_config_proto_rawDesc = "" + "\x0fech_config_list\x18\x13 \x01(\tR\rechConfigList\x12&\n" + "\x0fech_force_query\x18\x14 \x01(\tR\rechForceQuery\x12U\n" + "\x13ech_socket_settings\x18\x15 \x01(\v2%.xray.transport.internet.SocketConfigR\x11echSocketSettings\x125\n" + - "\x17pinned_peer_cert_sha256\x18\x16 \x03(\fR\x14pinnedPeerCertSha256Bs\n" + + "\x17pinned_peer_cert_sha256\x18\x16 \x03(\fR\x14pinnedPeerCertSha256\x12\x14\n" + + "\x05spoof\x18\x17 \x01(\tR\x05spoof\x12!\n" + + "\fspoof_method\x18\x18 \x01(\tR\vspoofMethod\x12\x1f\n" + + "\vspoof_count\x18\x19 \x01(\x05R\n" + + "spoofCountBs\n" + "\x1fcom.xray.transport.internet.tlsP\x01Z0github.com/xtls/xray-core/transport/internet/tls\xaa\x02\x1bXray.Transport.Internet.Tlsb\x06proto3" var ( diff --git a/transport/internet/tls/config.proto b/transport/internet/tls/config.proto index 4592822649c3..0039d0901a7d 100644 --- a/transport/internet/tls/config.proto +++ b/transport/internet/tls/config.proto @@ -87,4 +87,11 @@ message Config { SocketConfig ech_socket_settings = 21; repeated bytes pinned_peer_cert_sha256 = 22; + + string spoof = 23; + + string spoof_method = 24; + + // Number of times to inject the fake ClientHello (0 or 1 = single-shot). + int32 spoof_count = 25; } diff --git a/transport/internet/tls/tls.go b/transport/internet/tls/tls.go index 7fa3c25be55d..b8bc4102a31f 100644 --- a/transport/internet/tls/tls.go +++ b/transport/internet/tls/tls.go @@ -5,13 +5,17 @@ import ( "crypto/rand" "crypto/tls" "math/big" + gonet "net" "slices" + "strings" "time" utls "github.com/refraction-networking/utls" "github.com/xtls/xray-core/common/buf" + "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/utils" + "github.com/xtls/xray-core/transport/internet/tls/tlsspoof" ) type Interface interface { @@ -64,6 +68,35 @@ func Client(c net.Conn, config *tls.Config) net.Conn { return &Conn{Conn: tlsConn} } +// WrapWithSpoof wraps a connection with TLS spoofing if the config has +// spoof settings. The spoofed ClientHello is injected via raw sockets +// before the real TLS handshake, causing DPI middleboxes to see the +// forged SNI while the actual connection proceeds normally. +// spoofCount controls how many Write() calls trigger injection (0 = single-shot). +func WrapWithSpoof(c net.Conn, spoofSNI string, spoofMethodStr string, spoofCount int32, serverName string) (net.Conn, error) { + spoofSNI, method, err := tlsspoof.ParseOptions(spoofSNI, spoofMethodStr) + if err != nil { + return nil, errors.New("tls_spoof: invalid options").Base(err) + } + if spoofSNI == "" { + return c, nil + } + if serverName == "" { + return nil, errors.New("tls_spoof: requires a TLS server name (SNI)") + } + if gonet.ParseIP(serverName) != nil { + return nil, errors.New("tls_spoof: cannot spoof when server name is an IP literal") + } + if strings.EqualFold(spoofSNI, serverName) { + return nil, errors.New("tls_spoof: spoof must differ from server_name") + } + wrapped, err := tlsspoof.NewConn(c, method, spoofSNI, int(spoofCount)) + if err != nil { + return nil, errors.New("tls_spoof: failed to create spoof conn").Base(err) + } + return wrapped, nil +} + // Server initiates a TLS server handshake on the given connection. func Server(c net.Conn, config *tls.Config) net.Conn { tlsConn := tls.Server(c, config) From 7ac0e9bff9026d6a0d574a75a339ac4c21c97c70 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Thu, 7 May 2026 15:00:00 +0600 Subject: [PATCH 05/42] Transport: Integrate TLS spoof into dialers --- transport/internet/grpc/dial.go | 6 ++++++ transport/internet/httpupgrade/dialer.go | 6 ++++++ transport/internet/kcp/dialer.go | 9 ++++++++- transport/internet/splithttp/dialer.go | 6 ++++++ transport/internet/tcp/dialer.go | 10 ++++++++++ transport/internet/tls/config.pb.go | 2 +- transport/internet/websocket/dialer.go | 8 ++++++++ 7 files changed, 45 insertions(+), 2 deletions(-) diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index c8b8423c6579..b17caa9730fc 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -140,6 +140,12 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in if config.ServerName == "" && address.Family().IsDomain() { config.ServerName = address.Domain() } + if spoofConn, err := tls.WrapWithSpoof(c, tlsConfig.Spoof, tlsConfig.SpoofMethod, tlsConfig.SpoofCount, config.ServerName); err != nil { + c.Close() + return nil, err + } else { + c = spoofConn + } if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil { return tls.UClient(c, config, fingerprint), nil } else { // Fallback to normal gRPC TLS diff --git a/transport/internet/httpupgrade/dialer.go b/transport/internet/httpupgrade/dialer.go index 571797f6172d..bb9df1c912fb 100644 --- a/transport/internet/httpupgrade/dialer.go +++ b/transport/internet/httpupgrade/dialer.go @@ -66,6 +66,12 @@ func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings * tConfig := tls.ConfigFromStreamSettings(streamSettings) if tConfig != nil { tlsConfig := tConfig.GetTLSConfig(tls.WithDestination(dest), tls.WithNextProto("http/1.1")) + if spoofConn, err := tls.WrapWithSpoof(pconn, tConfig.Spoof, tConfig.SpoofMethod, tConfig.SpoofCount, tlsConfig.ServerName); err != nil { + pconn.Close() + return nil, err + } else { + pconn = spoofConn + } if fingerprint := tls.GetFingerprint(tConfig.Fingerprint); fingerprint != nil { conn = tls.UClient(pconn, tlsConfig, fingerprint) if err := conn.(*tls.UConn).WebsocketHandshakeContext(ctx); err != nil { diff --git a/transport/internet/kcp/dialer.go b/transport/internet/kcp/dialer.go index 175998ec7dd3..e3ff0bdc9a19 100644 --- a/transport/internet/kcp/dialer.go +++ b/transport/internet/kcp/dialer.go @@ -97,7 +97,14 @@ func DialKCP(ctx context.Context, dest net.Destination, streamSettings *internet var iConn stat.Connection = session if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { - iConn = tls.Client(iConn, config.GetTLSConfig(tls.WithDestination(dest))) + tlsConfig := config.GetTLSConfig(tls.WithDestination(dest)) + if spoofConn, err := tls.WrapWithSpoof(iConn, config.Spoof, config.SpoofMethod, config.SpoofCount, tlsConfig.ServerName); err != nil { + iConn.Close() + return nil, err + } else { + iConn = spoofConn.(stat.Connection) + } + iConn = tls.Client(iConn, tlsConfig) } return iConn, nil diff --git a/transport/internet/splithttp/dialer.go b/transport/internet/splithttp/dialer.go index f89c71ed9a07..d35a6f7c3db8 100644 --- a/transport/internet/splithttp/dialer.go +++ b/transport/internet/splithttp/dialer.go @@ -138,6 +138,12 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea } if gotlsConfig != nil { + if spoofConn, err := tls.WrapWithSpoof(conn, tlsConfig.Spoof, tlsConfig.SpoofMethod, tlsConfig.SpoofCount, gotlsConfig.ServerName); err != nil { + conn.Close() + return nil, err + } else { + conn = spoofConn + } if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil { conn = tls.UClient(conn, gotlsConfig, fingerprint) if err := conn.(*tls.UConn).HandshakeContext(ctxInner); err != nil { diff --git a/transport/internet/tcp/dialer.go b/transport/internet/tcp/dialer.go index 92fa7557f13a..e226a5657cb3 100644 --- a/transport/internet/tcp/dialer.go +++ b/transport/internet/tcp/dialer.go @@ -74,6 +74,11 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me } } if fingerprint := tls.GetFingerprint(config.Fingerprint); fingerprint != nil { + if spoofConn, err := tls.WrapWithSpoof(conn, config.Spoof, config.SpoofMethod, config.SpoofCount, tlsConfig.ServerName); err != nil { + return nil, err + } else { + conn = spoofConn + } conn = tls.UClient(conn, tlsConfig, fingerprint) if len(tlsConfig.NextProtos) == 1 && tlsConfig.NextProtos[0] == "http/1.1" { // allow manually specify err = conn.(*tls.UConn).WebsocketHandshakeContext(ctx) @@ -81,6 +86,11 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me err = conn.(*tls.UConn).HandshakeContext(ctx) } } else { + if spoofConn, err := tls.WrapWithSpoof(conn, config.Spoof, config.SpoofMethod, config.SpoofCount, tlsConfig.ServerName); err != nil { + return nil, err + } else { + conn = spoofConn + } conn = tls.Client(conn, tlsConfig) err = conn.(*tls.Conn).HandshakeContext(ctx) } diff --git a/transport/internet/tls/config.pb.go b/transport/internet/tls/config.pb.go index 2d0e60a19448..700c70883ab1 100644 --- a/transport/internet/tls/config.pb.go +++ b/transport/internet/tls/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v7.34.1 +// protoc v6.33.5 // source: transport/internet/tls/config.proto package tls diff --git a/transport/internet/websocket/dialer.go b/transport/internet/websocket/dialer.go index 8e295da062e8..f6eb73e1edae 100644 --- a/transport/internet/websocket/dialer.go +++ b/transport/internet/websocket/dialer.go @@ -94,6 +94,14 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in pconn = newConn } + // Wrap with TLS spoofing if configured + if spoofConn, err := tls.WrapWithSpoof(pconn, tConfig.Spoof, tConfig.SpoofMethod, tConfig.SpoofCount, tlsConfig.ServerName); err != nil { + pconn.Close() + return nil, err + } else { + pconn = spoofConn + } + // TLS and apply the handshake cn := tls.UClient(pconn, tlsConfig, fingerprint).(*tls.UConn) if err := cn.WebsocketHandshakeContext(ctx); err != nil { From a68197d24e3ad0aea0cf941268ccce96abece906 Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Thu, 7 May 2026 15:00:00 +0600 Subject: [PATCH 06/42] Transport: Integrate TLS spoof into dialers --- transport/internet/grpc/dial.go | 6 ++++++ transport/internet/httpupgrade/dialer.go | 6 ++++++ transport/internet/kcp/dialer.go | 9 ++++++++- transport/internet/splithttp/dialer.go | 6 ++++++ transport/internet/tcp/dialer.go | 10 ++++++++++ transport/internet/websocket/dialer.go | 8 ++++++++ 6 files changed, 44 insertions(+), 1 deletion(-) diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index c8b8423c6579..b17caa9730fc 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -140,6 +140,12 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in if config.ServerName == "" && address.Family().IsDomain() { config.ServerName = address.Domain() } + if spoofConn, err := tls.WrapWithSpoof(c, tlsConfig.Spoof, tlsConfig.SpoofMethod, tlsConfig.SpoofCount, config.ServerName); err != nil { + c.Close() + return nil, err + } else { + c = spoofConn + } if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil { return tls.UClient(c, config, fingerprint), nil } else { // Fallback to normal gRPC TLS diff --git a/transport/internet/httpupgrade/dialer.go b/transport/internet/httpupgrade/dialer.go index 571797f6172d..bb9df1c912fb 100644 --- a/transport/internet/httpupgrade/dialer.go +++ b/transport/internet/httpupgrade/dialer.go @@ -66,6 +66,12 @@ func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings * tConfig := tls.ConfigFromStreamSettings(streamSettings) if tConfig != nil { tlsConfig := tConfig.GetTLSConfig(tls.WithDestination(dest), tls.WithNextProto("http/1.1")) + if spoofConn, err := tls.WrapWithSpoof(pconn, tConfig.Spoof, tConfig.SpoofMethod, tConfig.SpoofCount, tlsConfig.ServerName); err != nil { + pconn.Close() + return nil, err + } else { + pconn = spoofConn + } if fingerprint := tls.GetFingerprint(tConfig.Fingerprint); fingerprint != nil { conn = tls.UClient(pconn, tlsConfig, fingerprint) if err := conn.(*tls.UConn).WebsocketHandshakeContext(ctx); err != nil { diff --git a/transport/internet/kcp/dialer.go b/transport/internet/kcp/dialer.go index 175998ec7dd3..e3ff0bdc9a19 100644 --- a/transport/internet/kcp/dialer.go +++ b/transport/internet/kcp/dialer.go @@ -97,7 +97,14 @@ func DialKCP(ctx context.Context, dest net.Destination, streamSettings *internet var iConn stat.Connection = session if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { - iConn = tls.Client(iConn, config.GetTLSConfig(tls.WithDestination(dest))) + tlsConfig := config.GetTLSConfig(tls.WithDestination(dest)) + if spoofConn, err := tls.WrapWithSpoof(iConn, config.Spoof, config.SpoofMethod, config.SpoofCount, tlsConfig.ServerName); err != nil { + iConn.Close() + return nil, err + } else { + iConn = spoofConn.(stat.Connection) + } + iConn = tls.Client(iConn, tlsConfig) } return iConn, nil diff --git a/transport/internet/splithttp/dialer.go b/transport/internet/splithttp/dialer.go index f89c71ed9a07..d35a6f7c3db8 100644 --- a/transport/internet/splithttp/dialer.go +++ b/transport/internet/splithttp/dialer.go @@ -138,6 +138,12 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea } if gotlsConfig != nil { + if spoofConn, err := tls.WrapWithSpoof(conn, tlsConfig.Spoof, tlsConfig.SpoofMethod, tlsConfig.SpoofCount, gotlsConfig.ServerName); err != nil { + conn.Close() + return nil, err + } else { + conn = spoofConn + } if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil { conn = tls.UClient(conn, gotlsConfig, fingerprint) if err := conn.(*tls.UConn).HandshakeContext(ctxInner); err != nil { diff --git a/transport/internet/tcp/dialer.go b/transport/internet/tcp/dialer.go index 92fa7557f13a..e226a5657cb3 100644 --- a/transport/internet/tcp/dialer.go +++ b/transport/internet/tcp/dialer.go @@ -74,6 +74,11 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me } } if fingerprint := tls.GetFingerprint(config.Fingerprint); fingerprint != nil { + if spoofConn, err := tls.WrapWithSpoof(conn, config.Spoof, config.SpoofMethod, config.SpoofCount, tlsConfig.ServerName); err != nil { + return nil, err + } else { + conn = spoofConn + } conn = tls.UClient(conn, tlsConfig, fingerprint) if len(tlsConfig.NextProtos) == 1 && tlsConfig.NextProtos[0] == "http/1.1" { // allow manually specify err = conn.(*tls.UConn).WebsocketHandshakeContext(ctx) @@ -81,6 +86,11 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me err = conn.(*tls.UConn).HandshakeContext(ctx) } } else { + if spoofConn, err := tls.WrapWithSpoof(conn, config.Spoof, config.SpoofMethod, config.SpoofCount, tlsConfig.ServerName); err != nil { + return nil, err + } else { + conn = spoofConn + } conn = tls.Client(conn, tlsConfig) err = conn.(*tls.Conn).HandshakeContext(ctx) } diff --git a/transport/internet/websocket/dialer.go b/transport/internet/websocket/dialer.go index 8e295da062e8..f6eb73e1edae 100644 --- a/transport/internet/websocket/dialer.go +++ b/transport/internet/websocket/dialer.go @@ -94,6 +94,14 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in pconn = newConn } + // Wrap with TLS spoofing if configured + if spoofConn, err := tls.WrapWithSpoof(pconn, tConfig.Spoof, tConfig.SpoofMethod, tConfig.SpoofCount, tlsConfig.ServerName); err != nil { + pconn.Close() + return nil, err + } else { + pconn = spoofConn + } + // TLS and apply the handshake cn := tls.UClient(pconn, tlsConfig, fingerprint).(*tls.UConn) if err := cn.WebsocketHandshakeContext(ctx); err != nil { From 0aed3e2b5a5155ed1ca664a03d4ba3d69844171d Mon Sep 17 00:00:00 2001 From: Tamim Hossain <132823494+codewithtamim@users.noreply.github.com> Date: Sat, 9 May 2026 10:30:00 +0600 Subject: [PATCH 07/42] Rawpacket: Add raw socket spoofers for Linux, Darwin, FreeBSD and Windows --- .../internet/finalmask/rawpacket/endpoints.go | 27 + .../internet/finalmask/rawpacket/packet.go | 163 +++ .../finalmask/rawpacket/raw_darwin.go | 200 +++ .../finalmask/rawpacket/raw_freebsd.go | 174 +++ .../internet/finalmask/rawpacket/raw_linux.go | 168 +++ .../internet/finalmask/rawpacket/raw_stub.go | 15 + .../internet/finalmask/rawpacket/raw_unix.go | 25 + .../finalmask/rawpacket/raw_windows.go | 236 ++++ .../internet/finalmask/rawpacket/tcpip.go | 155 +++ .../rawpacket/windivert/assets/LICENSE.txt | 1191 +++++++++++++++++ .../windivert/assets/WinDivert32.sys | Bin 0 -> 79792 bytes .../windivert/assets/WinDivert64.sys | Bin 0 -> 94144 bytes .../rawpacket/windivert/assets_386.go | 14 + .../rawpacket/windivert/assets_amd64.go | 14 + .../rawpacket/windivert/assets_unsupported.go | 7 + .../rawpacket/windivert/driver_windows.go | 211 +++ .../finalmask/rawpacket/windivert/filter.go | 181 +++ .../rawpacket/windivert/handle_windows.go | 323 +++++ .../rawpacket/windivert/windivert.go | 78 ++ 19 files changed, 3182 insertions(+) create mode 100644 transport/internet/finalmask/rawpacket/endpoints.go create mode 100644 transport/internet/finalmask/rawpacket/packet.go create mode 100644 transport/internet/finalmask/rawpacket/raw_darwin.go create mode 100644 transport/internet/finalmask/rawpacket/raw_freebsd.go create mode 100644 transport/internet/finalmask/rawpacket/raw_linux.go create mode 100644 transport/internet/finalmask/rawpacket/raw_stub.go create mode 100644 transport/internet/finalmask/rawpacket/raw_unix.go create mode 100644 transport/internet/finalmask/rawpacket/raw_windows.go create mode 100644 transport/internet/finalmask/rawpacket/tcpip.go create mode 100644 transport/internet/finalmask/rawpacket/windivert/assets/LICENSE.txt create mode 100644 transport/internet/finalmask/rawpacket/windivert/assets/WinDivert32.sys create mode 100644 transport/internet/finalmask/rawpacket/windivert/assets/WinDivert64.sys create mode 100644 transport/internet/finalmask/rawpacket/windivert/assets_386.go create mode 100644 transport/internet/finalmask/rawpacket/windivert/assets_amd64.go create mode 100644 transport/internet/finalmask/rawpacket/windivert/assets_unsupported.go create mode 100644 transport/internet/finalmask/rawpacket/windivert/driver_windows.go create mode 100644 transport/internet/finalmask/rawpacket/windivert/filter.go create mode 100644 transport/internet/finalmask/rawpacket/windivert/handle_windows.go create mode 100644 transport/internet/finalmask/rawpacket/windivert/windivert.go diff --git a/transport/internet/finalmask/rawpacket/endpoints.go b/transport/internet/finalmask/rawpacket/endpoints.go new file mode 100644 index 000000000000..6c7107eb3987 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/endpoints.go @@ -0,0 +1,27 @@ +package rawpacket + +import ( + "net" + "net/netip" + + "errors" +) + +// The returned addresses are v4-unmapped and share the same family. +func tcpEndpoints(conn net.Conn) (*net.TCPConn, netip.AddrPort, netip.AddrPort, error) { + tcpConn, isTCP := conn.(*net.TCPConn) + if !isTCP { + return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("rawpacket: underlying conn is not *net.TCPConn") + } + local := tcpConn.LocalAddr().(*net.TCPAddr).AddrPort() + remote := tcpConn.RemoteAddr().(*net.TCPAddr).AddrPort() + if !local.IsValid() || !remote.IsValid() { + return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("rawpacket: invalid conn address") + } + local = netip.AddrPortFrom(local.Addr().Unmap(), local.Port()) + remote = netip.AddrPortFrom(remote.Addr().Unmap(), remote.Port()) + if local.Addr().Is4() != remote.Addr().Is4() { + return nil, netip.AddrPort{}, netip.AddrPort{}, errors.New("rawpacket: local/remote address family mismatch") + } + return tcpConn, local, remote, nil +} diff --git a/transport/internet/finalmask/rawpacket/packet.go b/transport/internet/finalmask/rawpacket/packet.go new file mode 100644 index 000000000000..914dc04760b8 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/packet.go @@ -0,0 +1,163 @@ +package rawpacket + +import ( + "encoding/binary" + "net/netip" + + "fmt" +) + +const ( + defaultWindowSize uint16 = 0xFFFF + tcpHeaderLen = TCPMinimumSize + + tcpOptionMD5Signature = 19 + tcpOptionMD5SignatureLength = 18 + tcpTimestampBackdate = 3600000 +) + +type spoofPacketInfo struct { + seqNum uint32 + ackNum uint32 + corrupt bool + options []byte +} + +func buildTCPSegment( + src netip.AddrPort, + dst netip.AddrPort, + packetInfo spoofPacketInfo, + payload []byte, + ttl uint8, +) []byte { + if src.Addr().Is4() != dst.Addr().Is4() { + panic("rawpacket: mixed IPv4/IPv6 address family") + } + var ( + frame []byte + ipHeaderLen int + ) + ipPayloadLen := tcpHeaderLen + len(packetInfo.options) + len(payload) + if src.Addr().Is4() { + ipHeaderLen = IPv4MinimumSize + frame = make([]byte, ipHeaderLen+ipPayloadLen) + ip := IPv4(frame[:ipHeaderLen]) + ip.Encode(uint16(len(frame)), 0, ttl, TCPProtocolNumber, src.Addr(), dst.Addr()) + } else { + ipHeaderLen = IPv6MinimumSize + frame = make([]byte, ipHeaderLen+ipPayloadLen) + ip := IPv6(frame[:ipHeaderLen]) + ip.Encode(uint16(ipPayloadLen), TCPProtocolNumber, ttl, src.Addr(), dst.Addr()) + } + encodeTCP(frame, ipHeaderLen, src, dst, packetInfo, payload) + return frame +} + +func encodeTCP(frame []byte, ipHeaderLen int, src, dst netip.AddrPort, packetInfo spoofPacketInfo, payload []byte) { + tcp := TCP(frame[ipHeaderLen:]) + copy(frame[ipHeaderLen+tcpHeaderLen:], packetInfo.options) + optionsLen := len(packetInfo.options) + copy(frame[ipHeaderLen+tcpHeaderLen+optionsLen:], payload) + tcp.Encode(src.Port(), dst.Port(), packetInfo.seqNum, packetInfo.ackNum, uint8(tcpHeaderLen+optionsLen), TCPFlagAck|TCPFlagPsh, defaultWindowSize) + applyTCPChecksum(tcp, src.Addr(), dst.Addr(), payload, packetInfo.corrupt) +} + +func buildSpoofFrame(method Method, src, dst netip.AddrPort, sendNext, receiveNext, timestamp uint32, tcpOptions, payload []byte, ttl uint8) ([]byte, error) { + packetInfo, err := resolveSpoofPacketInfo(method, sendNext, receiveNext, timestamp, tcpOptions, payload) + if err != nil { + return nil, err + } + return buildTCPSegment(src, dst, packetInfo, payload, ttl), nil +} + +// buildSpoofTCPSegment returns a TCP segment without an IP header, for +// platforms where the kernel synthesises the IP header (darwin IPv6). +func buildSpoofTCPSegment(method Method, src, dst netip.AddrPort, sendNext, receiveNext, timestamp uint32, payload []byte) ([]byte, error) { + packetInfo, err := resolveSpoofPacketInfo(method, sendNext, receiveNext, timestamp, nil, payload) + if err != nil { + return nil, err + } + segment := make([]byte, tcpHeaderLen+len(packetInfo.options)+len(payload)) + encodeTCP(segment, 0, src, dst, packetInfo, payload) + return segment, nil +} + +func resolveSpoofPacketInfo(method Method, sendNext, receiveNext, timestamp uint32, tcpOptions, payload []byte) (spoofPacketInfo, error) { + packetInfo := spoofPacketInfo{seqNum: sendNext, ackNum: receiveNext} + switch method { + case MethodWrongSequence: + packetInfo.seqNum = sendNext - uint32(len(payload)) + case MethodWrongChecksum: + packetInfo.corrupt = true + case MethodWrongAcknowledgment: + packetInfo.ackNum = receiveNext - uint32(defaultWindowSize/2) + case MethodWrongMD5Sig: + packetInfo.options = buildMD5SignatureOptions() + case MethodWrongTimestamp: + packetInfo.options = buildWrongTimestampOptions(timestamp, tcpOptions) + default: + return packetInfo, fmt.Errorf("rawpacket: unknown method %v", method) + } + return packetInfo, nil +} + +func buildMD5SignatureOptions() []byte { + options := make([]byte, tcpOptionMD5SignatureLength+2) + options[0] = tcpOptionMD5Signature + options[1] = tcpOptionMD5SignatureLength + return options +} + +func buildWrongTimestampOptions(timestamp uint32, tcpOptions []byte) []byte { + spoofedTimestamp := timestamp + if spoofedTimestamp > tcpTimestampBackdate { + spoofedTimestamp -= tcpTimestampBackdate + } else { + spoofedTimestamp = 0 + } + if rewriteTCPOptionTimestamp(tcpOptions, spoofedTimestamp) { + return tcpOptions + } + options := make([]byte, TCPOptionTSLength+2) + EncodeTSOption(spoofedTimestamp, 0, options) + return options +} + +// rewriteTCPOptionTimestamp finds the TS option in tcpOptions and writes +// timestamp into its TSVal field in place. The caller must own tcpOptions +// (parseTCPPacket already returns a private copy on Windows). +func rewriteTCPOptionTimestamp(tcpOptions []byte, timestamp uint32) bool { + for i := 0; i < len(tcpOptions); { + switch tcpOptions[i] { + case TCPOptionEOL: + return false + case TCPOptionNOP: + i++ + continue + } + if i+1 >= len(tcpOptions) { + return false + } + optionLen := int(tcpOptions[i+1]) + if optionLen < 2 || i+optionLen > len(tcpOptions) { + return false + } + if tcpOptions[i] == TCPOptionTS && optionLen == TCPOptionTSLength { + binary.BigEndian.PutUint32(tcpOptions[i+2:], timestamp) + return true + } + i += optionLen + } + return false +} + +func applyTCPChecksum(tcp TCP, srcAddr, dstAddr netip.Addr, payload []byte, corrupt bool) { + tcpLen := int(tcp.DataOffset()) + len(payload) + pseudo := PseudoHeaderChecksum(TCPProtocolNumber, srcAddr.AsSlice(), dstAddr.AsSlice(), uint16(tcpLen)) + payloadChecksum := Checksum(payload, 0) + tcpChecksum := ^tcp.CalculateChecksum(CombineChecksum(pseudo, payloadChecksum)) + if corrupt { + tcpChecksum ^= 0xFFFF + } + tcp.SetChecksum(tcpChecksum) +} diff --git a/transport/internet/finalmask/rawpacket/raw_darwin.go b/transport/internet/finalmask/rawpacket/raw_darwin.go new file mode 100644 index 000000000000..1b2335565ae2 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/raw_darwin.go @@ -0,0 +1,200 @@ +package rawpacket + +import ( + "encoding/binary" + "net" + "net/netip" + "strconv" + "strings" + "sync" + "syscall" + + "errors" + "fmt" + + "golang.org/x/sys/unix" +) + +const PlatformSupported = true + +// Offsets into xinpcb_n within each net.inet.tcp.pcblist_n record, identical +// to the values used by common/process/searcher_darwin_shared.go. +const ( + darwinXinpgenSize = 24 + darwinXsocketOffset = 104 + darwinXinpcbForeignPort = 16 + darwinXinpcbLocalPort = 18 + darwinXinpcbVFlag = 44 + darwinXinpcbForeignAddr = 48 + darwinXinpcbLocalAddr = 64 + darwinXinpcbIPv4Offset = 12 + + darwinTCPExtraSize = 208 + + darwinXtcpcbSndNxtOffset = 56 + darwinXtcpcbRcvNxtOffset = 80 +) + +// darwinStructSize returns the size of xinpcb_n for the running Darwin kernel. +// Darwin 22 (macOS 13 Ventura) grew the struct from 384 to 408 bytes; there is +// no ABI-stable way to read it, so we key off the kernel version. +var darwinStructSize = sync.OnceValues(func() (int, error) { + value, err := syscall.Sysctl("kern.osrelease") + if err != nil { + return 0, fmt.Errorf("sysctl kern.osrelease: %w", err) + } + major, _, ok := strings.Cut(value, ".") + if !ok { + return 0, fmt.Errorf("unexpected kern.osrelease format: %s", value) + } + n, err := strconv.ParseInt(major, 10, 64) + if err != nil { + return 0, fmt.Errorf("parse kern.osrelease major version: : %w", err) + } + if n >= 22 { + return 408, nil + } + return 384, nil +}) + +type darwinSpoofer struct { + method Method + src netip.AddrPort + dst netip.AddrPort + rawFD int + rawSockAddr unix.Sockaddr + sendNext uint32 + receiveNext uint32 + ttl uint8 +} + +func newRawSpoofer(conn net.Conn, method Method, ttl uint8) (rawSpoofer, error) { + if method == MethodWrongTimestamp { + return nil, errors.New("rawpacket: wrong-timestamp is not supported on macOS") + } + _, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + fd, sockaddr, err := openDarwinRawSocket(src, dst) + if err != nil { + return nil, err + } + sendNext, receiveNext, err := readDarwinTCPSequence(src, dst) + if err != nil { + unix.Close(fd) + return nil, err + } + return &darwinSpoofer{ + method: method, + src: src, + dst: dst, + rawFD: fd, + rawSockAddr: sockaddr, + sendNext: sendNext, + receiveNext: receiveNext, + ttl: ttl, + }, nil +} + +// readDarwinTCPSequence scans net.inet.tcp.pcblist_n for the PCB that matches +// src -> dst and returns (snd_nxt, rcv_nxt). These live in xtcpcb_n at the end +// of each record; see darwin-xnu bsd/netinet/in_pcblist.c:get_pcblist_n. +func readDarwinTCPSequence(src, dst netip.AddrPort) (uint32, uint32, error) { + buffer, err := unix.SysctlRaw("net.inet.tcp.pcblist_n") + if err != nil { + return 0, 0, fmt.Errorf("sysctl net.inet.tcp.pcblist_n: %w", err) + } + structSize, err := darwinStructSize() + if err != nil { + return 0, 0, err + } + itemSize := structSize + darwinTCPExtraSize + for i := darwinXinpgenSize; i+itemSize <= len(buffer); i += itemSize { + inpcb := buffer[i : i+darwinXsocketOffset] + xtcpcb := buffer[i+structSize : i+itemSize] + localPort := binary.BigEndian.Uint16(inpcb[darwinXinpcbLocalPort : darwinXinpcbLocalPort+2]) + remotePort := binary.BigEndian.Uint16(inpcb[darwinXinpcbForeignPort : darwinXinpcbForeignPort+2]) + if localPort != src.Port() || remotePort != dst.Port() { + continue + } + versionFlag := inpcb[darwinXinpcbVFlag] + var localAddr, remoteAddr netip.Addr + switch { + case versionFlag&0x1 != 0: + localAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset : darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset+4])) + remoteAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset : darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset+4])) + case versionFlag&0x2 != 0: + localAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbLocalAddr : darwinXinpcbLocalAddr+16])) + remoteAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbForeignAddr : darwinXinpcbForeignAddr+16])) + default: + continue + } + if localAddr.Unmap() != src.Addr() || remoteAddr.Unmap() != dst.Addr() { + continue + } + sendNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbSndNxtOffset : darwinXtcpcbSndNxtOffset+4]) + receiveNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbRcvNxtOffset : darwinXtcpcbRcvNxtOffset+4]) + return sendNext, receiveNext, nil + } + return 0, 0, fmt.Errorf("rawpacket: connection %v->%v not found in pcblist_n", src, dst) +} + +func openDarwinRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) { + if dst.Addr().Is4() { + return openIPv4RawSocket(dst) + } + // macOS does not accept IPV6_HDRINCL on AF_INET6 SOCK_RAW IPPROTO_TCP + // sockets, so the kernel builds the IPv6 header itself. Bind to the real + // connection's source address so in6_selectsrc returns it, and rely on + // in6p_cksum defaulting to -1 so the user-supplied TCP checksum is + // preserved (including deliberately corrupted ones). + fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, fmt.Errorf("open AF_INET6 SOCK_RAW: %w", err) + } + err = unix.Bind(fd, &unix.SockaddrInet6{Addr: src.Addr().As16()}) + if err != nil { + unix.Close(fd) + return -1, nil, fmt.Errorf("bind AF_INET6 SOCK_RAW: %w", err) + } + sockaddr := &unix.SockaddrInet6{Port: int(dst.Port()), Addr: dst.Addr().As16()} + return fd, sockaddr, nil +} + +func (s *darwinSpoofer) Inject(payload []byte) error { + if !s.src.Addr().Is4() { + segment, err := buildSpoofTCPSegment(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, payload) + if err != nil { + return err + } + err = unix.Sendto(s.rawFD, segment, 0, s.rawSockAddr) + if err != nil { + return fmt.Errorf("sendto raw socket: %w", err) + } + return nil + } + frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, nil, payload, s.ttl) + if err != nil { + return err + } + // Darwin inherits the historical BSD quirk: with IP_HDRINCL the kernel + // expects ip_len and ip_off in host byte order, not network byte order. + ip := IPv4(frame) + binary.NativeEndian.PutUint16(ip[2:4], ip.TotalLength()) + binary.NativeEndian.PutUint16(ip[6:8], uint16(ip.Flags())<<13|ip.FragmentOffset()) + err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) + if err != nil { + return fmt.Errorf("sendto raw socket: %w", err) + } + return nil +} + +func (s *darwinSpoofer) Close() error { + if s.rawFD < 0 { + return nil + } + err := unix.Close(s.rawFD) + s.rawFD = -1 + return err +} diff --git a/transport/internet/finalmask/rawpacket/raw_freebsd.go b/transport/internet/finalmask/rawpacket/raw_freebsd.go new file mode 100644 index 000000000000..b3d2e13492a8 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/raw_freebsd.go @@ -0,0 +1,174 @@ +package rawpacket + +import ( + "encoding/binary" + "errors" + "fmt" + "net" + "net/netip" + "syscall" + "unsafe" + + "golang.org/x/sys/unix" +) + +const PlatformSupported = true + +// FreeBSD tcp_info offsets for snd_nxt and rcv_nxt. +// Derived from FreeBSD sys/netinet/tcp.h struct tcp_info layout. +// +// struct tcp_info { +// u8 state, __ca, __retrans, __probes, __backoff, opts, wscale = 8 bytes (with pad) +// u32 rto, __ato, snd_mss, rcv_mss = 16 bytes (offset 8) +// u32 __unacked, __sacked, __lost, __retrans, __fackets = 20 bytes (offset 24) +// u32 __last_data_sent, __last_ack_sent, last_data_recv, __last_ack_recv = 16 bytes (offset 44) +// u32 __pmtu, __rcv_ssthresh, rtt, rttvar, snd_ssthresh, snd_cwnd, __advmss, __reordering = 32 bytes (offset 60) +// u32 __rcv_rtt, rcv_space = 8 bytes (offset 92) +// u32 snd_wnd, snd_bwnd = 8 bytes (offset 100) +// u32 snd_nxt, rcv_nxt = 8 bytes (offset 108) +// ... remaining fields +// } +const ( + freebsdTCPInfoSndNxtOffset = 108 + freebsdTCPInfoRcvNxtOffset = 112 + freebsdTCPInfoMinSize = 116 // must read at least through rcv_nxt +) + +type freebsdSpoofer struct { + method Method + src netip.AddrPort + dst netip.AddrPort + rawFD int + rawSockAddr unix.Sockaddr + sendNext uint32 + receiveNext uint32 + ttl uint8 +} + +func newRawSpoofer(conn net.Conn, method Method, ttl uint8) (rawSpoofer, error) { + if method == MethodWrongTimestamp { + return nil, errors.New("rawpacket: wrong-timestamp is not supported on FreeBSD") + } + tcpConn, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + fd, sockaddr, err := openFreeBSDRawSocket(src, dst) + if err != nil { + return nil, err + } + sendNext, receiveNext, err := readFreeBSDTCPSequence(tcpConn) + if err != nil { + unix.Close(fd) + return nil, err + } + return &freebsdSpoofer{ + method: method, + src: src, + dst: dst, + rawFD: fd, + rawSockAddr: sockaddr, + sendNext: sendNext, + receiveNext: receiveNext, + ttl: ttl, + }, nil +} + +// readFreeBSDTCPSequence retrieves snd_nxt and rcv_nxt via TCP_INFO getsockopt. +func readFreeBSDTCPSequence(conn *net.TCPConn) (uint32, uint32, error) { + raw, err := conn.SyscallConn() + if err != nil { + return 0, 0, fmt.Errorf("rawpacket: SyscallConn: %w", err) + } + var sendNext, receiveNext uint32 + var sockErr error + err = raw.Control(func(fd uintptr) { + buf := make([]byte, 256) // generous buffer for tcp_info + bufLen := uint32(len(buf)) + _, _, errno := syscall.Syscall6( + syscall.SYS_GETSOCKOPT, + fd, + uintptr(syscall.IPPROTO_TCP), + uintptr(0x20), // TCP_INFO = 0x20 + uintptr(unsafe.Pointer(&buf[0])), + uintptr(unsafe.Pointer(&bufLen)), + 0, + ) + if errno != 0 { + sockErr = fmt.Errorf("rawpacket: getsockopt TCP_INFO: %w", errno) + return + } + if bufLen < freebsdTCPInfoMinSize { + sockErr = fmt.Errorf("rawpacket: TCP_INFO too short: %d < %d", bufLen, freebsdTCPInfoMinSize) + return + } + sendNext = binary.NativeEndian.Uint32(buf[freebsdTCPInfoSndNxtOffset : freebsdTCPInfoSndNxtOffset+4]) + receiveNext = binary.NativeEndian.Uint32(buf[freebsdTCPInfoRcvNxtOffset : freebsdTCPInfoRcvNxtOffset+4]) + }) + if err != nil { + return 0, 0, err + } + if sockErr != nil { + return 0, 0, sockErr + } + return sendNext, receiveNext, nil +} + +func openFreeBSDRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) { + if dst.Addr().Is4() { + return openIPv4RawSocket(dst) + } + // FreeBSD, like macOS, does not support IPV6_HDRINCL on SOCK_RAW/IPPROTO_TCP. + // The kernel constructs the IPv6 header. Bind to the source address + // and let the kernel fill in the IPv6 header automatically. + fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, fmt.Errorf("rawpacket: open AF_INET6 SOCK_RAW: %w", err) + } + err = unix.Bind(fd, &unix.SockaddrInet6{Addr: src.Addr().As16()}) + if err != nil { + unix.Close(fd) + return -1, nil, fmt.Errorf("rawpacket: bind AF_INET6 SOCK_RAW: %w", err) + } + sockaddr := &unix.SockaddrInet6{Port: int(dst.Port()), Addr: dst.Addr().As16()} + return fd, sockaddr, nil +} + +func (s *freebsdSpoofer) Inject(payload []byte) error { + if !s.src.Addr().Is4() { + // IPv6: kernel builds the IP header, we supply TCP segment only. + segment, err := buildSpoofTCPSegment(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, payload) + if err != nil { + return err + } + err = unix.Sendto(s.rawFD, segment, 0, s.rawSockAddr) + if err != nil { + return fmt.Errorf("rawpacket: sendto raw socket: %w", err) + } + return nil + } + // IPv4: we build the full IP+TCP frame with IP_HDRINCL. + frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, nil, payload, s.ttl) + if err != nil { + return err + } + // FreeBSD inherits the historical BSD quirk: with IP_HDRINCL the kernel + // expects ip_len and ip_off in host byte order, not network byte order. + ip := IPv4(frame) + binary.NativeEndian.PutUint16(ip[2:4], ip.TotalLength()) + binary.NativeEndian.PutUint16(ip[6:8], uint16(ip.Flags())<<13|ip.FragmentOffset()) + err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) + if err != nil { + return fmt.Errorf("rawpacket: sendto raw socket: %w", err) + } + return nil +} + +func (s *freebsdSpoofer) Close() error { + if s.rawFD < 0 { + return nil + } + err := unix.Close(s.rawFD) + s.rawFD = -1 + return err +} diff --git a/transport/internet/finalmask/rawpacket/raw_linux.go b/transport/internet/finalmask/rawpacket/raw_linux.go new file mode 100644 index 000000000000..1de3ff862839 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/raw_linux.go @@ -0,0 +1,168 @@ +package rawpacket + +import ( + "fmt" + "net" + "net/netip" + + "golang.org/x/sys/unix" +) + +const PlatformSupported = true + +const ( + // Values of enum { TCP_NO_QUEUE, TCP_RECV_QUEUE, TCP_SEND_QUEUE } from + // include/net/tcp.h; not exported by golang.org/x/sys/unix. + tcpRecvQueue = 1 + tcpSendQueue = 2 +) + +type linuxSpoofer struct { + method Method + src netip.AddrPort + dst netip.AddrPort + rawFD int + rawSockAddr unix.Sockaddr + sendNext uint32 + receiveNext uint32 + timestamp uint32 + ttl uint8 +} + +func newRawSpoofer(conn net.Conn, method Method, ttl uint8) (rawSpoofer, error) { + tcpConn, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + fd, sockaddr, err := openLinuxRawSocket(dst) + if err != nil { + return nil, err + } + spoofer := &linuxSpoofer{ + method: method, + src: src, + dst: dst, + rawFD: fd, + rawSockAddr: sockaddr, + ttl: ttl, + } + err = spoofer.loadSequenceNumbers(tcpConn) + if err != nil { + unix.Close(fd) + return nil, err + } + return spoofer, nil +} + +func openLinuxRawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) { + if dst.Addr().Is4() { + return openIPv4RawSocket(dst) + } + fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, fmt.Errorf("open AF_INET6 SOCK_RAW: %w", err) + } + err = unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_HDRINCL, 1) + if err != nil { + unix.Close(fd) + return -1, nil, fmt.Errorf("set IPV6_HDRINCL: %w", err) + } + // Linux raw IPv6 sockets interpret sin6_port as a nexthdr protocol number + // (see raw(7)); any value other than 0 or the socket's IPPROTO_TCP causes + // sendto to fail with EINVAL. The destination is already encoded in the + // user-supplied IPv6 header under IPV6_HDRINCL. + sockaddr := &unix.SockaddrInet6{Addr: dst.Addr().As16()} + return fd, sockaddr, nil +} + +// loadSequenceNumbers puts the socket briefly into TCP_REPAIR mode to read +// snd_nxt and rcv_nxt from the kernel. TCP_REPAIR requires CAP_NET_ADMIN; +// callers must run as root or grant both CAP_NET_RAW and CAP_NET_ADMIN. +// +// If the TCP_REPAIR_OFF revert fails, the socket would stay in TCP_REPAIR +// state and subsequent Write() calls would silently buffer instead of sending. +// Surface that error so callers can abort. +func (s *linuxSpoofer) loadSequenceNumbers(tcpConn *net.TCPConn) error { + rawConn, err := tcpConn.SyscallConn() + if err != nil { + return err + } + var ctrlErr error + err = rawConn.Control(func(raw uintptr) { + fd := int(raw) + + if s.method == MethodWrongTimestamp { + timestamp, tsErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_TIMESTAMP) + if tsErr != nil { + ctrlErr = fmt.Errorf("rawpacket: read timestamp: %w", tsErr) + return + } + s.timestamp = uint32(timestamp) + } + + ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR, unix.TCP_REPAIR_ON) + if ctrlErr != nil { + ctrlErr = fmt.Errorf("rawpacket: enter TCP_REPAIR (need CAP_NET_ADMIN): %w", ctrlErr) + return + } + defer func() { + offErr := unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR, unix.TCP_REPAIR_OFF) + if offErr != nil { + offErr = fmt.Errorf("rawpacket: leave TCP_REPAIR: %w", offErr) + if ctrlErr == nil { + ctrlErr = offErr + } else { + ctrlErr = fmt.Errorf("%v; also %w", ctrlErr, offErr) + } + } + }() + + ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR_QUEUE, tcpSendQueue) + if ctrlErr != nil { + ctrlErr = fmt.Errorf("rawpacket: select TCP_SEND_QUEUE: %w", ctrlErr) + return + } + sendSequence, seqErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_QUEUE_SEQ) + if seqErr != nil { + ctrlErr = fmt.Errorf("rawpacket: read send queue sequence: %w", seqErr) + return + } + ctrlErr = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR_QUEUE, tcpRecvQueue) + if ctrlErr != nil { + ctrlErr = fmt.Errorf("rawpacket: select TCP_RECV_QUEUE: %w", ctrlErr) + return + } + receiveSequence, seqErr := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_QUEUE_SEQ) + if seqErr != nil { + ctrlErr = fmt.Errorf("rawpacket: read recv queue sequence: %w", seqErr) + return + } + s.sendNext = uint32(sendSequence) + s.receiveNext = uint32(receiveSequence) + }) + if err != nil { + return err + } + return ctrlErr +} + +func (s *linuxSpoofer) Inject(payload []byte) error { + frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, s.timestamp, nil, payload, s.ttl) + if err != nil { + return err + } + err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) + if err != nil { + return fmt.Errorf("sendto raw socket: %w", err) + } + return nil +} + +func (s *linuxSpoofer) Close() error { + if s.rawFD < 0 { + return nil + } + err := unix.Close(s.rawFD) + s.rawFD = -1 + return err +} diff --git a/transport/internet/finalmask/rawpacket/raw_stub.go b/transport/internet/finalmask/rawpacket/raw_stub.go new file mode 100644 index 000000000000..c06a40f48bb2 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/raw_stub.go @@ -0,0 +1,15 @@ +//go:build !linux && !darwin && !freebsd && !(windows && (amd64 || 386)) + +package rawpacket + +import ( + "net" + + "errors" +) + +const PlatformSupported = false + +func newRawSpoofer(conn net.Conn, method Method, ttl uint8) (rawSpoofer, error) { + return nil, errors.New("rawpacket: unsupported platform") +} diff --git a/transport/internet/finalmask/rawpacket/raw_unix.go b/transport/internet/finalmask/rawpacket/raw_unix.go new file mode 100644 index 000000000000..bccd0fefe0de --- /dev/null +++ b/transport/internet/finalmask/rawpacket/raw_unix.go @@ -0,0 +1,25 @@ +//go:build linux || darwin || freebsd + +package rawpacket + +import ( + "fmt" + "net/netip" + + "golang.org/x/sys/unix" +) + +func openIPv4RawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) { + fd, err := unix.Socket(unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, fmt.Errorf("open AF_INET SOCK_RAW: %w", err) + } + err = unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_HDRINCL, 1) + if err != nil { + unix.Close(fd) + return -1, nil, fmt.Errorf("set IP_HDRINCL: %w", err) + } + sockaddr := &unix.SockaddrInet4{Port: int(dst.Port())} + sockaddr.Addr = dst.Addr().As4() + return fd, sockaddr, nil +} diff --git a/transport/internet/finalmask/rawpacket/raw_windows.go b/transport/internet/finalmask/rawpacket/raw_windows.go new file mode 100644 index 000000000000..acfed30ff0c8 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/raw_windows.go @@ -0,0 +1,236 @@ +//go:build windows && (amd64 || 386) + +package rawpacket + +import ( + "errors" + "net" + "net/netip" + "slices" + "sync" + "sync/atomic" + "time" + + "github.com/xtls/xray-core/transport/internet/finalmask/rawpacket/windivert" + "golang.org/x/sys/windows" +) + +const PlatformSupported = true + +// closeGracePeriod caps how long Close() waits for the divert goroutine to +// observe the kernel-emitted real ClientHello and perform the reorder +// (fake → real). In practice this completes in microseconds; the cap +// bounds the pathological case where the kernel buffers the packet. +const closeGracePeriod = 2 * time.Second + +// windowsSpoofer uses a single WinDivert handle for both capture and +// injection. Sequential Send() calls on one handle traverse one driver queue, +// so the fake provably precedes the released real on the wire — a guarantee +// two separate handles cannot make because cross-handle order depends on the +// scheduler. +type windowsSpoofer struct { + method Method + src, dst netip.AddrPort + divertH *windivert.Handle + ttl uint8 + + fakeReady chan []byte // buffered(1): staged by Inject + done chan struct{} // closed by run() on exit + closeOnce sync.Once + runErr atomic.Pointer[error] +} + +func newRawSpoofer(conn net.Conn, method Method, ttl uint8) (rawSpoofer, error) { + _, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + filter, err := windivert.OutboundTCP(src, dst) + if err != nil { + return nil, err + } + divertH, err := windivert.Open(filter, windivert.LayerNetwork, 0, 0) + if err != nil { + return nil, err + } + s := &windowsSpoofer{ + method: method, + src: src, + dst: dst, + divertH: divertH, + ttl: ttl, + fakeReady: make(chan []byte, 1), + done: make(chan struct{}), + } + go s.run() + return s, nil +} + +func (s *windowsSpoofer) Inject(payload []byte) error { + select { + case s.fakeReady <- payload: + return nil + case <-s.done: + if p := s.runErr.Load(); p != nil { + return *p + } + return errors.New("rawpacket: spoofer closed before Inject") + } +} + +func (s *windowsSpoofer) Close() error { + s.closeOnce.Do(func() { + // Give run() a grace window to finish handling the real packet. + select { + case <-s.done: + case <-time.After(closeGracePeriod): + // Force Recv() to return by closing the divert handle. + s.divertH.Close() + <-s.done + } + }) + if p := s.runErr.Load(); p != nil { + return *p + } + return nil +} + +func (s *windowsSpoofer) recordErr(err error) { s.runErr.Store(&err) } + +func (s *windowsSpoofer) run() { + defer close(s.done) + defer s.divertH.Close() + + buf := make([]byte, windivert.MTUMax) + for { + n, addr, err := s.divertH.Recv(buf) + if err != nil { + if errors.Is(err, windows.ERROR_OPERATION_ABORTED) || + errors.Is(err, windows.ERROR_NO_DATA) { + return + } + s.recordErr(err) + return + } + pkt := buf[:n] + seq, ack, tcpOptions, payloadLen, ok := parseTCPPacket(pkt, addr.IPv6()) + if !ok { + // Our filter is OutboundTCP(src, dst); a non-TCP or truncated + // match means driver state is suspect. Re-inject so the kernel + // still sees the byte stream, then abort — continuing would risk + // reordering against an unknown reference point. + _, sendErr := s.divertH.Send(pkt, &addr) + if sendErr != nil { + s.recordErr(sendErr) + return + } + s.recordErr(errors.New("windivert received malformed packet matching spoof filter")) + return + } + if payloadLen == 0 { + // Handshake ACK, keepalive, FIN — pass through unchanged. + _, err := s.divertH.Send(pkt, &addr) + if err != nil { + s.recordErr(err) + return + } + continue + } + + // Non-empty outbound TCP payload = the real ClientHello. + var fake []byte + select { + case fake = <-s.fakeReady: + default: + // Inject() not yet called — pass through and keep observing. + _, err := s.divertH.Send(pkt, &addr) + if err != nil { + s.recordErr(err) + return + } + continue + } + + var timestamp uint32 + if tsVal, hasTS := ParseTCPOptions(tcpOptions); hasTS { + timestamp = tsVal + } + frame, err := buildSpoofFrame(s.method, s.src, s.dst, seq, ack, timestamp, tcpOptions, fake, s.ttl) + if err != nil { + s.recordErr(err) + return + } + fakeAddr := addr // inherit Outbound, IfIdx + // buildSpoofFrame emits ready-to-wire bytes. The driver recomputes + // checksums on Send when TCPChecksum/IPChecksum are 0 — which would + // overwrite the intentionally corrupt checksum in WrongChecksum mode. + // Force both to 1 to keep our bytes intact. + fakeAddr.SetIPChecksum(true) + fakeAddr.SetTCPChecksum(true) + _, err = s.divertH.Send(frame, &fakeAddr) + if err != nil { + s.recordErr(err) + return + } + _, err = s.divertH.Send(pkt, &addr) + if err != nil { + s.recordErr(err) + return + } + return // single-shot reorder complete + } +} + +func parseTCPPacket(pkt []byte, isV6 bool) (seq, ack uint32, options []byte, payloadLen int, ok bool) { + if isV6 { + if len(pkt) < IPv6MinimumSize+TCPMinimumSize { + return 0, 0, nil, 0, false + } + ip := IPv6(pkt) + if ip.TransportProtocol() != TCPProtocolNumber { + return 0, 0, nil, 0, false + } + tcp := TCP(pkt[IPv6MinimumSize:]) + tcpHdr := int(tcp.DataOffset()) + if tcpHdr < TCPMinimumSize || IPv6MinimumSize+tcpHdr > len(pkt) { + return 0, 0, nil, 0, false + } + total := IPv6MinimumSize + int(ip.PayloadLength()) + if total == IPv6MinimumSize || total > len(pkt) { + total = len(pkt) + } + if total < IPv6MinimumSize+tcpHdr { + return 0, 0, nil, 0, false + } + return tcp.SequenceNumber(), tcp.AckNumber(), slices.Clone(tcp.Options()), + total - IPv6MinimumSize - tcpHdr, true + } + if len(pkt) < IPv4MinimumSize+TCPMinimumSize { + return 0, 0, nil, 0, false + } + ip := IPv4(pkt) + if ip.Protocol() != TCPProtocolNumber { + return 0, 0, nil, 0, false + } + ihl := int(ip.HeaderLength()) + // ihl+TCPMinimumSize guards the TCP-header field reads below; without + // this, an IPv4 packet with options (ihl>20) against a 40-byte buffer + // reads past the TCP slice when calling DataOffset. + if ihl < IPv4MinimumSize || ihl+TCPMinimumSize > len(pkt) { + return 0, 0, nil, 0, false + } + tcp := TCP(pkt[ihl:]) + tcpHdr := int(tcp.DataOffset()) + if tcpHdr < TCPMinimumSize || ihl+tcpHdr > len(pkt) { + return 0, 0, nil, 0, false + } + total := int(ip.TotalLength()) + if total == 0 || total > len(pkt) { + total = len(pkt) + } + if total < ihl+tcpHdr { + return 0, 0, nil, 0, false + } + return tcp.SequenceNumber(), tcp.AckNumber(), slices.Clone(tcp.Options()), + total - ihl - tcpHdr, true +} diff --git a/transport/internet/finalmask/rawpacket/tcpip.go b/transport/internet/finalmask/rawpacket/tcpip.go new file mode 100644 index 000000000000..8814422e35ed --- /dev/null +++ b/transport/internet/finalmask/rawpacket/tcpip.go @@ -0,0 +1,155 @@ +package rawpacket + +import ( + "encoding/binary" + "net/netip" +) + +const ( + IPv4MinimumSize = 20 + IPv6MinimumSize = 40 + TCPMinimumSize = 20 + TCPProtocolNumber = 6 + + TCPOptionEOL = 0 + TCPOptionNOP = 1 + TCPOptionTS = 8 + TCPOptionTSLength = 10 + + TCPFlagFin = 0x01 + TCPFlagSyn = 0x02 + TCPFlagRst = 0x04 + TCPFlagPsh = 0x08 + TCPFlagAck = 0x10 +) + +func Checksum(data []byte, initial uint16) uint16 { + var csum uint32 = uint32(initial) + for i := 0; i < len(data)-1; i += 2 { + csum += uint32(binary.BigEndian.Uint16(data[i:])) + } + if len(data)%2 == 1 { + csum += uint32(data[len(data)-1]) << 8 + } + for csum > 0xffff { + csum = (csum >> 16) + (csum & 0xffff) + } + return uint16(csum) +} + +func PseudoHeaderChecksum(protocol uint8, srcAddr, dstAddr []byte, totalLen uint16) uint16 { + var csum uint32 + for i := 0; i < len(srcAddr); i += 2 { + csum += uint32(binary.BigEndian.Uint16(srcAddr[i:])) + } + for i := 0; i < len(dstAddr); i += 2 { + csum += uint32(binary.BigEndian.Uint16(dstAddr[i:])) + } + csum += uint32(protocol) + csum += uint32(totalLen) + for csum > 0xffff { + csum = (csum >> 16) + (csum & 0xffff) + } + return uint16(csum) +} + +func CombineChecksum(c1, c2 uint16) uint16 { + csum := uint32(c1) + uint32(c2) + for csum > 0xffff { + csum = (csum >> 16) + (csum & 0xffff) + } + return uint16(csum) +} + +func EncodeTSOption(val uint32, ecr uint32, b []byte) { + b[0] = TCPOptionTS + b[1] = TCPOptionTSLength + binary.BigEndian.PutUint32(b[2:], val) + binary.BigEndian.PutUint32(b[6:], ecr) +} + +func ParseTCPOptions(b []byte) (tsVal uint32, hasTS bool) { + for i := 0; i < len(b); { + if b[i] == TCPOptionEOL { + break + } + if b[i] == TCPOptionNOP { + i++ + continue + } + if i+1 >= len(b) { + break + } + optLen := int(b[i+1]) + if optLen < 2 || i+optLen > len(b) { + break + } + if b[i] == TCPOptionTS && optLen == TCPOptionTSLength { + return binary.BigEndian.Uint32(b[i+2:]), true + } + i += optLen + } + return 0, false +} + +// IPv4 header representation +type IPv4 []byte + +func (b IPv4) TotalLength() uint16 { return binary.BigEndian.Uint16(b[2:]) } +func (b IPv4) Flags() uint8 { return uint8(binary.BigEndian.Uint16(b[6:]) >> 13) } +func (b IPv4) FragmentOffset() uint16 { return binary.BigEndian.Uint16(b[6:]) & 0x1fff } +func (b IPv4) Protocol() uint8 { return b[9] } +func (b IPv4) HeaderLength() uint8 { return (b[0] & 0x0f) * 4 } + +func (b IPv4) Encode(totalLength uint16, id uint16, ttl uint8, protocol uint8, src, dst netip.Addr) { + b[0] = (4 << 4) | 5 // IPv4, Header Length = 20 + b[1] = 0 // TOS + binary.BigEndian.PutUint16(b[2:], totalLength) + binary.BigEndian.PutUint16(b[4:], id) + binary.BigEndian.PutUint16(b[6:], 0) // Flags and Fragment Offset + b[8] = ttl + b[9] = protocol + b[10] = 0 // Checksum (0 for calculation) + copy(b[12:16], src.AsSlice()) + copy(b[16:20], dst.AsSlice()) + csum := Checksum(b[:20], 0) + binary.BigEndian.PutUint16(b[10:], ^csum) +} + +type IPv6 []byte + +func (b IPv6) PayloadLength() uint16 { return binary.BigEndian.Uint16(b[4:]) } +func (b IPv6) TransportProtocol() uint8 { return b[6] } + +func (b IPv6) Encode(payloadLength uint16, transportProtocol uint8, hopLimit uint8, src, dst netip.Addr) { + binary.BigEndian.PutUint32(b[0:], 6<<28) // Version 6, Traffic Class 0, Flow Label 0 + binary.BigEndian.PutUint16(b[4:], payloadLength) + b[6] = transportProtocol + b[7] = hopLimit + copy(b[8:24], src.AsSlice()) + copy(b[24:40], dst.AsSlice()) +} + +type TCP []byte + +func (b TCP) DataOffset() uint8 { return (b[12] >> 4) * 4 } +func (b TCP) SequenceNumber() uint32 { return binary.BigEndian.Uint32(b[4:]) } +func (b TCP) AckNumber() uint32 { return binary.BigEndian.Uint32(b[8:]) } +func (b TCP) Options() []byte { return b[TCPMinimumSize:b.DataOffset()] } +func (b TCP) SetChecksum(csum uint16) { binary.BigEndian.PutUint16(b[16:], csum) } + +func (b TCP) Encode(srcPort, dstPort uint16, seqNum, ackNum uint32, dataOffset uint8, flags uint8, windowSize uint16) { + binary.BigEndian.PutUint16(b[0:], srcPort) + binary.BigEndian.PutUint16(b[2:], dstPort) + binary.BigEndian.PutUint32(b[4:], seqNum) + binary.BigEndian.PutUint32(b[8:], ackNum) + b[12] = (dataOffset / 4) << 4 + b[13] = flags + binary.BigEndian.PutUint16(b[14:], windowSize) + b[16] = 0 // Checksum + binary.BigEndian.PutUint16(b[18:], 0) // Urgent pointer +} + +func (b TCP) CalculateChecksum(initial uint16) uint16 { + return Checksum(b, initial) +} diff --git a/transport/internet/finalmask/rawpacket/windivert/assets/LICENSE.txt b/transport/internet/finalmask/rawpacket/windivert/assets/LICENSE.txt new file mode 100644 index 000000000000..8489a8e773c3 --- /dev/null +++ b/transport/internet/finalmask/rawpacket/windivert/assets/LICENSE.txt @@ -0,0 +1,1191 @@ +WinDivert is dual-licensed under your choice of the GNU Lesser General Public +License (LGPL) Version 3 or the GNU General Public License (GPL) Version 2. +Copies of the LGPLv3, GPLv3 and GPLv2 are provided below. + +============================================================================== + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + +============================================================================== + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + +============================================================================== + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + diff --git a/transport/internet/finalmask/rawpacket/windivert/assets/WinDivert32.sys b/transport/internet/finalmask/rawpacket/windivert/assets/WinDivert32.sys new file mode 100644 index 0000000000000000000000000000000000000000..d06738cbb78351cc57754fd484b77fac0df52cea GIT binary patch literal 79792 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%0DB0KXuLbP(03=gskd! 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�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%0A~=3F;g8l!;0U`QAv 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 E0GFf)F8}}l literal 0 HcmV?d00001 diff --git a/transport/internet/finalmask/rawpacket/windivert/assets/WinDivert64.sys b/transport/internet/finalmask/rawpacket/windivert/assets/WinDivert64.sys new file mode 100644 index 0000000000000000000000000000000000000000..218ccaf423ef0a67696226f9ef3a09149e4441d0 GIT binary patch literal 94144 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 za=me