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