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