/* * OpenVPN -- An application to securely tunnel IP networks * over a single TCP/UDP port, with support for SSL/TLS-based * session authentication and key exchange, * packet encryption, packet authentication, and * packet compression. * * Copyright (C) 2002-2021 OpenVPN Technologies, Inc. * Copyright (C) 2010 Fabian Knittel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #elif defined(_MSC_VER) #include "config-msvc.h" #endif #include "syshead.h" #include "multi.h" #include "options.h" #include "vlan.h" /* * Retrieve the VLAN Identifier (VID) from the IEEE 802.1Q header. * * @param hdr Pointer to the Ethernet header with IEEE 802.1Q tagging. * @return Returns the VID in host byte order. */ static uint16_t vlanhdr_get_vid(const struct openvpn_8021qhdr *hdr) { return ntohs(hdr->pcp_cfi_vid & OPENVPN_8021Q_MASK_VID); } /* * Set the VLAN Identifier (VID) in an IEEE 802.1Q header. * * @param hdr Pointer to the Ethernet header with IEEE 802.1Q tagging. * @param vid The VID to set (in host byte order). */ static void vlanhdr_set_vid(struct openvpn_8021qhdr *hdr, const uint16_t vid) { hdr->pcp_cfi_vid = (hdr->pcp_cfi_vid & ~OPENVPN_8021Q_MASK_VID) | (htons(vid) & OPENVPN_8021Q_MASK_VID); } /* * vlan_decapsulate - remove 802.1q header and return VID * * For vlan_accept == VLAN_ONLY_UNTAGGED_OR_PRIORITY: * Only untagged frames and frames that are priority-tagged (VID == 0) are * accepted. (This means that VLAN-tagged frames are dropped.) For frames * that aren't dropped, the global vlan_pvid is returned as VID. * * For vlan_accept == VLAN_ONLY_TAGGED: * If a frame is VLAN-tagged the tagging is removed and the embedded VID is * returned. Any included priority information is lost. * If a frame isn't VLAN-tagged, the frame is dropped. * * For vlan_accept == VLAN_ALL: * Accepts both VLAN-tagged and untagged (or priority-tagged) frames and * and handles them as described above. * * @param c The global context. * @param buf The ethernet frame. * @return Returns -1 if the frame is dropped or the VID if it is accepted. */ int16_t vlan_decapsulate(const struct context *c, struct buffer *buf) { const struct openvpn_8021qhdr *vlanhdr; struct openvpn_ethhdr *ethhdr; uint16_t vid; /* assume untagged frame */ if (BLEN(buf) < sizeof(*ethhdr)) { goto drop; } ethhdr = (struct openvpn_ethhdr *)BPTR(buf); if (ethhdr->proto != htons(OPENVPN_ETH_P_8021Q)) { /* reject untagged frame */ if (c->options.vlan_accept == VLAN_ONLY_TAGGED) { msg(D_VLAN_DEBUG, "dropping frame without vlan-tag (proto/len 0x%04x)", ntohs(ethhdr->proto)); goto drop; } /* untagged frame is accepted and associated with the global VID */ msg(D_VLAN_DEBUG, "assuming pvid for frame without vlan-tag, pvid: %u (proto/len 0x%04x)", c->options.vlan_pvid, ntohs(ethhdr->proto)); return c->options.vlan_pvid; } /* tagged frame */ if (BLEN(buf) < sizeof(*vlanhdr)) { goto drop; } vlanhdr = (const struct openvpn_8021qhdr *)BPTR(buf); vid = vlanhdr_get_vid(vlanhdr); switch (c->options.vlan_accept) { case VLAN_ONLY_UNTAGGED_OR_PRIORITY: /* VLAN-tagged frame: drop packet */ if (vid != 0) { msg(D_VLAN_DEBUG, "dropping frame with vlan-tag, vid: %u (proto/len 0x%04x)", vid, ntohs(vlanhdr->proto)); goto drop; } /* vid == 0 means prio-tagged packet: don't drop and fall-through */ case VLAN_ONLY_TAGGED: case VLAN_ALL: /* tagged frame can be accepted: extract vid and strip encapsulation */ /* in case of prio-tagged frame (vid == 0), assume the sender * knows what he is doing and forward the packet as it is, so to * keep the priority information intact. */ if (vid == 0) { /* return the global VID for priority-tagged frames */ return c->options.vlan_pvid; } /* here we have a proper VLAN tagged frame: perform decapsulation * and return embedded VID */ msg(D_VLAN_DEBUG, "removing vlan-tag from frame: vid: %u, wrapped proto/len: 0x%04x", vid, ntohs(vlanhdr->proto)); /* save inner protocol to be restored later after decapsulation */ uint16_t proto = vlanhdr->proto; /* move the buffer head forward to adjust the headroom to a * non-tagged frame */ buf_advance(buf, SIZE_ETH_TO_8021Q_HDR); /* move the content of the 802.1q header to the new head, so that * src/dst addresses are copied over */ ethhdr = memmove(BPTR(buf), vlanhdr, sizeof(*ethhdr)); /* restore the inner protocol value */ ethhdr->proto = proto; return vid; } drop: buf->len = 0; return -1; } /* * vlan_encapsulate - add 802.1q header and set the context related VID * * Assumes vlan_accept == VLAN_ONLY_TAGGED * * @param c The current context. * @param buf The ethernet frame to encapsulate. */ void vlan_encapsulate(const struct context *c, struct buffer *buf) { const struct openvpn_ethhdr *ethhdr; struct openvpn_8021qhdr *vlanhdr; if (BLEN(buf) < sizeof(*ethhdr)) { goto drop; } ethhdr = (const struct openvpn_ethhdr *)BPTR(buf); if (ethhdr->proto == htons(OPENVPN_ETH_P_8021Q)) { /* Priority-tagged frame. (VLAN-tagged frames have been dropped before * getting to this point) */ /* Frame too small for header type? */ if (BLEN(buf) < sizeof(*vlanhdr)) { goto drop; } vlanhdr = (struct openvpn_8021qhdr *)BPTR(buf); /* sanity check: ensure this packet is really just prio-tagged */ uint16_t vid = vlanhdr_get_vid(vlanhdr); if (vid != 0) { goto drop; } } else { /* Untagged frame. */ /* Not enough head room for VLAN tag? */ if (buf_reverse_capacity(buf) < SIZE_ETH_TO_8021Q_HDR) { goto drop; } vlanhdr = (struct openvpn_8021qhdr *)buf_prepend(buf, SIZE_ETH_TO_8021Q_HDR); /* Initialise VLAN/802.1q header. * Move the Eth header so to keep dst/src addresses the same and then * assign the other fields. * * Also, save the inner protocol first, so that it can be restored later * after the memmove() */ uint16_t proto = ethhdr->proto; memmove(vlanhdr, ethhdr, sizeof(*ethhdr)); vlanhdr->tpid = htons(OPENVPN_ETH_P_8021Q); vlanhdr->pcp_cfi_vid = 0; vlanhdr->proto = proto; } /* set the VID corresponding to the current context (client) */ vlanhdr_set_vid(vlanhdr, c->options.vlan_pvid); msg(D_VLAN_DEBUG, "tagging frame: vid %u (wrapping proto/len: %04x)", c->options.vlan_pvid, vlanhdr->proto); return; drop: /* Drop the frame. */ buf->len = 0; } /* * vlan_is_tagged - check if a packet is VLAN-tagged * * Checks whether ethernet frame is VLAN-tagged. * * @param buf The ethernet frame. * @return Returns true if the frame is VLAN-tagged, false otherwise. */ bool vlan_is_tagged(const struct buffer *buf) { const struct openvpn_8021qhdr *vlanhdr; uint16_t vid; if (BLEN(buf) < sizeof(struct openvpn_8021qhdr)) { /* frame too small to be VLAN-tagged */ return false; } vlanhdr = (const struct openvpn_8021qhdr *)BPTR(buf); if (ntohs(vlanhdr->tpid) != OPENVPN_ETH_P_8021Q) { /* non tagged frame */ return false; } vid = vlanhdr_get_vid(vlanhdr); if (vid == 0) { /* no vid: piority tagged only */ return false; } return true; } void vlan_process_outgoing_tun(struct multi_context *m, struct multi_instance *mi) { if (!m->top.options.vlan_tagging) { return; } if (m->top.options.vlan_accept == VLAN_ONLY_UNTAGGED_OR_PRIORITY) { /* Packets forwarded to the TAP devices aren't VLAN-tagged. Only packets * matching the PVID configured globally are allowed to be received */ if (m->top.options.vlan_pvid != mi->context.options.vlan_pvid) { /* Packet is coming from the wrong VID, drop it. */ mi->context.c2.to_tun.len = 0; } } else if (m->top.options.vlan_accept == VLAN_ALL) { /* Packets either need to be VLAN-tagged or not, depending on the * packet's originating VID and the port's native VID (PVID). */ if (m->top.options.vlan_pvid != mi->context.options.vlan_pvid) { /* Packets need to be VLAN-tagged, because the packet's VID does not * match the port's PVID. */ vlan_encapsulate(&mi->context, &mi->context.c2.to_tun); } } else if (m->top.options.vlan_accept == VLAN_ONLY_TAGGED) { /* All packets on the port (the tap device) need to be VLAN-tagged. */ vlan_encapsulate(&mi->context, &mi->context.c2.to_tun); } }