ts_packet/geneve.rs
1//! Geneve (RFC 8926) fixed-header codec for Tailscale **peer-relay** framing.
2//!
3//! Tailscale's peer-relay data path encapsulates both relayed disco (the bind handshake) and
4//! relayed WireGuard data in a Geneve header carrying a 24-bit VNI (virtual network identifier).
5//! This module parses/encodes just the 8-byte fixed Geneve header Tailscale uses — the relay data
6//! path itself (the `relayManager` handshake + magicsock integration) is not yet implemented in this
7//! fork, but recognizing the framing keeps the fork wire-aware (e.g. so relayed frames can be
8//! classified rather than treated as opaque/undecodable).
9//!
10//! Header layout (RFC 8926 §3.4, fixed 8 bytes; Tailscale uses no variable options):
11//!
12//! ```text
13//! 0 1 2 3
14//! 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
15//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16//! |Ver| Opt Len |O|C| Rsvd. | Protocol Type |
17//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
18//! | Virtual Network Identifier (VNI) | Reserved |
19//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20//! ```
21
22/// The fixed Geneve header length in bytes (Tailscale uses no variable options, so `Opt Len` is 0).
23pub const GENEVE_FIXED_HEADER_LEN: usize = 8;
24
25/// Geneve "Protocol Type" for relayed **disco** frames (Tailscale `GeneveProtocolDisco`).
26pub const GENEVE_PROTOCOL_DISCO: u16 = 0x7A11;
27/// Geneve "Protocol Type" for relayed **WireGuard** frames (Tailscale `GeneveProtocolWireGuard`).
28pub const GENEVE_PROTOCOL_WIREGUARD: u16 = 0x7A12;
29
30/// A parsed Tailscale Geneve fixed header.
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct GeneveHeader {
33 /// The "Control" (C) bit: set when the payload is a control message (the relay bind handshake)
34 /// rather than tunneled data.
35 pub control: bool,
36 /// The inner protocol type (`GENEVE_PROTOCOL_DISCO` / `GENEVE_PROTOCOL_WIREGUARD`).
37 pub protocol: u16,
38 /// The 24-bit Virtual Network Identifier.
39 pub vni: u32,
40}
41
42/// Errors decoding a Geneve header.
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum GeneveError {
45 /// The buffer is shorter than the 8-byte fixed header.
46 TooShort,
47 /// The version field is not 0 (the only version Tailscale emits).
48 BadVersion,
49 /// A non-zero `Opt Len` was present (Tailscale uses no variable options; we don't parse them).
50 UnexpectedOptions,
51}
52
53impl GeneveHeader {
54 /// Parse a Geneve fixed header from the front of `buf`. Returns the header and the offset of the
55 /// inner payload (always [`GENEVE_FIXED_HEADER_LEN`]). Rejects a non-zero version or option
56 /// length (Tailscale emits neither) so a malformed/foreign Geneve packet is not mis-decoded.
57 pub fn parse(buf: &[u8]) -> Result<(GeneveHeader, usize), GeneveError> {
58 if buf.len() < GENEVE_FIXED_HEADER_LEN {
59 return Err(GeneveError::TooShort);
60 }
61 // Byte 0: Ver (2 bits) | Opt Len (6 bits, in 4-byte words).
62 let version = buf[0] >> 6;
63 if version != 0 {
64 return Err(GeneveError::BadVersion);
65 }
66 let opt_len_words = buf[0] & 0x3f;
67 if opt_len_words != 0 {
68 return Err(GeneveError::UnexpectedOptions);
69 }
70 // Byte 1: O (bit 7) | C (bit 6) | reserved.
71 let control = (buf[1] & 0x40) != 0;
72 // Bytes 2..4: Protocol Type (big-endian u16).
73 let protocol = u16::from_be_bytes([buf[2], buf[3]]);
74 // Bytes 4..7: 24-bit VNI (big-endian); byte 7 is reserved.
75 let vni = (u32::from(buf[4]) << 16) | (u32::from(buf[5]) << 8) | u32::from(buf[6]);
76
77 Ok((
78 GeneveHeader {
79 control,
80 protocol,
81 vni,
82 },
83 GENEVE_FIXED_HEADER_LEN,
84 ))
85 }
86
87 /// Encode this header into an 8-byte fixed Geneve header (no variable options).
88 pub fn encode(&self) -> [u8; GENEVE_FIXED_HEADER_LEN] {
89 let mut out = [0u8; GENEVE_FIXED_HEADER_LEN];
90 // Ver = 0, Opt Len = 0 => byte 0 is 0.
91 out[0] = 0;
92 // O bit unused (0); set C bit when control.
93 out[1] = if self.control { 0x40 } else { 0x00 };
94 out[2..4].copy_from_slice(&self.protocol.to_be_bytes());
95 out[4] = (self.vni >> 16) as u8;
96 out[5] = (self.vni >> 8) as u8;
97 out[6] = self.vni as u8;
98 // out[7] reserved = 0.
99 out
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn roundtrip_disco_data() {
109 let h = GeneveHeader {
110 control: true,
111 protocol: GENEVE_PROTOCOL_DISCO,
112 vni: 0x0A_BC_DE,
113 };
114 let bytes = h.encode();
115 let (parsed, off) = GeneveHeader::parse(&bytes).unwrap();
116 assert_eq!(off, GENEVE_FIXED_HEADER_LEN);
117 assert_eq!(parsed, h);
118 }
119
120 #[test]
121 fn roundtrip_wireguard_no_control() {
122 let h = GeneveHeader {
123 control: false,
124 protocol: GENEVE_PROTOCOL_WIREGUARD,
125 vni: 1,
126 };
127 let bytes = h.encode();
128 assert_eq!(bytes[1] & 0x40, 0, "control bit must be clear");
129 let (parsed, _) = GeneveHeader::parse(&bytes).unwrap();
130 assert_eq!(parsed, h);
131 }
132
133 #[test]
134 fn vni_is_24_bits() {
135 let h = GeneveHeader {
136 control: false,
137 protocol: GENEVE_PROTOCOL_DISCO,
138 vni: 0xFF_FF_FF,
139 };
140 let bytes = h.encode();
141 // Byte 7 (reserved) must stay zero even at max VNI.
142 assert_eq!(bytes[7], 0);
143 let (parsed, _) = GeneveHeader::parse(&bytes).unwrap();
144 assert_eq!(parsed.vni, 0xFF_FF_FF);
145 }
146
147 #[test]
148 fn rejects_short_buffer() {
149 assert_eq!(GeneveHeader::parse(&[0u8; 7]), Err(GeneveError::TooShort));
150 }
151
152 #[test]
153 fn rejects_bad_version() {
154 let mut bytes = GeneveHeader {
155 control: false,
156 protocol: GENEVE_PROTOCOL_DISCO,
157 vni: 0,
158 }
159 .encode();
160 bytes[0] = 0x40; // version = 1
161 assert_eq!(GeneveHeader::parse(&bytes), Err(GeneveError::BadVersion));
162 }
163
164 #[test]
165 fn rejects_variable_options() {
166 let mut bytes = GeneveHeader {
167 control: false,
168 protocol: GENEVE_PROTOCOL_DISCO,
169 vni: 0,
170 }
171 .encode();
172 bytes[0] = 0x02; // opt len = 2 words
173 assert_eq!(
174 GeneveHeader::parse(&bytes),
175 Err(GeneveError::UnexpectedOptions)
176 );
177 }
178
179 #[test]
180 fn encode_matches_spec_byte_layout() {
181 // Byte-exact reference vector hand-derived from RFC 8926 §3.4 + Tailscale's usage,
182 // NOT computed by round-tripping through this fork's own encoder (that would be
183 // circular and would mask any byte-order / bit-position bug). The 6 round-trip tests
184 // above only prove encode/parse are mutually consistent, not that either matches the
185 // wire format.
186 //
187 // For GeneveHeader { control: true, protocol: GENEVE_PROTOCOL_DISCO (0x7A11),
188 // vni: 0x0ABCDE }:
189 // byte 0: Ver(2b)=00 | Opt Len(6b)=000000 => 0x00
190 // byte 1: O(bit7)=0 | C(bit6)=1 | Rsvd(6b)=0 (0b0100_0000) => 0x40
191 // byte 2: Protocol Type high byte (0x7A11 big-endian) => 0x7A
192 // byte 3: Protocol Type low byte => 0x11
193 // byte 4: VNI[23:16] of 0x0ABCDE => 0x0A
194 // byte 5: VNI[15:8] => 0xBC
195 // byte 6: VNI[7:0] => 0xDE
196 // byte 7: Reserved => 0x00
197 //
198 // Residual gap: this is a SPEC-derived vector, not one captured from a live Go
199 // `tailscaled` peer-relay packet. Full Go cross-validation would require a captured
200 // on-wire Geneve frame from tailscaled and is left as the remaining verification step.
201 let h = GeneveHeader {
202 control: true,
203 protocol: GENEVE_PROTOCOL_DISCO,
204 vni: 0x0A_BC_DE,
205 };
206 assert_eq!(h.encode(), [0x00, 0x40, 0x7A, 0x11, 0x0A, 0xBC, 0xDE, 0x00]);
207 }
208
209 #[test]
210 fn parse_known_wire_bytes() {
211 // Hand-built wire bytes (NOT produced by this fork's encoder), decoded field-by-field
212 // per RFC 8926 §3.4:
213 // byte 0 = 0x00: Ver=00 (ok), Opt Len=000000 (no options)
214 // byte 1 = 0x00: O=0, C(bit6)=0 => control = false
215 // bytes 2..4 = 0x7A,0x12: Protocol Type big-endian 0x7A12 = GENEVE_PROTOCOL_WIREGUARD
216 // bytes 4..7 = 0x00,0x00,0x01: 24-bit VNI big-endian = 0x000001 = 1
217 // byte 7 = 0x00: Reserved
218 // Inner payload therefore begins at offset GENEVE_FIXED_HEADER_LEN (8).
219 //
220 // Residual gap: spec-derived, not captured from Go `tailscaled`; a real captured
221 // peer-relay frame would be needed for full cross-implementation validation.
222 let wire = [0x00, 0x00, 0x7A, 0x12, 0x00, 0x00, 0x01, 0x00];
223 let (parsed, off) = GeneveHeader::parse(&wire).unwrap();
224 assert_eq!(
225 parsed,
226 GeneveHeader {
227 control: false,
228 protocol: GENEVE_PROTOCOL_WIREGUARD,
229 vni: 1,
230 }
231 );
232 assert_eq!(off, GENEVE_FIXED_HEADER_LEN);
233 }
234
235 #[test]
236 fn parse_returns_payload_offset() {
237 let mut buf = GeneveHeader {
238 control: false,
239 protocol: GENEVE_PROTOCOL_WIREGUARD,
240 vni: 7,
241 }
242 .encode()
243 .to_vec();
244 buf.extend_from_slice(b"payload");
245 let (_, off) = GeneveHeader::parse(&buf).unwrap();
246 assert_eq!(&buf[off..], b"payload");
247 }
248}