summaryrefslogtreecommitdiff
path: root/src/openvpn/vlan.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/openvpn/vlan.c')
-rw-r--r--src/openvpn/vlan.c333
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..573a990
--- /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-2021 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);
+ }
+}