diff options
author | Bernhard Schmidt <berni@debian.org> | 2020-09-01 16:52:17 +0200 |
---|---|---|
committer | Bernhard Schmidt <berni@debian.org> | 2020-09-01 16:52:17 +0200 |
commit | 9fc3b98112217f2d92a67977dbde0987cc7a1803 (patch) | |
tree | 29fcc8654ee65d9dd89ade797bea2f3d9dfd9cfd /src/openvpn/vlan.c | |
parent | a8758c0e03eed188dcb9da0e4fd781a67c25bf1e (diff) | |
parent | 69b02b1f7fd609d84ace13ab04697158de2418a9 (diff) |
Merge branch 'debian/experimental-2.5'
Diffstat (limited to 'src/openvpn/vlan.c')
-rw-r--r-- | src/openvpn/vlan.c | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/src/openvpn/vlan.c b/src/openvpn/vlan.c new file mode 100644 index 0000000..dd8d7c1 --- /dev/null +++ b/src/openvpn/vlan.c @@ -0,0 +1,333 @@ +/* + * 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-2019 OpenVPN Technologies, Inc. <sales@openvpn.net> + * Copyright (C) 2010 Fabian Knittel <fabian.knittel@lettink.de> + * + * 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); + } +} |