Skip to content

Commit 8fa4457

Browse files
committed
Teredo: Parse teredo data from IPv6 packet
1 parent 2524248 commit 8fa4457

4 files changed

Lines changed: 148 additions & 5 deletions

File tree

tests/conftest.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Dict
22
import os
33
import pytest
4+
from binascii import unhexlify
45

56
@pytest.fixture(scope='session')
67
def test_data() -> Dict[str, bytes]:
@@ -10,4 +11,31 @@ def test_data() -> Dict[str, bytes]:
1011
with open(os.path.join(data_path, f), 'rb') as fh:
1112
data[f] = fh.read()
1213

13-
return data
14+
return data
15+
16+
@pytest.fixture(scope='session')
17+
def teredo_packet() -> bytes:
18+
"""
19+
Teredo IPv6 over UDP tunneling
20+
Internet Protocol Version 6, Src: 2001:0:338c:24f4:1c38:f3fd:d2f3:c93d, Dst: 2001:0:338c:24f4:43b:30e3:d2f3:c93d
21+
0110 .... = Version: 6
22+
.... 0000 0000 .... .... .... .... .... = Traffic Class: 0x00 (DSCP: CS0, ECN: Not-ECT)
23+
.... 0000 00.. .... .... .... .... .... = Differentiated Services Codepoint: Default (0)
24+
.... .... ..00 .... .... .... .... .... = Explicit Congestion Notification: Not ECN-Capable Transport (0)
25+
.... .... .... 0000 0000 0000 0000 0000 = Flow Label: 0x00000
26+
Payload Length: 0
27+
Next Header: No Next Header for IPv6 (59)
28+
Hop Limit: 21
29+
Source Address: 2001:0:338c:24f4:1c38:f3fd:d2f3:c93d
30+
Destination Address: 2001:0:338c:24f4:43b:30e3:d2f3:c93d
31+
[Source Teredo Server IPv4: 51.140.36.244]
32+
[Source Teredo Port: 3074]
33+
[Source Teredo Client IPv4: 45.12.54.194]
34+
[Destination Teredo Server IPv4: 51.140.36.244]
35+
[Destination Teredo Port: 53020]
36+
[Destination Teredo Client IPv4: 45.12.54.194]
37+
"""
38+
return unhexlify(
39+
'6000000000003b1520010000338c24f41c38f3fdd2f3c93d20010000'
40+
'338c24f4043b30e3d2f3c93d01049eb8960803080000c0a889db0c02'
41+
)

tests/test_teredo.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from binascii import unhexlify
2+
from xcloud.protocol import teredo
3+
4+
def test_teredo_convert(teredo_packet: bytes):
5+
parsed = teredo.TeredoPacket.parse(teredo_packet)
6+
7+
# Ipv6
8+
assert parsed.ipv6.version == 6
9+
assert parsed.ipv6.traffic_cls == 0x00
10+
assert parsed.ipv6.flow_label == 0x00
11+
assert parsed.ipv6.payload_len == 0x00
12+
assert parsed.ipv6.next_header == 59 # No next header
13+
assert parsed.ipv6.hop_limit == 21
14+
assert parsed.ipv6.src == unhexlify('20010000338c24f41c38f3fdd2f3c93d')
15+
assert parsed.ipv6.dst == unhexlify('20010000338c24f4043b30e3d2f3c93d')
16+
17+
# Teredo source
18+
assert parsed.src_teredo.udp_port == 3074
19+
assert parsed.src_teredo.flags == 0x1c38
20+
assert str(parsed.src_teredo.teredo_server_ipv4) == '51.140.36.244'
21+
assert str(parsed.src_teredo.client_pub_ipv4) == '45.12.54.194'
22+
23+
# Teredo destination
24+
assert parsed.dst_teredo.udp_port == 53020
25+
assert parsed.dst_teredo.flags == 0x43b
26+
assert str(parsed.dst_teredo.teredo_server_ipv4) == '51.140.36.244'
27+
assert str(parsed.dst_teredo.client_pub_ipv4) == '45.12.54.194'
28+
29+

xcloud/protocol/ipv6.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,45 @@ def __init__(
77
) -> None:
88
self.ipv6_base = ipv6_base
99

10+
@property
11+
def version(self) -> int:
12+
return self.ipv6_base.v
13+
14+
@property
15+
def traffic_cls(self) -> int:
16+
return self.ipv6_base.fc
17+
18+
@property
19+
def flow_label(self) -> int:
20+
return self.ipv6_base.flow
21+
22+
@property
23+
def payload_len(self) -> int:
24+
return self.ipv6_base.plen
25+
26+
@property
27+
def next_header(self) -> int:
28+
return self.ipv6_base.nxt
29+
30+
@property
31+
def hop_limit(self) -> int:
32+
return self.ipv6_base.hlim
33+
34+
@property
35+
def src(self) -> bytes:
36+
return self.ipv6_base.src
37+
38+
@property
39+
def dst(self) -> bytes:
40+
return self.ipv6_base.dst
41+
1042
@property
1143
def data(self) -> bytes:
1244
return self.ipv6_base.data
1345

1446
def __repr__(self):
1547
return (
16-
f"IPv6Packet(V={self.ipv6_base.v}, SRC={self.ipv6_base.src}, DST={self.ipv6_base.dst}, PLEN={self.ipv6_base.plen} NEXT={self.ipv6_base.nxt} HLIM={self.ipv6_base.hlim}) DATA={self.data}"
48+
f"IPv6Packet(V={self.version}, SRC={self.src}, DST={self.dst}, PLEN={self.payload_len} NEXT={self.next_header} HLIM={self.hop_limit}) DATA={self.data}"
1749
)
1850

1951
@classmethod

xcloud/protocol/teredo.py

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,74 @@
22
from dataclasses import dataclass, field
33
from struct import pack, unpack, unpack_from
44
from typing import Any, List, Optional, Tuple, Union
5+
from ipaddress import IPv4Address, IPv6Address
56

67
from . import ipv6
78

89
TEREDO_PORT = 3544
910
TEREDO_HEADER_LENGTH = 22
1011

12+
13+
@dataclass
14+
class TeredoEndpoint:
15+
teredo_server_ipv4: IPv4Address
16+
flags: int
17+
udp_port: int
18+
client_pub_ipv4: IPv4Address
19+
20+
21+
def convert_teredo_addr_to_endpoint(
22+
teredo_addr: bytes
23+
) -> TeredoEndpoint:
24+
"""
25+
0x00-0x04 Prefix // 32 bits
26+
0x04-0x08 Teredo server IPv4 // 32 bits
27+
0x08-0x0A Flags // 16 bits
28+
0x0A-0x0C UDP Port // 16 bits
29+
0x0C-0x10 Client public IPv4 // 32 bits
30+
31+
Client IP address and UDP port are obfuscated / inverted
32+
"""
33+
addr = IPv6Address(teredo_addr)
34+
teredo_tuple = addr.teredo
35+
if not teredo_tuple:
36+
raise ValueError('Not a teredo address')
37+
38+
prefix, server_ipv4, flags, udp_port, client_ipv4 = \
39+
unpack('!IIHHI', teredo_addr)
40+
41+
# Deobfuscate/invert client address and port
42+
client_ipv4 ^= 0xFFFFFFFF
43+
udp_port ^= 0xFFFF
44+
45+
# Convert IP addresses to object
46+
server_ipv4 = IPv4Address(server_ipv4)
47+
client_ipv4 = IPv4Address(client_ipv4)
48+
49+
assert server_ipv4 == teredo_tuple[0]
50+
assert client_ipv4 == teredo_tuple[1]
51+
52+
return TeredoEndpoint(
53+
teredo_server_ipv4=server_ipv4,
54+
flags=flags,
55+
udp_port=udp_port,
56+
client_pub_ipv4=client_ipv4
57+
)
58+
1159
class TeredoPacket:
1260
def __init__(
1361
self,
14-
ipv6_base: ipv6.IPv6Packet
62+
ipv6_base: ipv6.IPv6Packet,
63+
src_teredo: TeredoEndpoint,
64+
dst_teredo: TeredoEndpoint
1565
) -> None:
1666
self.ipv6 = ipv6_base
67+
self.src_teredo = src_teredo
68+
self.dst_teredo = dst_teredo
1769

1870
def __repr__(self) -> str:
1971
return (
20-
f"TeredoPacket(IPv6={self.ipv6})"
72+
f"TeredoPacket(IPv6={self.ipv6}, SRC={self.src_teredo}, DST={self.dst_teredo})"
2173
)
2274

2375
@classmethod
@@ -27,5 +79,7 @@ def parse(cls, data: bytes):
2779
f"Teredo packet length is less than {TEREDO_HEADER_LENGTH} bytes"
2880
)
2981
base = ipv6.IPv6Packet.parse(data)
30-
return cls(base)
82+
src_teredo = convert_teredo_addr_to_endpoint(base.src)
83+
dst_teredo = convert_teredo_addr_to_endpoint(base.dst)
84+
return cls(base, src_teredo, dst_teredo)
3185

0 commit comments

Comments
 (0)