blob: dd8d7c17c4b1357d739a25640b3adc02887ab239 [file] [log] [blame]
/*
* 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);
}
}