1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
use bytes::Buf;
/// Heuristic identification of a packet.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct PacketIdent {
/// The type of the packet.
pub ty: PacketType,
/// Whether the packet is encapsulated.
pub encapsulation: Encapsulation,
}
/// Heuristically-determined packet type.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum PacketType {
/// This is a Tailscale disco packet.
Disco,
/// This is a WireGuard packet.
Wireguard,
/// This is a STUN binding packet.
StunBinding,
/// The type of this packet is unknown.
///
/// It should be dropped.
#[default]
Unknown,
}
/// The encapsulation format of a packet.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum Encapsulation {
/// Packet is not encapsulated.
#[default]
None,
/// Packet is encapsulated in a valid Geneve header.
Geneve(GeneveHeader),
}
impl Encapsulation {
/// Return the offset to the payload data contents.
///
/// This skips any encapsulating header (currently Geneve) if present.
pub const fn payload_offset(&self) -> usize {
match self {
Encapsulation::None => 0,
Encapsulation::Geneve(geneve) => {
size_of::<GeneveHeader>() + (geneve.opt_len() * 4) as usize
}
}
}
}
impl PacketIdent {
/// Determine the type and encapsulation of `pkt`.
///
/// This differs from the Go (`magicsock.go:packetLooksLike`) in that
/// `PacketType::Unknown` is separated from WireGuard traffic and means that the packet
/// is positively not interpretable as a known packet type. WireGuard is still the catchall, we
/// just verify the first few bytes of the message here using
/// [`PacketIdent::could_be_wireguard`].
pub fn identify(pkt: &[u8]) -> PacketIdent {
let geneve = Self::parse_geneve(pkt);
let geneve_ty = geneve.map(|x| x.packet_ty()).unwrap_or_default();
let encapsulation = geneve.map(Encapsulation::Geneve).unwrap_or_default();
let payload = &pkt[encapsulation.payload_offset()..];
let ty = if disco::is_disco_message(payload) {
PacketType::Disco
} else if Self::could_be_wireguard(payload) {
// Assume that all remaining traffic that could be a Wireguard packet is one.
PacketType::Wireguard
} else {
PacketType::Unknown
};
let ty = match (ty, geneve_ty) {
(x, y) if x == y => x,
// A single Unknown verdict resolves to the known packet type.
(PacketType::Unknown, x) | (x, PacketType::Unknown) => x,
// Packet inspection and Geneve disagreed.
_ => PacketType::Unknown,
};
Self { encapsulation, ty }
}
/// Attempt to parse `pkt` as a Geneve-encapsulated packet.
pub fn parse_geneve(mut pkt: &[u8]) -> Option<GeneveHeader> {
let b = pkt.try_get_u64().ok()?;
let header = GeneveHeader(b);
if !header.is_acceptable_data_packet() {
return None;
}
Some(header)
}
/// Report whether the packet could be a Wireguard packet.
///
/// Checks certain invariants that must be true if this is Wireguard; does not establish
/// conclusive proof.
pub fn could_be_wireguard(pkt: &[u8]) -> bool {
if pkt.len() < 5 {
return false;
}
let msgty = pkt[0];
if !(1u8..=4).contains(&msgty) {
return false;
}
[0u8; 4] == pkt[1..=4]
}
}
bitrs::layout!({
/// Geneve encapsulation protocol header as described in [RFC8926].
///
/// [RFC8926]: https://www.rfc-editor.org/info/rfc8926/#name-tunnel-header-fields.
pub struct GeneveHeader(pub u64);
{
/// The version of the Geneve packet.
///
/// See [`GeneveHeader::VERSION`] for the current version.
let version @ 63..62;
/// The length of the variable-length options in multiples of 4 bytes. Excludes the length
/// of the header.
let opt_len @ 61..56;
/// This is a control packet.
let control @ 55;
/// This packet carries critical option fields that must be interpreted or else the packet
/// dropped.
let critical @ 54;
let __ @ 53..48 = 0;
/// The type of data encapsulated by the header.
let ethertype @ 47..32;
/// The virtual network identifier.
let vni @ 31..8;
/// Reserved field specified by the RFC to be zero on transmit, ignore on receive.
///
/// Tailscale checks whether it is zeroed because we always zero it.
let trailer @ 7..0 = 0;
}
});
impl GeneveHeader {
/// Current version of the Geneve header.
pub const VERSION: u8 = 0;
/// Ethertype indicating this is a Tailscale disco message.
pub const PROTO_DISCO: u16 = 0x7a11;
/// Ethertype field indicating this is a WireGuard message.
pub const PROTO_WIREGUARD: u16 = 0x7a12;
/// Report whether this is a valid Geneve header.
///
/// This checks both the version (against [`GeneveHeader::VERSION`]) and that the final reserved
/// field is zero. The RFC specifies that this field should be ignored on receipt, but we always
/// zero it, so sanity-check that it came from Tailscale.
pub const fn is_valid(&self) -> bool {
self.version() == Self::VERSION && self.trailer() == 0
}
/// Report whether this is an acceptable Tailscale data packet.
///
/// Ensures that the packet [`is_valid`][GeneveHeader::is_valid], there are no options, this
/// isn't a control packet, and it doesn't have the critical options bit set.
pub const fn is_acceptable_data_packet(&self) -> bool {
// NOTE(npry): the Go doesn't filter on the control bit, but according to the RFC,
// "Control messages are sent between tunnel endpoints. Tunnel endpoints MUST NOT forward
// the payload." I take this to mean that control messages are treated as their own logical
// stream for configuring the tunnel itself, and I don't believe we use them. If this
// interpretation turns out to be incorrect, we'll just need to remove the control bit check
// in the future.
self.is_valid() && !self.control() && !self.critical() && self.opt_len() == 0
}
/// Report the [`PacketType`] encapsulated by this header.
///
/// If the [`GeneveHeader::ethertype`] isn't recognized, [`PacketType::Unknown`] is returned.
pub const fn packet_ty(&self) -> PacketType {
match self.ethertype() {
Self::PROTO_DISCO => PacketType::Disco,
Self::PROTO_WIREGUARD => PacketType::Wireguard,
_ => PacketType::Unknown,
}
}
}