182 lines
5.5 KiB
Python
182 lines
5.5 KiB
Python
# 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 socket
|
|
import struct
|
|
|
|
import cstruct
|
|
|
|
|
|
# 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", "@LL", "base len")
|
|
MsgHdr = cstruct.Struct("msghdr", "@LLLLLLi",
|
|
"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")
|
|
|
|
# Constants.
|
|
CMSG_ALIGNTO = struct.calcsize("@L") # The kernel defines this as sizeof(long).
|
|
MSG_CONFIRM = 0X800
|
|
|
|
# Find the C library.
|
|
libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
|
|
|
|
|
|
def PaddedLength(length):
|
|
return CMSG_ALIGNTO * ((length / CMSG_ALIGNTO) + (length % CMSG_ALIGNTO != 0))
|
|
|
|
|
|
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 not isinstance(data, str):
|
|
raise TypeError("unknown data type for opt %i: %s" % (i, type(data)))
|
|
|
|
datalen = len(data)
|
|
msg_len = len(CMsgHdr) + datalen
|
|
padding = "\x00" * (PaddedLength(datalen) - datalen)
|
|
msg_control += CMsgHdr((msg_len, msg_level, msg_type)).Pack()
|
|
msg_control += data + padding
|
|
|
|
return msg_control
|
|
|
|
|
|
def Bind(s, to):
|
|
"""Python wrapper for connect."""
|
|
ret = libc.bind(s.fileno(), to.CPointer(), len(to))
|
|
MaybeRaiseSocketError(ret)
|
|
return ret
|
|
|
|
|
|
def Connect(s, to):
|
|
"""Python wrapper for connect."""
|
|
ret = libc.connect(s.fileno(), to.CPointer(), 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
|