I know how to create a tagged interface in linux using vconfig like eth1.10 which makes it receive and send tagged packets. Is it possible to create an interface which receives and sends untagged packet for vlan 10?
An untagged interface already exists: it's the parent interface, that is,
eth1 in this case.
Sending packets through
eth1.10 results in those packets being tagged while packets received with VLAN tag 10 come in through that interface.
Sending packets through
eth1 results in those packets being untagged while packets received without a VLAN tag come in through that interface.
There is no such thing as "sending untagged packets for vlan 10". Untagged packets don't have any vlan tag, by definition.
I've found the following method to work.
- Use TC to match packets without 802.1q on ingres and push a vlan header with id 0
- Use TC to match packets in vlan 0 on egress, and pop the vlan header
- Create the untagged logical interface with type=vlan and id=0
Here's an example:
$ tc qdisc del dev eth0 root $ tc qdisc add dev eth0 handle 1: root prio $ tc filter add dev eth0 parent 1: protocol 802.1q basic match 'meta(vlan eq 0)' action vlan pop $ tc qdisc del dev eth0 ingress $ tc qdisc add dev eth0 handle ffff: ingress $ tc filter add dev eth0 parent ffff: protocol all basic match 'not meta(protocol eq 0x8100)' action vlan push id 0 $ ip link add eth0-vlan0 type vlan id 0 $ ip link set eth0-vlan0 up $ ip addr add 192.168.10.253/24 dev eth0-vlan0 peer$ ping 192.168.10.253 PING 192.168.10.253 (192.168.10.253) 56(84) bytes of data. 64 bytes from 192.168.10.253: icmp_seq=1 ttl=64 time=0.209 ms 64 bytes from 192.168.10.253: icmp_seq=2 ttl=64 time=0.188 ms ^C
Note that adding the prio qdisc on egress (known as "root" above) disables the default mq discipline, which divides packets according to number of transmit queues on the NIC in order to allow multiple CPU threads to push packets to the different TX queues. To restore the default behavior, use mqprio as follows:
$ tx_q_count=$(ls -d /sys/class/net/eth0/queues/tx-* | wc -l) $ tc qdisc del dev eth0 root $ tc qdisc add dev eth0 handle 1: root prio bands $tx_q_count multiqueue $ tc filter add dev eth0 parent 1: protocol 802.1q basic match 'meta(vlan eq 0)' action vlan pop $ for i in $(seq 1 $tx_q_count); do tc qdisc add dev eth0 parent 1:$i pfifo_fast; done $ tc qdisc del dev eth0 ingress $ tc qdisc add dev eth0 handle ffff: ingress $ tc filter add dev eth0 parent ffff: protocol all basic match 'not meta(protocol eq 0x8100)' action vlan push id 0 $ ip link add eth0-vlan0 type vlan id 0 $ ip link set eth0-vlan0 up $ ip addr add 192.168.10.253/24 dev eth0-vlan0 peer$ ping 192.168.10.253 PING 192.168.10.253 (192.168.10.253) 56(84) bytes of data. 64 bytes from 192.168.10.253: icmp_seq=1 ttl=64 time=0.230 ms 64 bytes from 192.168.10.253: icmp_seq=2 ttl=64 time=0.198 ms ^C
A different solution that also works is to add a software bridge next to the vlan logical interfaces on the physical interface. The software bridge has a vlan filtering feature that allows limited vlan remapping (pvid->vid on ingres and vid->pvid on egress, but not full remapping) that can be used. Here's an example:
$ ip link add eth0-vlan0 type bridge $ echo 0 > /sys/class/net/eth0-vlan0/bridge/default_pvid $ echo 1 > /sys/class/net/eth0-vlan0/bridge/vlan_filtering $ ip link set dev eth0 master eth0-vlan0 $ bridge vlan add vid 4094 dev eth0 pvid untagged $ bridge vlan add vid 4094 dev eth0-vlan0 untagged pvid self $ ip link add eth0-vlan4094 link eth0 type vlan id 4094 # ... BEGIN: only if you intend to use vlan 4094 ... $ ip link set eth0-vlan4094 up $ ip addr add 10.10.40.253/24 dev eth0-vlan4094 # ... END ... $ ip link set eth0-vlan0 up $ ip addr add 192.168.10.253/24 dev eth0-vlan0 peer$ ping 192.168.10.253 PING 192.168.10.253 (192.168.10.253) 56(84) bytes of data. 64 bytes from 192.168.10.253: icmp_seq=1 ttl=64 time=0.238 ms 64 bytes from 192.168.10.253: icmp_seq=2 ttl=64 time=0.215 ms ^C
You need the VLAN interface matching the VID number used inside the bridge, because the VLAN filtering feature is a bit so-so. It accepts both untagged frames using the pvid->vid mapping, buuut it also accepts frames with a VLAN tag on wire matching the remapped vid. The VLAN interface eats up those frames instead. Leave it in the down state if you do not need that particular VLAN.
The software bridge has recently gained a "default_pvid" feature which makes it work like cheap hardware switches: it is vlan aware, but will per default do a pvid->vid mapping to vid 1, and allow vid 1 on all ports, so that you can plug the switch in and start using ports for untagged traffic without a single configuration directive.
Finally, a more modern solution would be to use tc_clsact and write a small BPF program to push/pop the tags for the PVID. Works just like the TC example above, except that some NICs allow hardware offload of BPF programs, so it could be more performant.
As mentioned in comments and another answer, there isn't specifically a way to send untagged packets belonging to a specific VLAN, because untagged packets have no way to describe which VLAN they belong to due to lack of a tag. As also mentioned, the parent interface (eth1 here) will send untagged packets.
However, this does not mean that untagged packets sent from your linux box can't belong to a particular VLAN on your network. It is up to the switch to assign untagged packets arriving on a given port to a particular VLAN. Typically, switches (which are capable of VLANs) will, by default, assign untagged packets to the "default VLAN (1)". However by changing the configuration on the switch, you can instruct it to put untagged packets received onto VLAN 10, and route them wherever else VLAN 10 is assigned.
If VLAN 10 is set to tagged on a different port, those same packets will come out tagged there if that's the destination of the packet and untagged on a different port that is set to untagged for VLAN 10
# ip link add link eth0 name untagged type vlan id 0
seems to do the trick.
tcpdump on the parent interface (eth0) shows untagged packets when you ping -I untagged (some-ip).
(tcpdump on the new logical interface should show only untagged traffic, while tcpdump on the parent interface should show all traffic, tagged and untagged, with 802.1q headers for the tagged stuff.)
you may need to disable ARP on the parent interface, so that untagged ARP replies are not put in the ARP table relative to the parent interface:
# ip link set eth0 arp off # ip link set untagged arp on
i would also disable IPv6 link-local addresses on the parent interface, and enable on the untagged logical interface, for good measure.
you can up/down the untagged interface without killing the whole parent interface (tagged traffic included) etc.
You can create a bridge between a physical interface (eth0) and a vlan interface (eth1:10). This bridge will then allow untagged packets from the physical interface (eth0) to communicate with the tagged network on the vlan interface (eth1:10).
I am using exactly this constellation. Whether this would work if the bridge was created over a parent interface (eth0) and its own subordinate vlan interface (eth0:10) I cannot say...
Here's the solution using eBPF.
To avoid having to install bcc and compile the code from source, here's the binary:
$ base64 -d > tag-native-vlan0.o f0VMRgIBAQAAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAALABAAAAAAAAAAAAAEAAAAAA AEAABwABAGESFAAAAAAAVQIDAAAAAAC3AgAAAIEAALcDAAAAAAAAhQAAABIAAAC3AAAAAAAAAJUA AAAAAAAAYRIUAAAAAAAVAgMAAAAAAGESGAAAAAAAVQIBAAAAAACFAAAAEwAAALcAAAAAAAAAlQAA AAAAAABHUEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF4AAAAAAAMAKAAAAAAAAAAAAAAA AAAAAFcAAAAAAAQAKAAAAAAAAAAAAAAAAAAAAD0AAAAQAAUAAAAAAAAAAAAAAAAAAAAAACMAAAAQ AAQAAAAAAAAAAAAAAAAAAAAAAAcAAAAQAAMAAAAAAAAAAAAAAAAAAAAAAAAudGV4dABjbHNhY3Rf aW5ncmVzcwBwdmlkX2luZ3Jlc3MAY2xzYWN0X2VncmVzcwBwdmlkX2VncmVzcwBfX2xpY2Vuc2UA LnN0cnRhYgAuc3ltdGFiAExCQjFfMwBMQkIwXzIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEcAAAADAAAAAAAAAAAAAAAA AAAAAAAAAEgBAAAAAAAAZQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAYA AAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAFgAA AAEAAAAGAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAA4AAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAA AAAAADEAAAABAAAABgAAAAAAAAAAAAAAAAAAAHgAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAgAAAAA AAAAAAAAAAAAAAA/AAAAAQAAAAMAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAQAAAAAAAAAAAAAAAAA AAABAAAAAAAAAAAAAAAAAAAATwAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAuAAAAAAAAACQAAAAAAAA AAEAAAADAAAACAAAAAAAAAAYAAAAAAAAAA== ^D
I've set the PVID to map to VID 0 instead of VID 10, for no particular reason.
(To change it to VID 10, use a disassembler and assembler, eg. ubpf or llvm-objdump, on the .o file, and change the VLAN tag. You can also talk to eBPF from user space, so it should be possible to make the pvid reconfigurable at runtime if need be.)
Make sure to run something like kernel 4.8.0 (
uname -a) and iproute2 4.9.0 (
Add the eBPF to the physical interface:
$ tc qdisc add dev eth0 clsact $ tc filter add dev eth0 ingress bpf object-file tag-native-vlan0.o section pvid_ingress $ tc filter add dev eth0 egress bpf object-file tag-native-vlan0.o section pvid_egress
Create a logical interface:
$ ip link add eth0-vlan0 link eth0 type vlan id 0 $ ip link set eth0-vlan0 up $ ip addr add 192.168.10.253/24 dev eth0-vlan0
L3 is configured on the logical interface, and the physical interface is used for L2.
Test from a connected box:
peer$ ping 192.168.10.253 PING 192.168.10.253 (192.168.10.253) 56(84) bytes of data. 64 bytes from 192.168.10.253: icmp_seq=1 ttl=64 time=0.380 ms 64 bytes from 192.168.10.253: icmp_seq=2 ttl=64 time=0.240 ms ^C
And there you have it, an "untagged interface on Linux".