blob: 340fc38d98b9e897440e778667b4921048fc77e5 [file] [log] [blame]
Zsolt Haraszti91350eb2016-11-05 15:33:53 -07001"""
2AF_PACKET receive support
3
4When VLAN offload is enabled on the NIC Linux will not deliver the VLAN tag
5in the data returned by recv. Instead, it delivers the VLAN TCI in a control
6message. Python 2.x doesn't have built-in support for recvmsg, so we have to
7use ctypes to call it. The recv function exported by this module reconstructs
8the VLAN tag if it was offloaded.
9"""
10
11import struct
12from ctypes import *
13
14ETH_P_8021Q = 0x8100
15SOL_PACKET = 263
16PACKET_AUXDATA = 8
17TP_STATUS_VLAN_VALID = 1 << 4
18
19class struct_iovec(Structure):
20 _fields_ = [
21 ("iov_base", c_void_p),
22 ("iov_len", c_size_t),
23 ]
24
25class struct_msghdr(Structure):
26 _fields_ = [
27 ("msg_name", c_void_p),
28 ("msg_namelen", c_uint32),
29 ("msg_iov", POINTER(struct_iovec)),
30 ("msg_iovlen", c_size_t),
31 ("msg_control", c_void_p),
32 ("msg_controllen", c_size_t),
33 ("msg_flags", c_int),
34 ]
35
36class struct_cmsghdr(Structure):
37 _fields_ = [
38 ("cmsg_len", c_size_t),
39 ("cmsg_level", c_int),
40 ("cmsg_type", c_int),
41 ]
42
43class struct_tpacket_auxdata(Structure):
44 _fields_ = [
45 ("tp_status", c_uint),
46 ("tp_len", c_uint),
47 ("tp_snaplen", c_uint),
48 ("tp_mac", c_ushort),
49 ("tp_net", c_ushort),
50 ("tp_vlan_tci", c_ushort),
51 ("tp_padding", c_ushort),
52 ]
53
54libc = CDLL("libc.so.6")
55recvmsg = libc.recvmsg
56recvmsg.argtypes = [c_int, POINTER(struct_msghdr), c_int]
57recvmsg.retype = c_int
58
59def enable_auxdata(sk):
60 """
61 Ask the kernel to return the VLAN tag in a control message
62
63 Must be called on the socket before afpacket.recv.
64 """
65 sk.setsockopt(SOL_PACKET, PACKET_AUXDATA, 1)
66
67def recv(sk, bufsize):
68 """
69 Receive a packet from an AF_PACKET socket
70 @sk Socket
71 @bufsize Maximum packet size
72 """
73 buf = create_string_buffer(bufsize)
74
75 ctrl_bufsize = sizeof(struct_cmsghdr) + sizeof(struct_tpacket_auxdata) + sizeof(c_size_t)
76 ctrl_buf = create_string_buffer(ctrl_bufsize)
77
78 iov = struct_iovec()
79 iov.iov_base = cast(buf, c_void_p)
80 iov.iov_len = bufsize
81
82 msghdr = struct_msghdr()
83 msghdr.msg_name = None
84 msghdr.msg_namelen = 0
85 msghdr.msg_iov = pointer(iov)
86 msghdr.msg_iovlen = 1
87 msghdr.msg_control = cast(ctrl_buf, c_void_p)
88 msghdr.msg_controllen = ctrl_bufsize
89 msghdr.msg_flags = 0
90
91 rv = recvmsg(sk.fileno(), byref(msghdr), 0)
92 if rv < 0:
93 raise RuntimeError("recvmsg failed: rv=%d", rv)
94
95 # The kernel only delivers control messages we ask for. We
96 # only enabled PACKET_AUXDATA, so we can assume it's the
97 # only control message.
98 assert msghdr.msg_controllen >= sizeof(struct_cmsghdr)
99
100 cmsghdr = struct_cmsghdr.from_buffer(ctrl_buf) # pylint: disable=E1101
101 assert cmsghdr.cmsg_level == SOL_PACKET
102 assert cmsghdr.cmsg_type == PACKET_AUXDATA
103
104 auxdata = struct_tpacket_auxdata.from_buffer(ctrl_buf, sizeof(struct_cmsghdr)) # pylint: disable=E1101
105
106 if auxdata.tp_vlan_tci != 0 or auxdata.tp_status & TP_STATUS_VLAN_VALID:
107 # Insert VLAN tag
108 tag = struct.pack("!HH", ETH_P_8021Q, auxdata.tp_vlan_tci)
109 return buf.raw[:12] + tag + buf.raw[12:rv]
110 else:
111 return buf.raw[:rv]