# Copyright 2014 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Python wrapper for C socket calls and data structures.""" import ctypes import ctypes.util import os import re import socket import struct import cstruct import util # Data structures. # These aren't constants, they're classes. So, pylint: disable=invalid-name CMsgHdr = cstruct.Struct("cmsghdr", "@Lii", "len level type") Iovec = cstruct.Struct("iovec", "@PL", "base len") MsgHdr = cstruct.Struct("msghdr", "@LLPLPLi", "name namelen iov iovlen control msg_controllen flags") SockaddrIn = cstruct.Struct("sockaddr_in", "=HH4sxxxxxxxx", "family port addr") SockaddrIn6 = cstruct.Struct("sockaddr_in6", "=HHI16sI", "family port flowinfo addr scope_id") SockaddrStorage = cstruct.Struct("sockaddr_storage", "=H126s", "family data") SockExtendedErr = cstruct.Struct("sock_extended_err", "@IBBBxII", "errno origin type code info data") InPktinfo = cstruct.Struct("in_pktinfo", "@i4s4s", "ifindex spec_dst addr") In6Pktinfo = cstruct.Struct("in6_pktinfo", "@16si", "addr ifindex") # Constants. # IPv4 socket options and cmsg types. IP_TTL = 2 IP_MTU_DISCOVER = 10 IP_PKTINFO = 8 IP_RECVERR = 11 IP_RECVTTL = 12 IP_MTU = 14 # IPv6 socket options and cmsg types. IPV6_MTU_DISCOVER = 23 IPV6_RECVERR = 25 IPV6_RECVPKTINFO = 49 IPV6_PKTINFO = 50 IPV6_RECVHOPLIMIT = 51 IPV6_HOPLIMIT = 52 IPV6_PATHMTU = 61 IPV6_DONTFRAG = 62 # PMTUD values. IP_PMTUDISC_DO = 1 CMSG_ALIGNTO = struct.calcsize("@L") # The kernel defines this as sizeof(long). # Sendmsg flags MSG_CONFIRM = 0X800 MSG_ERRQUEUE = 0x2000 # Linux errqueue API. SO_ORIGIN_ICMP = 2 SO_ORIGIN_ICMP6 = 3 # Find the C library. libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True) # TODO: Unlike most of this file, these functions aren't specific to wrapping C # library calls. Move them to a utils.py or constants.py file, once we have one. def LinuxVersion(): # Example: "3.4.67-00753-gb7a556f", "4.4.135+". # Get the prefix consisting of digits and dots. version = re.search("^[0-9.]*", os.uname()[2]).group() # Convert it into a tuple such as (3, 4, 67). That allows comparing versions # using < and >, since tuples are compared lexicographically. version = tuple(int(i) for i in version.split(".")) return version def AddressVersion(addr): return 6 if ":" in addr else 4 def SetSocketTimeout(sock, ms): s = ms / 1000 us = (ms % 1000) * 1000 sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, struct.pack("LL", s, us)) def VoidPointer(s): return ctypes.cast(s.CPointer(), ctypes.c_void_p) def MaybeRaiseSocketError(ret): if ret < 0: errno = ctypes.get_errno() raise socket.error(errno, os.strerror(errno)) def Sockaddr(addr): if ":" in addr[0]: family = socket.AF_INET6 if len(addr) == 4: addr, port, flowinfo, scope_id = addr else: (addr, port), flowinfo, scope_id = addr, 0, 0 addr = socket.inet_pton(family, addr) return SockaddrIn6((family, socket.ntohs(port), socket.ntohl(flowinfo), addr, scope_id)) else: family = socket.AF_INET addr, port = addr addr = socket.inet_pton(family, addr) return SockaddrIn((family, socket.ntohs(port), addr)) def _MakeMsgControl(optlist): """Creates a msg_control blob from a list of cmsg attributes. Takes a list of cmsg attributes. Each attribute is a tuple of: - level: An integer, e.g., SOL_IPV6. - type: An integer, the option identifier, e.g., IPV6_HOPLIMIT. - data: The option data. This is either a string or an integer. If it's an integer it will be written as an unsigned integer in host byte order. If it's a string, it's used as is. Data is padded to an integer multiple of CMSG_ALIGNTO. Args: optlist: A list of tuples describing cmsg options. Returns: A string, a binary blob usable as the control data for a sendmsg call. Raises: TypeError: Option data is neither an integer nor a string. """ msg_control = "" for i, opt in enumerate(optlist): msg_level, msg_type, data = opt if isinstance(data, int): data = struct.pack("=I", data) elif isinstance(data, ctypes.c_uint32): data = struct.pack("=I", data.value) elif not isinstance(data, str): raise TypeError("unknown data type for opt (%d, %d): %s" % ( msg_level, msg_type, type(data))) datalen = len(data) msg_len = len(CMsgHdr) + datalen padding = "\x00" * util.GetPadLength(CMSG_ALIGNTO, datalen) msg_control += CMsgHdr((msg_len, msg_level, msg_type)).Pack() msg_control += data + padding return msg_control def _ParseMsgControl(buf): """Parse a raw control buffer into a list of tuples.""" msglist = [] while len(buf) > 0: cmsghdr, buf = cstruct.Read(buf, CMsgHdr) datalen = cmsghdr.len - len(CMsgHdr) padlen = util.GetPadLength(CMSG_ALIGNTO, datalen) data, buf = buf[:datalen], buf[padlen + datalen:] if cmsghdr.level == socket.IPPROTO_IP: if cmsghdr.type == IP_PKTINFO: data = InPktinfo(data) elif cmsghdr.type == IP_TTL: data = struct.unpack("@I", data)[0] if cmsghdr.level == socket.IPPROTO_IPV6: if cmsghdr.type == IPV6_PKTINFO: data = In6Pktinfo(data) elif cmsghdr.type == IPV6_RECVERR: err, source = cstruct.Read(data, SockExtendedErr) if err.origin == SO_ORIGIN_ICMP6: source, pad = cstruct.Read(source, SockaddrIn6) data = (err, source) elif cmsghdr.type == IPV6_HOPLIMIT: data = struct.unpack("@I", data)[0] # If not, leave data as just the raw bytes. msglist.append((cmsghdr.level, cmsghdr.type, data)) return msglist def Bind(s, to): """Python wrapper for bind.""" ret = libc.bind(s.fileno(), VoidPointer(to), len(to)) MaybeRaiseSocketError(ret) return ret def Connect(s, to): """Python wrapper for connect.""" ret = libc.connect(s.fileno(), VoidPointer(to), len(to)) MaybeRaiseSocketError(ret) return ret def Sendmsg(s, to, data, control, flags): """Python wrapper for sendmsg. Args: s: A Python socket object. Becomes sockfd. to: An address tuple, or a SockaddrIn[6] struct. Becomes msg->msg_name. data: A string, the data to write. Goes into msg->msg_iov. control: A list of cmsg options. Becomes msg->msg_control. flags: An integer. Becomes msg->msg_flags. Returns: If sendmsg succeeds, returns the number of bytes written as an integer. Raises: socket.error: If sendmsg fails. """ # Create ctypes buffers and pointers from our structures. We need to hang on # to the underlying Python objects, because we don't want them to be garbage # collected and freed while we have C pointers to them. # Convert the destination address into a struct sockaddr. if to: if isinstance(to, tuple): to = Sockaddr(to) msg_name = to.CPointer() msg_namelen = len(to) else: msg_name = 0 msg_namelen = 0 # Convert the data to a data buffer and a struct iovec pointing at it. if data: databuf = ctypes.create_string_buffer(data) iov = Iovec((ctypes.addressof(databuf), len(data))) msg_iov = iov.CPointer() msg_iovlen = 1 else: msg_iov = 0 msg_iovlen = 0 # Marshal the cmsg options. if control: control = _MakeMsgControl(control) controlbuf = ctypes.create_string_buffer(control) msg_control = ctypes.addressof(controlbuf) msg_controllen = len(control) else: msg_control = 0 msg_controllen = 0 # Assemble the struct msghdr. msghdr = MsgHdr((msg_name, msg_namelen, msg_iov, msg_iovlen, msg_control, msg_controllen, flags)).Pack() # Call sendmsg. ret = libc.sendmsg(s.fileno(), msghdr, 0) MaybeRaiseSocketError(ret) return ret def _ToSocketAddress(addr, alen): addr = addr[:alen] # Attempt to convert the address to something we understand. if alen == 0: return None elif alen == len(SockaddrIn) and SockaddrIn(addr).family == socket.AF_INET: return SockaddrIn(addr) elif alen == len(SockaddrIn6) and SockaddrIn6(addr).family == socket.AF_INET6: return SockaddrIn6(addr) elif alen == len(SockaddrStorage): # Can this ever happen? return SockaddrStorage(addr) else: return addr # Unknown or malformed. Return the raw bytes. def Recvmsg(s, buflen, controllen, flags, addrlen=len(SockaddrStorage)): """Python wrapper for recvmsg. Args: s: A Python socket object. Becomes sockfd. buflen: An integer, the maximum number of bytes to read. addrlen: An integer, the maximum size of the source address. controllen: An integer, the maximum size of the cmsg buffer. Returns: A tuple of received bytes, socket address tuple, and cmg list. Raises: socket.error: If recvmsg fails. """ addr = ctypes.create_string_buffer(addrlen) msg_name = ctypes.addressof(addr) msg_namelen = addrlen buf = ctypes.create_string_buffer(buflen) iov = Iovec((ctypes.addressof(buf), buflen)) msg_iov = iov.CPointer() msg_iovlen = 1 control = ctypes.create_string_buffer(controllen) msg_control = ctypes.addressof(control) msg_controllen = controllen msghdr = MsgHdr((msg_name, msg_namelen, msg_iov, msg_iovlen, msg_control, msg_controllen, flags)) ret = libc.recvmsg(s.fileno(), VoidPointer(msghdr), flags) MaybeRaiseSocketError(ret) data = buf.raw[:ret] msghdr = MsgHdr(str(msghdr._buffer.raw)) addr = _ToSocketAddress(addr, msghdr.namelen) control = control.raw[:msghdr.msg_controllen] msglist = _ParseMsgControl(control) return data, addr, msglist def Recvfrom(s, size, flags=0): """Python wrapper for recvfrom.""" buf = ctypes.create_string_buffer(size) addr = ctypes.create_string_buffer(len(SockaddrStorage)) alen = ctypes.c_int(len(addr)) ret = libc.recvfrom(s.fileno(), buf, len(buf), flags, addr, ctypes.byref(alen)) MaybeRaiseSocketError(ret) data = buf[:ret] alen = alen.value addr = _ToSocketAddress(addr.raw, alen) return data, addr def Setsockopt(s, level, optname, optval, optlen): """Python wrapper for setsockopt. Mostly identical to the built-in setsockopt, but allows passing in arbitrary binary blobs, including NULL options, which the built-in python setsockopt does not allow. Args: s: The socket object on which to set the option. level: The level parameter. optname: The option to set. optval: A raw byte string, the value to set the option to (None for NULL). optlen: An integer, the length of the option. Raises: socket.error: if setsockopt fails. """ ret = libc.setsockopt(s.fileno(), level, optname, optval, optlen) MaybeRaiseSocketError(ret)