package udp import ( "bytes" "encoding/binary" "fmt" "testing" "github.com/apernet/OpenGFW/analyzer" "github.com/apernet/OpenGFW/analyzer/tcp" ) func etVarint(v uint64) []byte { var out []byte for { b := byte(v & 0x7f) v >>= 7 if v != 0 { b |= 0x80 } out = append(out, b) if v == 0 { return out } } } func etProtoVarint(field uint32, v uint64) []byte { return append(etVarint(uint64(field)<<3), etVarint(v)...) } func etProtoBytes(field uint32, v []byte) []byte { out := etVarint(uint64(field)<<3 | 2) out = append(out, etVarint(uint64(len(v)))...) return append(out, v...) } // etHandshakeRequest HandshakeRequest,按 send_handshake 的构造方式 func etHandshakeRequest(peerID uint32, networkName string) []byte { var out []byte out = append(out, etProtoVarint(1, easyTierHandshakeMagic)...) out = append(out, etProtoVarint(2, uint64(peerID))...) out = append(out, etProtoVarint(3, 1)...) out = append(out, etProtoBytes(4, []byte("liveness-echo-v1"))...) out = append(out, etProtoBytes(5, []byte(networkName))...) out = append(out, etProtoBytes(6, make([]byte, easyTierSecretDigestLen))...) return out } // etNoiseMsg1 Noise XX 先写的临时公钥,后面跟 PeerConnNoiseMsg1Pb func etNoiseMsg1(networkName string) []byte { out := bytes.Repeat([]byte{0xab}, easyTierNoiseEphemeralLen) out = append(out, etProtoVarint(1, 1)...) return append(out, etProtoBytes(2, []byte(networkName))...) } // etBody 帧体:16 字节 PeerManagerHeader 加载荷 func etBody(packetType uint8, from, to uint32, payload []byte) []byte { return etBodyRaw(packetType, from, to, 0, 1, 0, uint32(len(payload)), payload) } func etBodyRaw(packetType uint8, from, to uint32, flags, forwardCounter, reserved uint8, declaredLen uint32, payload []byte) []byte { out := make([]byte, 0, easyTierPeerManagerHeaderSize+len(payload)) out = binary.LittleEndian.AppendUint32(out, from) out = binary.LittleEndian.AppendUint32(out, to) out = append(out, packetType, flags, forwardCounter, reserved) out = binary.LittleEndian.AppendUint32(out, declaredLen) return append(out, payload...) } // etDatagram 8 字节 UDPTunnelHeader 加帧体 func etDatagram(connID uint32, msgType uint8, body []byte) []byte { out := make([]byte, 0, easyTierUDPTunnelHeaderSize+len(body)) out = binary.LittleEndian.AppendUint32(out, connID) out = append(out, msgType, 0) out = binary.LittleEndian.AppendUint16(out, uint16(len(body))) return append(out, body...) } func etSyn(connID uint32, magic uint64) []byte { return etDatagram(connID, easyTierMsgSyn, binary.LittleEndian.AppendUint64(nil, magic)) } func etSack(connID uint32, magic uint64) []byte { return etDatagram(connID, easyTierMsgSack, binary.LittleEndian.AppendUint64(nil, magic)) } // etSegment 4 字节 TCPTunnelHeader 加帧体 func etSegment(body []byte) []byte { out := binary.LittleEndian.AppendUint32(nil, uint32(len(body))) return append(out, body...) } func etUDP() analyzer.UDPStream { return (&EasyTierAnalyzer{}).NewUDP(analyzer.UDPInfo{}, nil) } func etTCP() analyzer.TCPStream { return (&EasyTierAnalyzer{}).NewTCP(analyzer.TCPInfo{}, nil) } func etFeedUDP(s analyzer.UDPStream, rev bool, data []byte) (analyzer.PropMap, bool) { u, done := s.Feed(rev, data) if u == nil { return analyzer.PropMap{}, done } return u.M, done } func etFeedTCP(s analyzer.TCPStream, rev bool, data []byte) (analyzer.PropMap, bool) { u, done := s.Feed(rev, false, false, 0, data) if u == nil { return analyzer.PropMap{}, done } return u.M, done } func etText(m analyzer.PropMap, key string) string { v, ok := m[key] if !ok { return "" } return fmt.Sprint(v) } func etFlag(m analyzer.PropMap, key string) bool { v, _ := m[key].(bool) return v } func etExpect(t *testing.T, m analyzer.PropMap, key, want string) { t.Helper() if got := etText(m, key); got != want { t.Fatalf("%s = %q, want %q (props: %v)", key, got, want, m) } } func TestEasyTier_HandshakeMagicEncodesToDocumentedBytes(t *testing.T) { want := []byte{0x08, 0xe1, 0xcb, 0x86, 0x8f, 0x0d} if got := etProtoVarint(1, easyTierHandshakeMagic); !bytes.Equal(got, want) { t.Fatalf("magic encodes to % x, want % x", got, want) } } func TestEasyTier_UDPHandshakeNamesTheMeshOnFirstDatagram(t *testing.T) { payload := etHandshakeRequest(0x1a2b3c4d, "office-mesh") m, done := etFeedUDP(etUDP(), false, etDatagram(7, easyTierMsgData, etBody(easyTierPktHandshake, 0x1a2b3c4d, 0, payload))) if done { t.Fatal("stream done after a single handshake") } if !etFlag(m, "yes") { t.Fatal("the magic alone should settle it") } if !etFlag(m, "magic_ok") { t.Fatal("magic_ok not set") } etExpect(t, m, "network_name", "office-mesh") etExpect(t, m, "version", "1") etExpect(t, m, "transport", "udp") etExpect(t, m, "msg_type_name", "data") etExpect(t, m, "packet_type_name", "handshake") etExpect(t, m, "from_peer_id", "439041101") etExpect(t, m, "to_peer_id", "0") etExpect(t, m, "secure", "false") } func TestEasyTier_TCPHandshakeNamesTheMeshOnFirstFrame(t *testing.T) { payload := etHandshakeRequest(9, "office-mesh") m, done := etFeedTCP(etTCP(), false, etSegment(etBody(easyTierPktHandshake, 9, 0, payload))) if done { t.Fatal("stream done after a single handshake") } if !etFlag(m, "yes") { t.Fatal("yes not set") } etExpect(t, m, "network_name", "office-mesh") etExpect(t, m, "transport", "tcp") etExpect(t, m, "tx_pkt_cnt", "1") } func TestEasyTier_HandshakeWithWrongMagicIsNotConclusive(t *testing.T) { payload := append(etProtoVarint(1, 0xd1e1a5e0), etProtoBytes(5, []byte("office-mesh"))...) m, _ := etFeedUDP(etUDP(), false, etDatagram(7, easyTierMsgData, etBody(easyTierPktHandshake, 1, 0, payload))) // 帧结构仍然自洽,字段照发;但一帧不够,而且对网络不做任何声明 if etFlag(m, "yes") { t.Fatal("yes set on a wrong magic") } etExpect(t, m, "network_name", "") etExpect(t, m, "magic_ok", "") } func TestEasyTier_HandshakeWithWrongDigestWidthIsRejected(t *testing.T) { // 上游这种情况会拒连,报匹配等于报一个根本连不起来的 mesh 名 payload := etProtoVarint(1, easyTierHandshakeMagic) payload = append(payload, etProtoBytes(5, []byte("office-mesh"))...) payload = append(payload, etProtoBytes(6, make([]byte, 16))...) m, _ := etFeedUDP(etUDP(), false, etDatagram(7, easyTierMsgData, etBody(easyTierPktHandshake, 1, 0, payload))) if etFlag(m, "yes") { t.Fatal("yes set on a wrong digest width") } etExpect(t, m, "network_name", "") } func TestEasyTier_NoiseFirstMessageStillGivesUpTheNetworkName(t *testing.T) { // Noise XX 写 msg1 时还没有密钥,临时公钥后面的载荷是明文 s := etUDP() m, _ := etFeedUDP(s, false, etDatagram(7, easyTierMsgData, etBody(easyTierPktNoiseMsg1, 5, 0, etNoiseMsg1("secure-mesh")))) if !etFlag(m, "secure") { t.Fatal("secure not set") } etExpect(t, m, "network_name", "secure-mesh") if etFlag(m, "yes") { t.Fatal("one frame with no magic to check should not settle it") } // 反方向再来一帧就到阈值了 m, _ = etFeedUDP(s, true, etDatagram(7, easyTierMsgData, etBody(14, 6, 5, bytes.Repeat([]byte{0xcd}, 48)))) if !etFlag(m, "yes") { t.Fatal("two consistent frames should settle it") } etExpect(t, m, "packet_type_name", "noise_handshake_msg2") } func TestEasyTier_OpaqueNoiseMessageSaysNothingAboutTheNetwork(t *testing.T) { m, _ := etFeedUDP(etUDP(), false, etDatagram(7, easyTierMsgData, etBody(easyTierPktNoiseMsg1, 5, 0, bytes.Repeat([]byte{0x5a}, 96)))) if !etFlag(m, "secure") { t.Fatal("secure not set") } etExpect(t, m, "network_name", "") } func TestEasyTier_SynAnsweredByItsSackSettlesTheFlow(t *testing.T) { s := etUDP() m, _ := etFeedUDP(s, false, etSyn(0x12345678, 0x0102030405060708)) if etFlag(m, "yes") { t.Fatal("a lone syn is 16 bytes of nearly nothing") } etExpect(t, m, "msg_type_name", "syn") m, _ = etFeedUDP(s, true, etSack(0x12345678, 0x0102030405060708)) if !etFlag(m, "yes") { t.Fatal("a matching sack should settle it") } etExpect(t, m, "msg_type_name", "sack") etExpect(t, m, "conn_id", "305419896") etExpect(t, m, "tx_pkt_cnt", "1") etExpect(t, m, "rx_pkt_cnt", "1") } func TestEasyTier_SackEchoingTheWrongMagicSettlesNothing(t *testing.T) { s := etUDP() etFeedUDP(s, false, etSyn(0x12345678, 1)) m, _ := etFeedUDP(s, true, etSack(0x12345678, 2)) if etFlag(m, "yes") { t.Fatal("yes set on a mismatched magic") } } func TestEasyTier_SackDownTheSameDirectionSettlesNothing(t *testing.T) { s := etUDP() etFeedUDP(s, false, etSyn(9, 42)) m, _ := etFeedUDP(s, false, etSack(9, 42)) if etFlag(m, "yes") { t.Fatal("yes set on a same-direction sack") } } func TestEasyTier_HolePunchProbesAreReportedButDoNotSettleTheFlow(t *testing.T) { // 往可能根本没跑 EasyTier 的地址上喷的,规则得点名才拦 s := etUDP() var m analyzer.PropMap for i := 0; i < 3; i++ { m, _ = etFeedUDP(s, false, etDatagram(1, easyTierMsgHolePunch, bytes.Repeat([]byte{0x7f}, 16))) } if etFlag(m, "yes") { t.Fatal("hole punches settled the flow") } etExpect(t, m, "msg_type_name", "hole_punch") } func TestEasyTier_BothHolePunchBodyLengthsAreRecognised(t *testing.T) { // 连通性路径 16 字节,UDP 会话层 32 字节;只有长度能把它们和噪声分开,两个都得列 for _, n := range []int{16, 32} { m, _ := etFeedUDP(etUDP(), false, etDatagram(1, easyTierMsgHolePunch, bytes.Repeat([]byte{0x7f}, n))) if got := etText(m, "msg_type_name"); got != "hole_punch" { t.Fatalf("body length %d: msg_type_name = %q", n, got) } } m, _ := etFeedUDP(etUDP(), false, etDatagram(1, easyTierMsgHolePunch, bytes.Repeat([]byte{0x7f}, 24))) etExpect(t, m, "msg_type_name", "") } func TestEasyTier_DirectedHolePunchMustEchoTheDestinationPortInConnID(t *testing.T) { punch := []byte{203, 0, 113, 9} punch = binary.LittleEndian.AppendUint16(punch, 11010) m, _ := etFeedUDP(etUDP(), false, etDatagram(11010, easyTierMsgV4HolePunch, punch)) etExpect(t, m, "msg_type_name", "v4_hole_punch") m, done := etFeedUDP(etUDP(), false, etDatagram(11011, easyTierMsgV4HolePunch, punch)) etExpect(t, m, "msg_type_name", "") if done { t.Fatal("one bad datagram is not enough to give up") } } func TestEasyTier_UDPLengthFieldMustMatchTheDatagram(t *testing.T) { wrong := etSyn(1, 2) wrong[6] = 9 // 声明 9,体是 8 m, _ := etFeedUDP(etUDP(), false, wrong) etExpect(t, m, "msg_type_name", "") } func TestEasyTier_UDPPaddingByteMustBeZero(t *testing.T) { wrong := etSyn(1, 2) wrong[5] = 1 m, _ := etFeedUDP(etUDP(), false, wrong) etExpect(t, m, "msg_type_name", "") } func TestEasyTier_FourUnparseableDatagramsEndAUDPFlow(t *testing.T) { s := etUDP() for i := 0; i < 3; i++ { if _, done := etFeedUDP(s, false, []byte("not easytier at all")); done { t.Fatalf("gave up after %d datagrams", i+1) } } if _, done := etFeedUDP(s, false, []byte("not easytier at all")); !done { t.Fatal("did not give up after four unparseable datagrams") } } func TestEasyTier_TwoLengthFieldsOfATCPFrameMustAgree(t *testing.T) { // 16 字节头后面声明载荷 4,实际到了 8 frame := etBodyRaw(1, 1, 2, 0, 1, 0, 4, bytes.Repeat([]byte{0xaa}, 8)) m, done := etFeedTCP(etTCP(), false, etSegment(frame)) if !done { t.Fatal("stream not done on a length mismatch") } etExpect(t, m, "packet_type_name", "") } func TestEasyTier_AEADTailIsAllowedToOutrunTheDeclaredLength(t *testing.T) { // 加密器追加 16 字节 tag 和 12 字节 nonce,不改头里声明的长度 payload := bytes.Repeat([]byte{0x11}, 40+easyTierAEADTailSize) frame := etBodyRaw(1, 1, 2, easyTierFlagEncrypted, 1, 0, 40, payload) m, _ := etFeedTCP(etTCP(), false, etSegment(frame)) if !etFlag(m, "encrypted") { t.Fatal("encrypted not set") } etExpect(t, m, "payload_len", "40") } func TestEasyTier_CompressedPayloadMayComeInUnderTheDeclaredLength(t *testing.T) { frame := etBodyRaw(1, 1, 2, easyTierFlagCompressed, 1, 0, 900, bytes.Repeat([]byte{0x22}, 61)) m, _ := etFeedTCP(etTCP(), false, etSegment(frame)) if !etFlag(m, "compressed") { t.Fatal("compressed not set") } etExpect(t, m, "payload_len", "900") } func TestEasyTier_UnencryptedPayloadMustMatchTheDeclaredLengthExactly(t *testing.T) { frame := etBodyRaw(1, 1, 2, 0, 1, 0, 40, bytes.Repeat([]byte{0x11}, 40+easyTierAEADTailSize)) if _, done := etFeedTCP(etTCP(), false, etSegment(frame)); !done { t.Fatal("stream not done on an unencrypted length mismatch") } } func TestEasyTier_ReservedByteIsZeroUnlessALivenessFlagIsSet(t *testing.T) { withFlag := etBodyRaw(1, 1, 2, 0b0010_0000, 1, 0x5c, 4, make([]byte, 4)) m, _ := etFeedTCP(etTCP(), false, etSegment(withFlag)) etExpect(t, m, "packet_type_name", "data") withoutFlag := etBodyRaw(1, 1, 2, 0, 1, 0x5c, 4, make([]byte, 4)) m, done := etFeedTCP(etTCP(), false, etSegment(withoutFlag)) if !done { t.Fatal("stream not done on a non-zero reserved byte") } etExpect(t, m, "packet_type_name", "") } func TestEasyTier_ForwardCounterPastTheRoutersOwnCapIsRejected(t *testing.T) { frame := etBodyRaw(1, 1, 2, 0, 9, 0, 4, make([]byte, 4)) m, done := etFeedTCP(etTCP(), false, etSegment(frame)) if !done { t.Fatal("stream not done on forward counter 9") } etExpect(t, m, "packet_type_name", "") } func TestEasyTier_UnnamedPacketTypeIsRejected(t *testing.T) { frame := etBodyRaw(22, 1, 2, 0, 1, 0, 4, make([]byte, 4)) if _, done := etFeedTCP(etTCP(), false, etSegment(frame)); !done { t.Fatal("stream not done on packet type 22") } } func TestEasyTier_FrameSplitAcrossSegmentsIsReassembled(t *testing.T) { wire := etSegment(etBody(easyTierPktHandshake, 9, 0, etHandshakeRequest(9, "office-mesh"))) s := etTCP() m, done := etFeedTCP(s, false, wire[:3]) if done { t.Fatal("gave up on a length field that had not fully arrived") } etExpect(t, m, "packet_type_name", "") m, done = etFeedTCP(s, false, wire[3:]) if done { t.Fatal("stream done after a valid handshake") } if !etFlag(m, "yes") { t.Fatal("yes not set after reassembly") } etExpect(t, m, "network_name", "office-mesh") } func TestEasyTier_ByteAtATimeDeliveryStillReadsTheFrame(t *testing.T) { wire := etSegment(etBody(easyTierPktHandshake, 9, 0, etHandshakeRequest(9, "mesh"))) s := etTCP() last := analyzer.PropMap{} for _, b := range wire { m, done := etFeedTCP(s, false, []byte{b}) if done { t.Fatal("stream done mid-frame") } if len(m) != 0 { last = m } } if !etFlag(last, "yes") { t.Fatal("yes not set after byte-at-a-time delivery") } etExpect(t, last, "network_name", "mesh") } func TestEasyTier_SeveralFramesPipelinedIntoOneSegmentAreAllRead(t *testing.T) { wire := etSegment(etBody(1, 9, 8, bytes.Repeat([]byte{0x01}, 32))) wire = append(wire, etSegment(etBody(4, 9, 8, nil))...) wire = append(wire, etSegment(etBody(1, 9, 8, bytes.Repeat([]byte{0x03}, 64)))...) m, done := etFeedTCP(etTCP(), false, wire) if done { t.Fatal("stream done after valid frames") } if !etFlag(m, "yes") { t.Fatal("yes not set after three consistent frames") } etExpect(t, m, "tx_pkt_cnt", "3") // map 描述的是最新一帧 etExpect(t, m, "payload_len", "64") } func TestEasyTier_NetworkNameSurvivesLaterFrames(t *testing.T) { // 只发一次。按 mesh 名写的规则得在握手滚过去之后还能用 s := etTCP() etFeedTCP(s, false, etSegment(etBody(easyTierPktHandshake, 9, 0, etHandshakeRequest(9, "office-mesh")))) m, _ := etFeedTCP(s, false, etSegment(etBody(1, 9, 8, bytes.Repeat([]byte{0xee}, 128)))) etExpect(t, m, "network_name", "office-mesh") etExpect(t, m, "packet_type_name", "data") } func TestEasyTier_GapInTheTCPStreamEndsIt(t *testing.T) { u, done := etTCP().Feed(false, false, false, 7, []byte("whatever")) if u != nil { t.Fatal("update published on a gap") } if !done { t.Fatal("stream not done on a gap") } } func TestEasyTier_OrdinaryTrafficIsNotMistakenForATunnel(t *testing.T) { m, done := etFeedTCP(etTCP(), false, []byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")) if !done || etFlag(m, "yes") { t.Fatalf("HTTP: done=%v yes=%v", done, etFlag(m, "yes")) } // TLS ClientHello 记录:16 03 01 ... 读出来是 21 MB 的帧体长度,直接拒绝 m, done = etFeedTCP(etTCP(), false, []byte{0x16, 0x03, 0x01, 0x01, 0x4a, 0x01, 0x00, 0x01, 0x46}) if !done || etFlag(m, "yes") { t.Fatalf("TLS: done=%v yes=%v", done, etFlag(m, "yes")) } } func TestEasyTier_FETDoesNotCatchTheTCPHandshake(t *testing.T) { // fet 只看客户端第一个分段,判完就 done。EasyTier 连接的第一个分段永远是明文握手, // 整条连接上唯一低熵的东西,所以 fet 读作"非全加密",引擎放行,后面真正加密的帧再也不看。 // 而且能不能中取决于握手里碰巧有多少随机内容(ex1 落在 3.4 豁免线附近,随网络名长短摇摆), // 一个随运营者起的名字长短而变的检测器不是检测器。 digest := make([]byte, 32) for i := range digest { digest[i] = byte(uint32(i) * 2654435761) } payload := etProtoVarint(1, easyTierHandshakeMagic) payload = append(payload, etProtoVarint(2, 0x1a2b3c4d)...) payload = append(payload, etProtoVarint(3, 1)...) payload = append(payload, etProtoBytes(4, []byte("liveness-echo-v1"))...) payload = append(payload, etProtoBytes(5, []byte("mesh"))...) payload = append(payload, etProtoBytes(6, digest)...) wire := etSegment(etBody(easyTierPktHandshake, 0x1a2b3c4d, 0, payload)) fet := (&tcp.FETAnalyzer{}).NewTCP(analyzer.TCPInfo{}, nil) u, done := fet.Feed(false, true, false, 0, wire) if !done { t.Fatal("fet should decide on the first segment and not look again") } if u != nil && etFlag(u.M, "yes") { t.Fatal("fet claimed this handshake, so this analyzer would be redundant") } // 同样的字节,给本分析器 m, _ := etFeedTCP(etTCP(), false, wire) if !etFlag(m, "yes") { t.Fatal("easytier did not claim its own handshake") } etExpect(t, m, "network_name", "mesh") } func TestEasyTier_WireGuardAnalyzerDoesNotClaimUDPTransport(t *testing.T) { // wireguard 覆盖的是 EasyTier 的 wg:// 监听,六分之一。udp:// 上没有任何东西读起来像 WireGuard 报文 datagrams := [][]byte{ etSyn(0x12345678, 0x0102030405060708), etDatagram(7, easyTierMsgData, etBody(easyTierPktHandshake, 0x1a2b3c4d, 0, etHandshakeRequest(9, "mesh"))), etDatagram(1, easyTierMsgHolePunch, bytes.Repeat([]byte{0x7f}, 16)), } for i, data := range datagrams { wg := (&WireGuardAnalyzer{}).NewUDP(analyzer.UDPInfo{}, nil) if u, _ := wg.Feed(false, data); u != nil { t.Fatalf("wireguard claimed easytier datagram %d: %v", i, u.M) } } } func TestEasyTier_STUNOnTheSameSocketIsNeitherEvidenceNorHeldAgainstTheFlow(t *testing.T) { // EasyTier 把 STUN 复用在自己的 UDP socket 上,并在自己的帧解析之前分流;四个 STUN 不能结束流 stun := append([]byte{0x00, 0x01, 0x00, 0x00, 0x21, 0x12, 0xa4, 0x42}, bytes.Repeat([]byte{0x11}, 12)...) s := etUDP() for i := 0; i < 8; i++ { m, done := etFeedUDP(s, false, stun) if done { t.Fatalf("STUN datagram %d ended the flow", i+1) } if len(m) != 0 { t.Fatalf("STUN datagram %d published props: %v", i+1, m) } } m, _ := etFeedUDP(s, false, etSyn(1, 2)) etExpect(t, m, "msg_type_name", "syn") }