Skip to main content

netget/server/vxlan/
frame.rs

1//! VXLAN (RFC 7348), Geneve (RFC 8926) and Ethernet over GRE (RFC 2784 with RFC 2890's key
2//! and sequence number, carried in UDP per RFC 8086) encapsulation, and just enough of
3//! Ethernet, ARP, IPv4, ICMP echo and UDP to be a host on the overlay. Shared by the server
4//! and the client (`src/client/vxlan/`). Pure: no sockets here.
5use anyhow::{bail, ensure, Context, Result};
6use std::net::Ipv4Addr;
7
8pub const VXLAN_PORT: u16 = 4789;
9pub const GENEVE_PORT: u16 = 6081;
10/// GRE-in-UDP (RFC 8086): the UDP payload is a GRE header and what it carries.
11pub const GRE_IN_UDP_PORT: u16 = 4754;
12/// Geneve's and GRE's protocol type for an Ethernet payload (Transparent Ethernet Bridging).
13const ETH_BRIDGING: u16 = 0x6558;
14/// GRE flag bits in the first header byte (RFC 2784 section 2.1, RFC 2890 section 2).
15const GRE_CHECKSUM: u8 = 0x80;
16const GRE_KEY: u8 = 0x20;
17const GRE_SEQUENCE: u8 = 0x10;
18/// RFC 2784's "bits 1-5", less RFC 2890's K and S: RFC 1701's Routing Present (0x40), Strict
19/// Source Route (0x08) and the top bit of Recursion Control (0x04). A receiver discards a
20/// packet with any of them set.
21const GRE_RFC1701_ONLY: u8 = 0x40 | 0x08 | 0x04;
22pub const ETHERTYPE_IPV4: u16 = 0x0800;
23pub const ETHERTYPE_ARP: u16 = 0x0806;
24/// The largest UDP payload: one IPv4 datagram's worth.
25pub const MAX_DATAGRAM: usize = 65_507;
26/// A UDP payload NetGet will build into one inner frame; bigger would need IP fragmentation,
27/// which the overlay host does not do.
28pub const MAX_UDP_PAYLOAD: usize = 1400;
29/// A locally administered MAC for NetGet's overlay hosts when none is given.
30pub const DEFAULT_MAC: &str = "02:4e:47:00:00:01";
31
32pub type Mac = [u8; 6];
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum Encap {
36    Vxlan,
37    Geneve,
38    /// Ethernet over GRE (a Linux `gretap` link), in UDP on port 4754 (a Linux `fou` port).
39    Gretap,
40}
41
42impl Encap {
43    pub fn parse(s: &str) -> Result<Self> {
44        match s {
45            "vxlan" => Ok(Encap::Vxlan),
46            "geneve" => Ok(Encap::Geneve),
47            "gretap" => Ok(Encap::Gretap),
48            other => bail!("encapsulation {other:?} is not vxlan, geneve or gretap"),
49        }
50    }
51    pub fn name(self) -> &'static str {
52        match self {
53            Encap::Vxlan => "vxlan",
54            Encap::Geneve => "geneve",
55            Encap::Gretap => "gretap",
56        }
57    }
58    pub fn port(self) -> u16 {
59        match self {
60            Encap::Vxlan => VXLAN_PORT,
61            Encap::Geneve => GENEVE_PORT,
62            Encap::Gretap => GRE_IN_UDP_PORT,
63        }
64    }
65    /// The largest VNI: 24 bits for VXLAN and Geneve, the whole 32-bit key for GRE.
66    pub fn max_vni(self) -> u32 {
67        match self {
68            Encap::Vxlan | Encap::Geneve => MAX_VNI,
69            Encap::Gretap => u32::MAX,
70        }
71    }
72}
73
74pub const MAX_VNI: u32 = 0xff_ffff;
75
76/// What an encapsulation header says besides the frame it carries.
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
78pub struct Header {
79    /// The VXLAN or Geneve VNI (always there), or the GRE key (there when the K bit is set).
80    pub vni: Option<u32>,
81    /// GRE only: a checksum is present. On input it has been verified.
82    pub checksum: bool,
83    /// GRE only: the sequence number, when the S bit is set.
84    pub sequence: Option<u32>,
85}
86
87impl Header {
88    pub fn vni(vni: u32) -> Self {
89        Header {
90            vni: Some(vni),
91            ..Default::default()
92        }
93    }
94}
95
96/// `VNI 42`, `key 42` or `no key`, for logs.
97pub fn vni_str(encap: Encap, vni: Option<u32>) -> String {
98    match (encap, vni) {
99        (Encap::Gretap, Some(k)) => format!("key {k}"),
100        (Encap::Gretap, None) => "no key".into(),
101        (_, v) => format!("VNI {}", v.unwrap_or_default()),
102    }
103}
104
105/// The header and the inner Ethernet frame of one encapsulated datagram.
106pub fn decap(encap: Encap, b: &[u8]) -> Result<(Header, &[u8])> {
107    if encap == Encap::Gretap {
108        return decap_gre(b);
109    }
110    ensure!(
111        b.len() >= 8,
112        "a {}-byte datagram has no {} header",
113        b.len(),
114        encap.name()
115    );
116    let vni = u32::from_be_bytes([0, b[4], b[5], b[6]]);
117    match encap {
118        Encap::Vxlan => {
119            ensure!(b[0] & 0x08 != 0, "the VXLAN I flag is clear: no valid VNI");
120            Ok((Header::vni(vni), &b[8..]))
121        }
122        Encap::Geneve => {
123            let version = b[0] >> 6;
124            ensure!(version == 0, "Geneve version {version}");
125            ensure!(b[1] & 0x80 == 0, "a Geneve OAM packet");
126            let options = (b[0] & 0x3f) as usize * 4;
127            let protocol = u16::from_be_bytes([b[2], b[3]]);
128            ensure!(
129                protocol == ETH_BRIDGING,
130                "Geneve protocol type {protocol:#06x}, not Ethernet"
131            );
132            ensure!(
133                b.len() >= 8 + options,
134                "Geneve options run past the datagram"
135            );
136            Ok((Header::vni(vni), &b[8 + options..]))
137        }
138        Encap::Gretap => unreachable!("decap_gre"),
139    }
140}
141
142/// RFC 2784 with RFC 2890's key and sequence number. Each optional field is length-checked
143/// before it is read, and a present checksum must verify over the header and the payload.
144fn decap_gre(b: &[u8]) -> Result<(Header, &[u8])> {
145    ensure!(
146        b.len() >= 4,
147        "a {}-byte datagram has no GRE header",
148        b.len()
149    );
150    let version = b[1] & 0x07;
151    ensure!(
152        version == 0,
153        "GRE version {version} (1 is PPTP's enhanced GRE)"
154    );
155    ensure!(
156        b[0] & GRE_RFC1701_ONLY == 0,
157        "GRE flags {:#04x}: RFC 1701 routing, strict source route or recursion bits are set",
158        b[0]
159    );
160    let protocol = u16::from_be_bytes([b[2], b[3]]);
161    ensure!(
162        protocol == ETH_BRIDGING,
163        "GRE protocol type {protocol:#06x}, not Transparent Ethernet Bridging"
164    );
165    let checksum = b[0] & GRE_CHECKSUM != 0;
166    let mut at = 4;
167    let mut field = |present: bool, what: &str| -> Result<Option<u32>> {
168        if !present {
169            return Ok(None);
170        }
171        ensure!(
172            b.len() >= at + 4,
173            "a {}-byte datagram ends inside the GRE {what}",
174            b.len()
175        );
176        let v = u32::from_be_bytes([b[at], b[at + 1], b[at + 2], b[at + 3]]);
177        at += 4;
178        Ok(Some(v))
179    };
180    field(checksum, "checksum")?;
181    let key = field(b[0] & GRE_KEY != 0, "key")?;
182    let sequence = field(b[0] & GRE_SEQUENCE != 0, "sequence number")?;
183    // RFC 2784: the checksum covers the GRE header and the payload, so a correct one sums the
184    // whole datagram, checksum field included, to zero.
185    ensure!(!checksum || self::checksum(&[b]) == 0, "bad GRE checksum");
186    Ok((
187        Header {
188            vni: key,
189            checksum,
190            sequence,
191        },
192        &b[at..],
193    ))
194}
195
196/// Encapsulate an inner frame. VXLAN and Geneve take the VNI (0 when `None`) and ignore the
197/// rest; GRE writes each field the header has.
198pub fn encap(encap: Encap, header: &Header, inner: &[u8]) -> Vec<u8> {
199    let mut out = Vec::with_capacity(16 + inner.len());
200    let v = header.vni.unwrap_or_default().to_be_bytes();
201    match encap {
202        Encap::Vxlan => out.extend_from_slice(&[0x08, 0, 0, 0, v[1], v[2], v[3], 0]),
203        Encap::Geneve => {
204            out.extend_from_slice(&[0, 0]);
205            out.extend_from_slice(&ETH_BRIDGING.to_be_bytes());
206            out.extend_from_slice(&[v[1], v[2], v[3], 0]);
207        }
208        Encap::Gretap => {
209            let flags = if header.checksum { GRE_CHECKSUM } else { 0 }
210                | if header.vni.is_some() { GRE_KEY } else { 0 }
211                | if header.sequence.is_some() {
212                    GRE_SEQUENCE
213                } else {
214                    0
215                };
216            out.extend_from_slice(&[flags, 0]);
217            out.extend_from_slice(&ETH_BRIDGING.to_be_bytes());
218            if header.checksum {
219                out.extend_from_slice(&[0; 4]);
220            }
221            if header.vni.is_some() {
222                out.extend_from_slice(&v);
223            }
224            if let Some(s) = header.sequence {
225                out.extend_from_slice(&s.to_be_bytes());
226            }
227            out.extend_from_slice(inner);
228            if header.checksum {
229                let sum = checksum(&[&out]);
230                out[4..6].copy_from_slice(&sum.to_be_bytes());
231            }
232            return out;
233        }
234    }
235    out.extend_from_slice(inner);
236    out
237}
238
239/// Write `sequence` into a GRE datagram that carries a sequence number, and redo its checksum
240/// when it has one, so the number can be given at the moment of sending: a peer that checks
241/// sequence numbers (a Linux `gretap` with `iseq`) drops one that goes backwards. False, with
242/// nothing changed, for a datagram that carries none.
243pub fn stamp_sequence(encap: Encap, datagram: &mut [u8], sequence: u32) -> bool {
244    if encap != Encap::Gretap || datagram.len() < 4 || datagram[0] & GRE_SEQUENCE == 0 {
245        return false;
246    }
247    let has_checksum = datagram[0] & GRE_CHECKSUM != 0;
248    let at = 4 + 4 * usize::from(has_checksum) + 4 * usize::from(datagram[0] & GRE_KEY != 0);
249    if datagram.len() < at + 4 {
250        return false;
251    }
252    datagram[at..at + 4].copy_from_slice(&sequence.to_be_bytes());
253    if has_checksum {
254        datagram[4..6].copy_from_slice(&[0, 0]);
255        let sum = checksum(&[datagram]);
256        datagram[4..6].copy_from_slice(&sum.to_be_bytes());
257    }
258    true
259}
260
261pub fn parse_mac(s: &str) -> Result<Mac> {
262    let parts: Vec<&str> = s.split([':', '-']).collect();
263    ensure!(
264        parts.len() == 6,
265        "{s:?} is not a MAC address like 02:00:00:00:00:01"
266    );
267    let mut mac = [0u8; 6];
268    for (i, p) in parts.iter().enumerate() {
269        mac[i] =
270            u8::from_str_radix(p, 16).with_context(|| format!("{s:?} is not a MAC address"))?;
271    }
272    ensure!(mac[0] & 1 == 0, "{s} is a multicast address, not a host's");
273    Ok(mac)
274}
275
276pub fn mac_str(m: &Mac) -> String {
277    m.iter()
278        .map(|b| format!("{b:02x}"))
279        .collect::<Vec<_>>()
280        .join(":")
281}
282
283#[derive(Debug, Clone, PartialEq, Eq)]
284pub struct Arp {
285    pub request: bool,
286    pub sender_mac: Mac,
287    pub sender_ip: Ipv4Addr,
288    pub target_mac: Mac,
289    pub target_ip: Ipv4Addr,
290}
291
292#[derive(Debug, Clone, PartialEq, Eq)]
293pub enum Payload {
294    Arp(Arp),
295    Echo {
296        request: bool,
297        identifier: u16,
298        sequence: u16,
299        data: Vec<u8>,
300    },
301    /// ICMP destination unreachable / time exceeded, with what it is about.
302    IcmpError {
303        kind: u8,
304        code: u8,
305        original_protocol: u8,
306        original_dst: Ipv4Addr,
307        original_dst_port: Option<u16>,
308    },
309    Udp {
310        src_port: u16,
311        dst_port: u16,
312        data: Vec<u8>,
313    },
314    /// Anything else, described but not answered.
315    Other {
316        what: String,
317    },
318}
319
320#[derive(Debug, Clone, PartialEq, Eq)]
321pub struct Frame {
322    pub dst_mac: Mac,
323    pub src_mac: Mac,
324    /// IPv4 only; ARP carries its addresses in `Payload::Arp`.
325    pub src_ip: Option<Ipv4Addr>,
326    pub dst_ip: Option<Ipv4Addr>,
327    pub payload: Payload,
328}
329
330/// RFC 1071.
331pub fn checksum(parts: &[&[u8]]) -> u16 {
332    let mut sum: u32 = 0;
333    let mut odd: Option<u8> = None;
334    for part in parts {
335        for &b in *part {
336            match odd.take() {
337                Some(hi) => sum += u32::from(u16::from_be_bytes([hi, b])),
338                None => odd = Some(b),
339            }
340        }
341    }
342    if let Some(hi) = odd {
343        sum += u32::from(u16::from_be_bytes([hi, 0]));
344    }
345    while sum >> 16 != 0 {
346        sum = (sum & 0xffff) + (sum >> 16);
347    }
348    !(sum as u16)
349}
350
351fn ip(b: &[u8]) -> Ipv4Addr {
352    Ipv4Addr::new(b[0], b[1], b[2], b[3])
353}
354
355fn mac(b: &[u8]) -> Mac {
356    let mut m = [0u8; 6];
357    m.copy_from_slice(&b[..6]);
358    m
359}
360
361/// Parse an inner Ethernet frame. Malformed frames are errors; well-formed frames NetGet
362/// does not answer (IPv6, TCP, fragments, other ICMP) are `Payload::Other`.
363pub fn parse_frame(b: &[u8]) -> Result<Frame> {
364    ensure!(
365        b.len() >= 14,
366        "a {}-byte inner frame is shorter than Ethernet",
367        b.len()
368    );
369    let (dst_mac, src_mac) = (mac(&b[0..6]), mac(&b[6..12]));
370    let ethertype = u16::from_be_bytes([b[12], b[13]]);
371    let body = &b[14..];
372    let other = |what: String| Frame {
373        dst_mac,
374        src_mac,
375        src_ip: None,
376        dst_ip: None,
377        payload: Payload::Other { what },
378    };
379    match ethertype {
380        ETHERTYPE_ARP => {
381            ensure!(body.len() >= 28, "a truncated ARP packet");
382            ensure!(
383                body[0..6] == [0, 1, 8, 0, 6, 4],
384                "ARP for something other than Ethernet/IPv4"
385            );
386            let op = u16::from_be_bytes([body[6], body[7]]);
387            ensure!(op == 1 || op == 2, "ARP operation {op}");
388            Ok(Frame {
389                dst_mac,
390                src_mac,
391                src_ip: None,
392                dst_ip: None,
393                payload: Payload::Arp(Arp {
394                    request: op == 1,
395                    sender_mac: mac(&body[8..14]),
396                    sender_ip: ip(&body[14..18]),
397                    target_mac: mac(&body[18..24]),
398                    target_ip: ip(&body[24..28]),
399                }),
400            })
401        }
402        ETHERTYPE_IPV4 => parse_ipv4(dst_mac, src_mac, body),
403        other_type => Ok(other(format!("ethertype {other_type:#06x}"))),
404    }
405}
406
407fn parse_ipv4(dst_mac: Mac, src_mac: Mac, b: &[u8]) -> Result<Frame> {
408    ensure!(b.len() >= 20, "a truncated IPv4 header");
409    ensure!(b[0] >> 4 == 4, "IP version {}", b[0] >> 4);
410    let ihl = (b[0] & 0x0f) as usize * 4;
411    ensure!(ihl >= 20 && b.len() >= ihl, "IPv4 header length {ihl}");
412    let total = u16::from_be_bytes([b[2], b[3]]) as usize;
413    ensure!(
414        total >= ihl && total <= b.len(),
415        "IPv4 total length {total} against a {}-byte payload",
416        b.len()
417    );
418    ensure!(checksum(&[&b[..ihl]]) == 0, "bad IPv4 header checksum");
419    let (src, dst) = (ip(&b[12..16]), ip(&b[16..20]));
420    let protocol = b[9];
421    let flags_frag = u16::from_be_bytes([b[6], b[7]]);
422    let body = &b[ihl..total];
423    let frame = |payload| Frame {
424        dst_mac,
425        src_mac,
426        src_ip: Some(src),
427        dst_ip: Some(dst),
428        payload,
429    };
430    if flags_frag & 0x3fff != 0 {
431        return Ok(frame(Payload::Other {
432            what: "an IPv4 fragment".into(),
433        }));
434    }
435    match protocol {
436        1 => {
437            ensure!(body.len() >= 8, "a truncated ICMP message");
438            ensure!(checksum(&[body]) == 0, "bad ICMP checksum");
439            let (kind, code) = (body[0], body[1]);
440            match kind {
441                0 | 8 => Ok(frame(Payload::Echo {
442                    request: kind == 8,
443                    identifier: u16::from_be_bytes([body[4], body[5]]),
444                    sequence: u16::from_be_bytes([body[6], body[7]]),
445                    data: body[8..].to_vec(),
446                })),
447                3 | 11 if body.len() >= 8 + 20 => {
448                    let inner = &body[8..];
449                    let inner_ihl = (inner[0] & 0x0f) as usize * 4;
450                    let original_protocol = inner[9];
451                    let original_dst_port = (original_protocol == 17
452                        && inner.len() >= inner_ihl + 4)
453                        .then(|| u16::from_be_bytes([inner[inner_ihl + 2], inner[inner_ihl + 3]]));
454                    Ok(frame(Payload::IcmpError {
455                        kind,
456                        code,
457                        original_protocol,
458                        original_dst: ip(&inner[16..20]),
459                        original_dst_port,
460                    }))
461                }
462                _ => Ok(frame(Payload::Other {
463                    what: format!("ICMP type {kind} code {code}"),
464                })),
465            }
466        }
467        17 => {
468            ensure!(body.len() >= 8, "a truncated UDP header");
469            let len = u16::from_be_bytes([body[4], body[5]]) as usize;
470            ensure!(len >= 8 && len <= body.len(), "UDP length {len}");
471            let sum = u16::from_be_bytes([body[6], body[7]]);
472            if sum != 0 {
473                ensure!(
474                    checksum(&[&pseudo_header(src, dst, 17, len), &body[..len]]) == 0,
475                    "bad UDP checksum"
476                );
477            }
478            Ok(frame(Payload::Udp {
479                src_port: u16::from_be_bytes([body[0], body[1]]),
480                dst_port: u16::from_be_bytes([body[2], body[3]]),
481                data: body[8..len].to_vec(),
482            }))
483        }
484        6 => Ok(frame(Payload::Other { what: "TCP".into() })),
485        p => Ok(frame(Payload::Other {
486            what: format!("IP protocol {p}"),
487        })),
488    }
489}
490
491fn pseudo_header(src: Ipv4Addr, dst: Ipv4Addr, protocol: u8, len: usize) -> Vec<u8> {
492    let mut p = Vec::with_capacity(12);
493    p.extend_from_slice(&src.octets());
494    p.extend_from_slice(&dst.octets());
495    p.extend_from_slice(&[0, protocol]);
496    p.extend_from_slice(&(len as u16).to_be_bytes());
497    p
498}
499
500pub fn ethernet(dst: Mac, src: Mac, ethertype: u16, payload: &[u8]) -> Vec<u8> {
501    let mut out = Vec::with_capacity(14 + payload.len());
502    out.extend_from_slice(&dst);
503    out.extend_from_slice(&src);
504    out.extend_from_slice(&ethertype.to_be_bytes());
505    out.extend_from_slice(payload);
506    out
507}
508
509/// A whole ARP frame. A request is broadcast with a zero target MAC.
510pub fn arp_frame(
511    request: bool,
512    sender_mac: Mac,
513    sender_ip: Ipv4Addr,
514    target_mac: Mac,
515    target_ip: Ipv4Addr,
516) -> Vec<u8> {
517    let mut a = vec![0, 1, 8, 0, 6, 4, 0, if request { 1 } else { 2 }];
518    a.extend_from_slice(&sender_mac);
519    a.extend_from_slice(&sender_ip.octets());
520    a.extend_from_slice(&if request { [0; 6] } else { target_mac });
521    a.extend_from_slice(&target_ip.octets());
522    let dst = if request { [0xff; 6] } else { target_mac };
523    ethernet(dst, sender_mac, ETHERTYPE_ARP, &a)
524}
525
526/// An IPv4 packet with TTL 64 and Don't Fragment.
527pub fn ipv4(src: Ipv4Addr, dst: Ipv4Addr, protocol: u8, id: u16, payload: &[u8]) -> Vec<u8> {
528    let total = (20 + payload.len()) as u16;
529    let mut h = vec![0x45, 0];
530    h.extend_from_slice(&total.to_be_bytes());
531    h.extend_from_slice(&id.to_be_bytes());
532    h.extend_from_slice(&[0x40, 0, 64, protocol, 0, 0]);
533    h.extend_from_slice(&src.octets());
534    h.extend_from_slice(&dst.octets());
535    let sum = checksum(&[&h]);
536    h[10..12].copy_from_slice(&sum.to_be_bytes());
537    h.extend_from_slice(payload);
538    h
539}
540
541pub fn icmp_echo(request: bool, identifier: u16, sequence: u16, data: &[u8]) -> Vec<u8> {
542    let mut m = vec![if request { 8 } else { 0 }, 0, 0, 0];
543    m.extend_from_slice(&identifier.to_be_bytes());
544    m.extend_from_slice(&sequence.to_be_bytes());
545    m.extend_from_slice(data);
546    let sum = checksum(&[&m]);
547    m[2..4].copy_from_slice(&sum.to_be_bytes());
548    m
549}
550
551pub fn udp(src: Ipv4Addr, dst: Ipv4Addr, src_port: u16, dst_port: u16, data: &[u8]) -> Vec<u8> {
552    let len = 8 + data.len();
553    let mut u = Vec::with_capacity(len);
554    u.extend_from_slice(&src_port.to_be_bytes());
555    u.extend_from_slice(&dst_port.to_be_bytes());
556    u.extend_from_slice(&(len as u16).to_be_bytes());
557    u.extend_from_slice(&[0, 0]);
558    u.extend_from_slice(data);
559    let mut sum = checksum(&[&pseudo_header(src, dst, 17, len), &u]);
560    if sum == 0 {
561        sum = 0xffff;
562    }
563    u[6..8].copy_from_slice(&sum.to_be_bytes());
564    u
565}
566
567/// Text when the bytes are printable UTF-8, hex otherwise; with the encoding named, so the
568/// model can answer in kind (the TCP convention).
569pub fn data_json(data: &[u8]) -> (String, &'static str) {
570    match std::str::from_utf8(data) {
571        Ok(s)
572            if s.chars()
573                .all(|c| !c.is_control() || matches!(c, '\n' | '\r' | '\t')) =>
574        {
575            (s.to_string(), "utf8")
576        }
577        _ => (hex::encode(data), "hex"),
578    }
579}
580
581/// The bytes of an action's `data`, decoded by its `encoding` (`utf8` default, or `hex`).
582pub fn data_bytes(v: &serde_json::Value) -> Result<Vec<u8>> {
583    let data = v["data"].as_str().unwrap_or_default();
584    let bytes = match v["encoding"].as_str().unwrap_or("utf8") {
585        "utf8" => data.as_bytes().to_vec(),
586        "hex" => hex::decode(data).context("data is not hex")?,
587        other => bail!("encoding {other:?} is not utf8 or hex"),
588    };
589    ensure!(
590        bytes.len() <= MAX_UDP_PAYLOAD,
591        "{} bytes is more than one frame carries ({MAX_UDP_PAYLOAD})",
592        bytes.len()
593    );
594    Ok(bytes)
595}