blob: 9ae80754f3af2a55ec1a89bf63ed8681feb437f5 [file] [log] [blame]
Zack Williams41513bf2018-07-07 20:08:35 -07001# Copyright 2017-present Open Networking Foundation
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
Zsolt Haraszti91350eb2016-11-05 15:33:53 -070014"""
15AF_PACKET receive support
16
17When VLAN offload is enabled on the NIC Linux will not deliver the VLAN tag
18in the data returned by recv. Instead, it delivers the VLAN TCI in a control
19message. Python 2.x doesn't have built-in support for recvmsg, so we have to
20use ctypes to call it. The recv function exported by this module reconstructs
21the VLAN tag if it was offloaded.
22"""
23
24import struct
25from ctypes import *
26
27ETH_P_8021Q = 0x8100
28SOL_PACKET = 263
29PACKET_AUXDATA = 8
30TP_STATUS_VLAN_VALID = 1 << 4
31
32class struct_iovec(Structure):
33 _fields_ = [
34 ("iov_base", c_void_p),
35 ("iov_len", c_size_t),
36 ]
37
38class struct_msghdr(Structure):
39 _fields_ = [
40 ("msg_name", c_void_p),
41 ("msg_namelen", c_uint32),
42 ("msg_iov", POINTER(struct_iovec)),
43 ("msg_iovlen", c_size_t),
44 ("msg_control", c_void_p),
45 ("msg_controllen", c_size_t),
46 ("msg_flags", c_int),
47 ]
48
49class struct_cmsghdr(Structure):
50 _fields_ = [
51 ("cmsg_len", c_size_t),
52 ("cmsg_level", c_int),
53 ("cmsg_type", c_int),
54 ]
55
56class struct_tpacket_auxdata(Structure):
57 _fields_ = [
58 ("tp_status", c_uint),
59 ("tp_len", c_uint),
60 ("tp_snaplen", c_uint),
61 ("tp_mac", c_ushort),
62 ("tp_net", c_ushort),
63 ("tp_vlan_tci", c_ushort),
64 ("tp_padding", c_ushort),
65 ]
66
67libc = CDLL("libc.so.6")
68recvmsg = libc.recvmsg
69recvmsg.argtypes = [c_int, POINTER(struct_msghdr), c_int]
70recvmsg.retype = c_int
71
72def enable_auxdata(sk):
73 """
74 Ask the kernel to return the VLAN tag in a control message
75
76 Must be called on the socket before afpacket.recv.
77 """
78 sk.setsockopt(SOL_PACKET, PACKET_AUXDATA, 1)
79
80def recv(sk, bufsize):
81 """
82 Receive a packet from an AF_PACKET socket
83 @sk Socket
84 @bufsize Maximum packet size
85 """
86 buf = create_string_buffer(bufsize)
87
88 ctrl_bufsize = sizeof(struct_cmsghdr) + sizeof(struct_tpacket_auxdata) + sizeof(c_size_t)
89 ctrl_buf = create_string_buffer(ctrl_bufsize)
90
91 iov = struct_iovec()
92 iov.iov_base = cast(buf, c_void_p)
93 iov.iov_len = bufsize
94
95 msghdr = struct_msghdr()
96 msghdr.msg_name = None
97 msghdr.msg_namelen = 0
98 msghdr.msg_iov = pointer(iov)
99 msghdr.msg_iovlen = 1
100 msghdr.msg_control = cast(ctrl_buf, c_void_p)
101 msghdr.msg_controllen = ctrl_bufsize
102 msghdr.msg_flags = 0
103
104 rv = recvmsg(sk.fileno(), byref(msghdr), 0)
105 if rv < 0:
106 raise RuntimeError("recvmsg failed: rv=%d", rv)
107
108 # The kernel only delivers control messages we ask for. We
109 # only enabled PACKET_AUXDATA, so we can assume it's the
110 # only control message.
111 assert msghdr.msg_controllen >= sizeof(struct_cmsghdr)
112
113 cmsghdr = struct_cmsghdr.from_buffer(ctrl_buf) # pylint: disable=E1101
114 assert cmsghdr.cmsg_level == SOL_PACKET
115 assert cmsghdr.cmsg_type == PACKET_AUXDATA
116
117 auxdata = struct_tpacket_auxdata.from_buffer(ctrl_buf, sizeof(struct_cmsghdr)) # pylint: disable=E1101
118
119 if auxdata.tp_vlan_tci != 0 or auxdata.tp_status & TP_STATUS_VLAN_VALID:
120 # Insert VLAN tag
121 tag = struct.pack("!HH", ETH_P_8021Q, auxdata.tp_vlan_tci)
122 return buf.raw[:12] + tag + buf.raw[12:rv]
123 else:
124 return buf.raw[:rv]