Networking · Network Virtualization

VXLAN Header Layout and the Scope of the VNI

A byte-level reading of the eight-byte VXLAN header, why the tenant identifier is twenty-four bits, and what the reserved fields constrain.

An overlay has to answer one question on every packet: which virtual network does this belong to. VXLAN answers it with a twenty-four-bit field sitting between an outer UDP header and an inner Ethernet frame. Everything else in the eight-byte header exists to make that field unambiguous and to keep older implementations from misreading it.

The eight bytes between UDP and Ethernet

The encapsulated packet is an outer Ethernet header, an outer IP header, a UDP header with destination port 4789, the VXLAN header, and then the complete original frame starting with its own Ethernet header. The inner frame is not parsed, not rewritten, and not truncated; it arrives at the far end as the bytes that were sent. This is why the design is called a Layer 2 overlay even when the underlay is pure Layer 3: the payload is a frame, including its MAC addresses and its optional 802.1Q tag.

The VXLAN header itself is eight bytes:

OffsetSizeField
01Flags: I bit set, rest reserved
13Reserved
43VNI
71Reserved

The I bit is bit 3 of the flags octet, counting from the most significant end, which places it at value 0x08. It indicates that a valid VNI is present. A receiver that finds it clear has no identifier it is entitled to use, and the safe reading is to discard the packet rather than treat the next twenty-four bits as a VNI; that convention is what lets a later revision reuse those bits without colliding with this one. The remaining twenty-four bits of the flags word and the final octet are reserved: transmitted as zero, ignored on receipt.

Reading the layout as a struct makes the alignment visible. The VNI does not start at a four-byte boundary, so an implementation cannot simply overlay a 32-bit word and mask it; it reads three bytes and assembles them.

#include <stdint.h>

struct vxlan_hdr {
    uint8_t  flags;        /* 0x08 = I bit, valid VNI follows */
    uint8_t  reserved1[3];
    uint8_t  vni[3];       /* big-endian, 24 bits */
    uint8_t  reserved2;
};

static inline uint32_t vxlan_vni(const struct vxlan_hdr *h)
{
    if (!(h->flags & 0x08))
        return 0;          /* caller must drop: no valid VNI */
    return ((uint32_t)h->vni[0] << 16) |
           ((uint32_t)h->vni[1] << 8)  |
            (uint32_t)h->vni[2];
}

Two details in that function are worth stating because they are the ones implementations get wrong. The three VNI bytes are in network order, so the assembly shifts the first byte left by sixteen, and there is no byte-swap of a 32-bit load. And the reserved fields are not part of the identifier: a receiver that masks the whole second word with 0x00ffffff would include the trailing reserved octet in the VNI and compute an identifier that no sender ever wrote.

Why twenty-four bits, and what the number costs

The field width is the answer to a specific capacity problem. An 802.1Q VLAN tag carries twelve bits of VLAN identifier, of which 0 and 4095 are reserved, leaving 4,094 usable values. In a data centre where every tenant needs isolation and each tenant may need several segments, 4,094 is not enough, and the shortage is not fixable within the tag format. Stacking tags, as 802.1ad does with an outer service tag, multiplies the space but consumes another four bytes of every frame and requires every intervening bridge to understand two tags.

Twenty-four bits gives 16,777,216 identifiers, a little over four thousand times the VLAN space, which is roughly the headroom a provider needs to give each tenant a handful of segments without ever reusing a number. The cost is three bytes per packet, which is less than the four bytes a second VLAN tag would add, and it is paid only on the encapsulated copy, not on the frame the guest sees. The specification does not carve out reserved values beyond the reserved bits, so the whole space is available to an allocation scheme, and it is the scheme rather than the format that has to keep two tenants from choosing the same number.

The identifier is global in a way the VLAN identifier is not. A VLAN tag is meaningful only on the link or within the bridged domain that carries it; the same value on two different links refers to two unrelated networks. A VNI is carried end to end through the tunnel and means the same thing at both endpoints. That global scope is what allows a virtual network to span hosts that share no Layer 2 connectivity, and it is also what makes the identifier allocation a fabric-wide concern rather than a per-switch one. Two independent administrative domains that both choose VNI 100 create two networks that collide the moment a tunnel is configured between them.

The inner frame keeps its own tag

Because the payload is a complete Ethernet frame, a guest can use 802.1Q inside the overlay. The tag travels inside the VNI, and the two identifiers compose: the VNI says which virtual network, the VLAN tag says which segment within it. A bridge at the tunnel endpoint must therefore decide, per frame, whether the inner tag is preserved or stripped before encapsulation, and the two choices produce different behaviour for the receiving side. Preserving it means the far endpoint sees a tagged frame and must be configured to accept that tag; stripping it means the receiving endpoint must re-add a tag from its own configuration. Neither is more correct, and mismatched choices on the two ends produce frames delivered to the wrong segment with no error anywhere.

The 802.1Q tag also interacts with the MTU arithmetic. An inner frame with a tag is four bytes longer than the same frame untagged, so an overlay that carries tagged frames has four more bytes of overhead than one that does not, on top of the fifty or seventy added by encapsulation. Tunnel encapsulation overhead and path MTU works through the arithmetic that follows from those bytes.

Reserved fields as a compatibility budget

The header has thirty-nine reserved bits: seven in the flags octet beside the I bit, the three octets between the flags and the VNI, and the final octet. In a protocol meant to last, reserved bits are a promise: senders set them to zero, receivers ignore them, and a later revision may define them without breaking anything already deployed. That promise has a limit, and VXLAN reached it.

The revision that became VXLAN-GPE takes part of that reserved space for a next-protocol field, so the payload need not be Ethernet and the header becomes a general encapsulation rather than a Layer 2 tunnel. A flag bit signals that the field is meaningful, which keeps the VNI in the same place and lets a receiver tell the two readings apart. It also uses a separate UDP destination port, so an endpoint can distinguish the two formats by the port alone rather than by inspecting the header. Geneve took the lesson further and made the option area explicit and length-prefixed from the start: its fixed header is the same eight bytes as VXLAN’s, and the metadata rides in a variable-length option region whose length field has to be validated before any option is read.

The general principle is that a fixed header with reserved bits is a bet that the protocol will not need to grow. When it does grow, the reserved bits become a version discriminator, and the deployed base splits into implementations that agree on the old meaning and implementations that agree on the new one, unless something outside the header, such as the destination port, separates the versions first. VXLAN-GPE bought that separation with a port; a header expected to carry metadata repeatedly would do better with a length-prefixed option list from the start.

Where the header is read

Nothing in the VXLAN header is examined by the underlay. A router between two endpoints sees UDP to port 4789 and forwards on the outer IP destination. It may hash the outer five-tuple for path selection, and it may apply a policy based on the port, but it does not look at the VNI and cannot enforce anything about it. Isolation between tenants is therefore a property of the endpoints, not of the network between them.

That placement has a security consequence worth naming plainly. Any host that can send a UDP datagram to an endpoint’s port 4789 can inject a frame into whatever VNI it names, and the receiving endpoint has no cryptographic way to tell that the packet did not come from a legitimate peer. Deployments handle this by restricting which source addresses may reach the tunnel port, by placing the tunnel endpoints behind a filtering boundary, or by authenticating the underlay with IPsec. The header format does not provide the protection and was not designed to. The overlay and underlay split explains why that division of responsibility is deliberate, and the limits it imposes on what the fabric can verify about a packet’s origin.

References

  1. RFC 7348: Virtual eXtensible Local Area Network (VXLAN) — IETF
  2. IEEE 802.1Q-2018: Bridges and Bridged Networks — IEEE
  3. RFC 8926: Geneve: Generic Network Virtualization Encapsulation — IETF