This is old, but this still hasn't been solved:
When VLAN offload is enabled on the NIC Linux will not deliver the VLAN tag in the data returned by recv. Instead, it delivers the VLAN TCI in a control message.
To get the control fields, you need to use PACKET_AUXDATA
, which will add the following to ancillary data:
struct tpacket_auxdata {
__u32 tp_status;
__u32 tp_len; /* packet length */
__u32 tp_snaplen; /* captured length */
__u16 tp_mac;
__u16 tp_net;
__u16 tp_vlan_tci;
__u16 tp_padding;
};
For instance
sock.setsockopt(SOL_PACKET, PACKET_AUXDATA, 1)
Once that is done, use recvmsg()
and iterate through the ancillary data looking for PACKET_AUXDATA
. Get tp_vlan_tci out of it, and inject this back into the packet data.
Examples of code
https://github.com/the-tcpdump-group/libpcap/blob/3a7962215f4f3b13ac792ad0e0487a53c0f1178e/pcap-linux.c#L1756
import ctypes, socket
# From bits/socket.h
SOL_PACKET = 263
# From asm/socket.h
SO_ATTACH_FILTER = 26
ETH_P_8021Q = 0x8100
TP_STATUS_VLAN_VALID = 1 << 4
class tpacket_auxdata(ctypes.Structure):
_fields_ = [
("tp_status", ctypes.c_uint),
("tp_len", ctypes.c_uint),
("tp_snaplen", ctypes.c_uint),
("tp_mac", ctypes.c_ushort),
("tp_net", ctypes.c_ushort),
("tp_vlan_tci", ctypes.c_ushort),
("tp_padding", ctypes.c_ushort),
]
def _recv_raw(sock, x=65535):
"""Internal function to receive a Packet,
and process ancillary data.
"""
flags_len = socket.CMSG_LEN(4096)
pkt, ancdata, flags, sa_ll = sock.recvmsg(x, flags_len)
if not pkt:
return pkt, sa_ll
for cmsg_lvl, cmsg_type, cmsg_data in ancdata:
# Check available ancillary data
if (cmsg_lvl == SOL_PACKET and cmsg_type == PACKET_AUXDATA):
# Parse AUXDATA
auxdata = tpacket_auxdata.from_buffer_copy(cmsg_data)
if auxdata.tp_vlan_tci != 0 or \
auxdata.tp_status & TP_STATUS_VLAN_VALID:
# Insert VLAN tag
tag = struct.pack(
"!HH",
ETH_P_8021Q,
auxdata.tp_vlan_tci
)
pkt = pkt[:12] + tag + pkt[12:]
return pkt
(From https://github.com/secdev/scapy)