diff options
author | shmel1k <shmel1k@ydb.tech> | 2023-11-26 18:16:14 +0300 |
---|---|---|
committer | shmel1k <shmel1k@ydb.tech> | 2023-11-26 18:43:30 +0300 |
commit | b8cf9e88f4c5c64d9406af533d8948deb050d695 (patch) | |
tree | 218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/Twisted/py2/twisted/pair | |
parent | 523f645a83a0ec97a0332dbc3863bb354c92a328 (diff) | |
download | ydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz |
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py2/twisted/pair')
-rw-r--r-- | contrib/python/Twisted/py2/twisted/pair/__init__.py | 13 | ||||
-rw-r--r-- | contrib/python/Twisted/py2/twisted/pair/ethernet.py | 56 | ||||
-rw-r--r-- | contrib/python/Twisted/py2/twisted/pair/ip.py | 71 | ||||
-rw-r--r-- | contrib/python/Twisted/py2/twisted/pair/raw.py | 40 | ||||
-rw-r--r-- | contrib/python/Twisted/py2/twisted/pair/rawudp.py | 59 | ||||
-rw-r--r-- | contrib/python/Twisted/py2/twisted/pair/testing.py | 572 | ||||
-rw-r--r-- | contrib/python/Twisted/py2/twisted/pair/tuntap.py | 433 |
7 files changed, 1244 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py2/twisted/pair/__init__.py b/contrib/python/Twisted/py2/twisted/pair/__init__.py new file mode 100644 index 0000000000..09fb3dc85d --- /dev/null +++ b/contrib/python/Twisted/py2/twisted/pair/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +Twisted Pair: The framework of your ethernet. + +Low-level networking transports and utilities. + +See also twisted.protocols.ethernet, twisted.protocols.ip, +twisted.protocols.raw and twisted.protocols.rawudp. + +Maintainer: Tommi Virtanen +""" diff --git a/contrib/python/Twisted/py2/twisted/pair/ethernet.py b/contrib/python/Twisted/py2/twisted/pair/ethernet.py new file mode 100644 index 0000000000..7c7f9a8996 --- /dev/null +++ b/contrib/python/Twisted/py2/twisted/pair/ethernet.py @@ -0,0 +1,56 @@ +# -*- test-case-name: twisted.pair.test.test_ethernet -*- +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +# + + +"""Support for working directly with ethernet frames""" + +import struct + + +from twisted.internet import protocol +from twisted.pair import raw +from zope.interface import implementer, Interface + + +class IEthernetProtocol(Interface): + """An interface for protocols that handle Ethernet frames""" + def addProto(): + """Add an IRawPacketProtocol protocol""" + + def datagramReceived(): + """An Ethernet frame has been received""" + +class EthernetHeader: + def __init__(self, data): + + (self.dest, self.source, self.proto) \ + = struct.unpack("!6s6sH", data[:6+6+2]) + + + +@implementer(IEthernetProtocol) +class EthernetProtocol(protocol.AbstractDatagramProtocol): + def __init__(self): + self.etherProtos = {} + + def addProto(self, num, proto): + proto = raw.IRawPacketProtocol(proto) + if num < 0: + raise TypeError('Added protocol must be positive or zero') + if num >= 2**16: + raise TypeError('Added protocol must fit in 16 bits') + if num not in self.etherProtos: + self.etherProtos[num] = [] + self.etherProtos[num].append(proto) + + def datagramReceived(self, data, partial=0): + header = EthernetHeader(data[:14]) + for proto in self.etherProtos.get(header.proto, ()): + proto.datagramReceived(data=data[14:], + partial=partial, + dest=header.dest, + source=header.source, + protocol=header.proto) diff --git a/contrib/python/Twisted/py2/twisted/pair/ip.py b/contrib/python/Twisted/py2/twisted/pair/ip.py new file mode 100644 index 0000000000..47db7075d7 --- /dev/null +++ b/contrib/python/Twisted/py2/twisted/pair/ip.py @@ -0,0 +1,71 @@ +# -*- test-case-name: twisted.pair.test.test_ip -*- +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +# + + +"""Support for working directly with IP packets""" + +import struct +import socket + +from twisted.internet import protocol +from twisted.pair import raw +from zope.interface import implementer + + +class IPHeader: + def __init__(self, data): + + (ihlversion, self.tos, self.tot_len, self.fragment_id, frag_off, + self.ttl, self.protocol, self.check, saddr, daddr) \ + = struct.unpack("!BBHHHBBH4s4s", data[:20]) + self.saddr = socket.inet_ntoa(saddr) + self.daddr = socket.inet_ntoa(daddr) + self.version = ihlversion & 0x0F + self.ihl = ((ihlversion & 0xF0) >> 4) << 2 + self.fragment_offset = frag_off & 0x1FFF + self.dont_fragment = (frag_off & 0x4000 != 0) + self.more_fragments = (frag_off & 0x2000 != 0) + +MAX_SIZE = 2**32 + +@implementer(raw.IRawPacketProtocol) +class IPProtocol(protocol.AbstractDatagramProtocol): + def __init__(self): + self.ipProtos = {} + + def addProto(self, num, proto): + proto = raw.IRawDatagramProtocol(proto) + if num < 0: + raise TypeError('Added protocol must be positive or zero') + if num >= MAX_SIZE: + raise TypeError('Added protocol must fit in 32 bits') + if num not in self.ipProtos: + self.ipProtos[num] = [] + self.ipProtos[num].append(proto) + + def datagramReceived(self, + data, + partial, + dest, + source, + protocol): + header = IPHeader(data) + for proto in self.ipProtos.get(header.protocol, ()): + proto.datagramReceived(data=data[20:], + partial=partial, + source=header.saddr, + dest=header.daddr, + protocol=header.protocol, + version=header.version, + ihl=header.ihl, + tos=header.tos, + tot_len=header.tot_len, + fragment_id=header.fragment_id, + fragment_offset=header.fragment_offset, + dont_fragment=header.dont_fragment, + more_fragments=header.more_fragments, + ttl=header.ttl, + ) diff --git a/contrib/python/Twisted/py2/twisted/pair/raw.py b/contrib/python/Twisted/py2/twisted/pair/raw.py new file mode 100644 index 0000000000..ed859db957 --- /dev/null +++ b/contrib/python/Twisted/py2/twisted/pair/raw.py @@ -0,0 +1,40 @@ +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. +""" +Interface definitions for working with raw packets +""" + +from zope.interface import Interface + + +class IRawDatagramProtocol(Interface): + """ + An interface for protocols such as UDP, ICMP and TCP. + """ + + def addProto(): + """ + Add a protocol on top of this one. + """ + + def datagramReceived(): + """ + An IP datagram has been received. Parse and process it. + """ + + + +class IRawPacketProtocol(Interface): + """ + An interface for low-level protocols such as IP and ARP. + """ + + def addProto(): + """ + Add a protocol on top of this one. + """ + + def datagramReceived(): + """ + An IP datagram has been received. Parse and process it. + """ diff --git a/contrib/python/Twisted/py2/twisted/pair/rawudp.py b/contrib/python/Twisted/py2/twisted/pair/rawudp.py new file mode 100644 index 0000000000..f52e417425 --- /dev/null +++ b/contrib/python/Twisted/py2/twisted/pair/rawudp.py @@ -0,0 +1,59 @@ +# -*- test-case-name: twisted.pair.test.test_rawudp -*- +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +Implementation of raw packet interfaces for UDP +""" + +import struct + +from twisted.internet import protocol +from twisted.pair import raw +from zope.interface import implementer + +class UDPHeader: + def __init__(self, data): + + (self.source, self.dest, self.len, self.check) \ + = struct.unpack("!HHHH", data[:8]) + + + +@implementer(raw.IRawDatagramProtocol) +class RawUDPProtocol(protocol.AbstractDatagramProtocol): + def __init__(self): + self.udpProtos = {} + + + def addProto(self, num, proto): + if not isinstance(proto, protocol.DatagramProtocol): + raise TypeError('Added protocol must be an instance of DatagramProtocol') + if num < 0: + raise TypeError('Added protocol must be positive or zero') + if num >= 2**16: + raise TypeError('Added protocol must fit in 16 bits') + if num not in self.udpProtos: + self.udpProtos[num] = [] + self.udpProtos[num].append(proto) + + + def datagramReceived(self, + data, + partial, + source, + dest, + protocol, + version, + ihl, + tos, + tot_len, + fragment_id, + fragment_offset, + dont_fragment, + more_fragments, + ttl): + header = UDPHeader(data) + for proto in self.udpProtos.get(header.dest, ()): + proto.datagramReceived(data[8:], + (source, header.source)) diff --git a/contrib/python/Twisted/py2/twisted/pair/testing.py b/contrib/python/Twisted/py2/twisted/pair/testing.py new file mode 100644 index 0000000000..b20136bdd1 --- /dev/null +++ b/contrib/python/Twisted/py2/twisted/pair/testing.py @@ -0,0 +1,572 @@ +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +Tools for automated testing of L{twisted.pair}-based applications. +""" + +import struct +import socket +from errno import ( + EPERM, EAGAIN, EWOULDBLOCK, ENOSYS, EBADF, EINVAL, EINTR, ENOBUFS) +from collections import deque +from functools import wraps + +from zope.interface import implementer + +from twisted.internet.protocol import DatagramProtocol +from twisted.pair.ethernet import EthernetProtocol +from twisted.pair.rawudp import RawUDPProtocol +from twisted.pair.ip import IPProtocol +from twisted.pair.tuntap import ( + _IFNAMSIZ, _TUNSETIFF, _IInputOutputSystem, TunnelFlags) +from twisted.python.compat import nativeString + + +# The number of bytes in the "protocol information" header that may be present +# on datagrams read from a tunnel device. This is two bytes of flags followed +# by two bytes of protocol identification. All this code does with this +# information is use it to discard the header. +_PI_SIZE = 4 + + +def _H(n): + """ + Pack an integer into a network-order two-byte string. + + @param n: The integer to pack. Only values that fit into 16 bits are + supported. + + @return: The packed representation of the integer. + @rtype: L{bytes} + """ + return struct.pack('>H', n) + + +_IPv4 = 0x0800 + + +def _ethernet(src, dst, protocol, payload): + """ + Construct an ethernet frame. + + @param src: The source ethernet address, encoded. + @type src: L{bytes} + + @param dst: The destination ethernet address, encoded. + @type dst: L{bytes} + + @param protocol: The protocol number of the payload of this datagram. + @type protocol: L{int} + + @param payload: The content of the ethernet frame (such as an IP datagram). + @type payload: L{bytes} + + @return: The full ethernet frame. + @rtype: L{bytes} + """ + return dst + src + _H(protocol) + payload + + + +def _ip(src, dst, payload): + """ + Construct an IP datagram with the given source, destination, and + application payload. + + @param src: The source IPv4 address as a dotted-quad string. + @type src: L{bytes} + + @param dst: The destination IPv4 address as a dotted-quad string. + @type dst: L{bytes} + + @param payload: The content of the IP datagram (such as a UDP datagram). + @type payload: L{bytes} + + @return: An IP datagram header and payload. + @rtype: L{bytes} + """ + ipHeader = ( + # Version and header length, 4 bits each + b'\x45' + # Differentiated services field + b'\x00' + # Total length + + _H(20 + len(payload)) + + b'\x00\x01\x00\x00\x40\x11' + # Checksum + + _H(0) + # Source address + + socket.inet_pton(socket.AF_INET, nativeString(src)) + # Destination address + + socket.inet_pton(socket.AF_INET, nativeString(dst))) + + # Total all of the 16-bit integers in the header + checksumStep1 = sum(struct.unpack('!10H', ipHeader)) + # Pull off the carry + carry = checksumStep1 >> 16 + # And add it to what was left over + checksumStep2 = (checksumStep1 & 0xFFFF) + carry + # Compute the one's complement sum + checksumStep3 = checksumStep2 ^ 0xFFFF + + # Reconstruct the IP header including the correct checksum so the platform + # IP stack, if there is one involved in this test, doesn't drop it on the + # floor as garbage. + ipHeader = ( + ipHeader[:10] + + struct.pack('!H', checksumStep3) + + ipHeader[12:]) + + return ipHeader + payload + + + +def _udp(src, dst, payload): + """ + Construct a UDP datagram with the given source, destination, and + application payload. + + @param src: The source port number. + @type src: L{int} + + @param dst: The destination port number. + @type dst: L{int} + + @param payload: The content of the UDP datagram. + @type payload: L{bytes} + + @return: A UDP datagram header and payload. + @rtype: L{bytes} + """ + udpHeader = ( + # Source port + _H(src) + # Destination port + + _H(dst) + # Length + + _H(len(payload) + 8) + # Checksum + + _H(0)) + return udpHeader + payload + + + +class Tunnel(object): + """ + An in-memory implementation of a tun or tap device. + + @cvar _DEVICE_NAME: A string representing the conventional filesystem entry + for the tunnel factory character special device. + @type _DEVICE_NAME: C{bytes} + """ + _DEVICE_NAME = b"/dev/net/tun" + + # Between POSIX and Python, there are 4 combinations. Here are two, at + # least. + EAGAIN_STYLE = IOError(EAGAIN, "Resource temporarily unavailable") + EWOULDBLOCK_STYLE = OSError(EWOULDBLOCK, "Operation would block") + + # Oh yea, and then there's the case where maybe we would've read, but + # someone sent us a signal instead. + EINTR_STYLE = IOError(EINTR, "Interrupted function call") + + nonBlockingExceptionStyle = EAGAIN_STYLE + + SEND_BUFFER_SIZE = 1024 + + def __init__(self, system, openFlags, fileMode): + """ + @param system: An L{_IInputOutputSystem} provider to use to perform I/O. + + @param openFlags: Any flags to apply when opening the tunnel device. + See C{os.O_*}. + + @type openFlags: L{int} + + @param fileMode: ignored + """ + self.system = system + + # Drop fileMode on the floor - evidence and logic suggest it is + # irrelevant with respect to /dev/net/tun + self.openFlags = openFlags + self.tunnelMode = None + self.requestedName = None + self.name = None + self.readBuffer = deque() + self.writeBuffer = deque() + self.pendingSignals = deque() + + + @property + def blocking(self): + """ + If the file descriptor for this tunnel is open in blocking mode, + C{True}. C{False} otherwise. + """ + return not (self.openFlags & self.system.O_NONBLOCK) + + + @property + def closeOnExec(self): + """ + If the file descriptor for this tunnel is marked as close-on-exec, + C{True}. C{False} otherwise. + """ + return bool(self.openFlags & self.system.O_CLOEXEC) + + + def addToReadBuffer(self, datagram): + """ + Deliver a datagram to this tunnel's read buffer. This makes it + available to be read later using the C{read} method. + + @param datagram: The IPv4 datagram to deliver. If the mode of this + tunnel is TAP then ethernet framing will be added automatically. + @type datagram: L{bytes} + """ + # TAP devices also include ethernet framing. + if self.tunnelMode & TunnelFlags.IFF_TAP.value: + datagram = _ethernet( + src=b'\x00' * 6, dst=b'\xff' * 6, protocol=_IPv4, + payload=datagram) + + self.readBuffer.append(datagram) + + + def read(self, limit): + """ + Read a datagram out of this tunnel. + + @param limit: The maximum number of bytes from the datagram to return. + If the next datagram is larger than this, extra bytes are dropped + and lost forever. + @type limit: L{int} + + @raise OSError: Any of the usual I/O problems can result in this + exception being raised with some particular error number set. + + @raise IOError: Any of the usual I/O problems can result in this + exception being raised with some particular error number set. + + @return: The datagram which was read from the tunnel. If the tunnel + mode does not include L{TunnelFlags.IFF_NO_PI} then the datagram is + prefixed with a 4 byte PI header. + @rtype: L{bytes} + """ + if self.readBuffer: + if self.tunnelMode & TunnelFlags.IFF_NO_PI.value: + header = b"" + else: + # Synthesize a PI header to include in the result. Nothing in + # twisted.pair uses the PI information yet so we can synthesize + # something incredibly boring (ie 32 bits of 0). + header = b"\x00" * _PI_SIZE + limit -= 4 + return header + self.readBuffer.popleft()[:limit] + elif self.blocking: + raise NotImplementedError() + else: + raise self.nonBlockingExceptionStyle + + + def write(self, datagram): + """ + Write a datagram into this tunnel. + + @param datagram: The datagram to write. + @type datagram: L{bytes} + + @raise IOError: Any of the usual I/O problems can result in this + exception being raised with some particular error number set. + + @return: The number of bytes of the datagram which were written. + @rtype: L{int} + """ + if self.pendingSignals: + self.pendingSignals.popleft() + raise IOError(EINTR, "Interrupted system call") + + if len(datagram) > self.SEND_BUFFER_SIZE: + raise IOError(ENOBUFS, "No buffer space available") + + self.writeBuffer.append(datagram) + return len(datagram) + + + +def _privileged(original): + """ + Wrap a L{MemoryIOSystem} method with permission-checking logic. The + returned function will check C{self.permissions} and raise L{IOError} with + L{errno.EPERM} if the function name is not listed as an available + permission. + + @param original: The L{MemoryIOSystem} instance to wrap. + + @return: A wrapper around C{original} that applies permission checks. + """ + @wraps(original) + def permissionChecker(self, *args, **kwargs): + if original.__name__ not in self.permissions: + raise IOError(EPERM, "Operation not permitted") + return original(self, *args, **kwargs) + return permissionChecker + + + +@implementer(_IInputOutputSystem) +class MemoryIOSystem(object): + """ + An in-memory implementation of basic I/O primitives, useful in the context + of unit testing as a drop-in replacement for parts of the C{os} module. + + @ivar _devices: + @ivar _openFiles: + @ivar permissions: + + @ivar _counter: + """ + _counter = 8192 + + O_RDWR = 1 << 0 + O_NONBLOCK = 1 << 1 + O_CLOEXEC = 1 << 2 + + def __init__(self): + self._devices = {} + self._openFiles = {} + self.permissions = set(['open', 'ioctl']) + + + def getTunnel(self, port): + """ + Get the L{Tunnel} object associated with the given L{TuntapPort}. + + @param port: A L{TuntapPort} previously initialized using this + L{MemoryIOSystem}. + + @return: The tunnel object created by a prior use of C{open} on this + object on the tunnel special device file. + @rtype: L{Tunnel} + """ + return self._openFiles[port.fileno()] + + + def registerSpecialDevice(self, name, cls): + """ + Specify a class which will be used to handle I/O to a device of a + particular name. + + @param name: The filesystem path name of the device. + @type name: L{bytes} + + @param cls: A class (like L{Tunnel}) to instantiated whenever this + device is opened. + """ + self._devices[name] = cls + + + @_privileged + def open(self, name, flags, mode=None): + """ + A replacement for C{os.open}. This initializes state in this + L{MemoryIOSystem} which will be reflected in the behavior of the other + file descriptor-related methods (eg L{MemoryIOSystem.read}, + L{MemoryIOSystem.write}, etc). + + @param name: A string giving the name of the file to open. + @type name: C{bytes} + + @param flags: The flags with which to open the file. + @type flags: C{int} + + @param mode: The mode with which to open the file. + @type mode: C{int} + + @raise OSError: With C{ENOSYS} if the file is not a recognized special + device file. + + @return: A file descriptor associated with the newly opened file + description. + @rtype: L{int} + """ + if name in self._devices: + fd = self._counter + self._counter += 1 + self._openFiles[fd] = self._devices[name](self, flags, mode) + return fd + raise OSError(ENOSYS, "Function not implemented") + + + def read(self, fd, limit): + """ + Try to read some bytes out of one of the in-memory buffers which may + previously have been populated by C{write}. + + @see: L{os.read} + """ + try: + return self._openFiles[fd].read(limit) + except KeyError: + raise OSError(EBADF, "Bad file descriptor") + + + def write(self, fd, data): + """ + Try to add some bytes to one of the in-memory buffers to be accessed by + a later C{read} call. + + @see: L{os.write} + """ + try: + return self._openFiles[fd].write(data) + except KeyError: + raise OSError(EBADF, "Bad file descriptor") + + + def close(self, fd): + """ + Discard the in-memory buffer and other in-memory state for the given + file descriptor. + + @see: L{os.close} + """ + try: + del self._openFiles[fd] + except KeyError: + raise OSError(EBADF, "Bad file descriptor") + + + @_privileged + def ioctl(self, fd, request, args): + """ + Perform some configuration change to the in-memory state for the given + file descriptor. + + @see: L{fcntl.ioctl} + """ + try: + tunnel = self._openFiles[fd] + except KeyError: + raise IOError(EBADF, "Bad file descriptor") + + if request != _TUNSETIFF: + raise IOError(EINVAL, "Request or args is not valid.") + + name, mode = struct.unpack('%dsH' % (_IFNAMSIZ,), args) + tunnel.tunnelMode = mode + tunnel.requestedName = name + tunnel.name = name[:_IFNAMSIZ - 3] + b"123" + + return struct.pack('%dsH' % (_IFNAMSIZ,), tunnel.name, mode) + + + def sendUDP(self, datagram, address): + """ + Write an ethernet frame containing an ip datagram containing a udp + datagram containing the given payload, addressed to the given address, + to a tunnel device previously opened on this I/O system. + + @param datagram: A UDP datagram payload to send. + @type datagram: L{bytes} + + @param address: The destination to which to send the datagram. + @type address: L{tuple} of (L{bytes}, L{int}) + + @return: A two-tuple giving the address from which gives the address + from which the datagram was sent. + @rtype: L{tuple} of (L{bytes}, L{int}) + """ + # Just make up some random thing + srcIP = '10.1.2.3' + srcPort = 21345 + + serialized = _ip( + src=srcIP, dst=address[0], payload=_udp( + src=srcPort, dst=address[1], payload=datagram)) + + openFiles = list(self._openFiles.values()) + openFiles[0].addToReadBuffer(serialized) + + return (srcIP, srcPort) + + + def receiveUDP(self, fileno, host, port): + """ + Get a socket-like object which can be used to receive a datagram sent + from the given address. + + @param fileno: A file descriptor representing a tunnel device which the + datagram will be received via. + @type fileno: L{int} + + @param host: The IPv4 address to which the datagram was sent. + @type host: L{bytes} + + @param port: The UDP port number to which the datagram was sent. + received. + @type port: L{int} + + @return: A L{socket.socket}-like object which can be used to receive + the specified datagram. + """ + return _FakePort(self, fileno) + + + +class _FakePort(object): + """ + A socket-like object which can be used to read UDP datagrams from + tunnel-like file descriptors managed by a L{MemoryIOSystem}. + """ + def __init__(self, system, fileno): + self._system = system + self._fileno = fileno + + + def recv(self, nbytes): + """ + Receive a datagram sent to this port using the L{MemoryIOSystem} which + created this object. + + This behaves like L{socket.socket.recv} but the data being I{sent} and + I{received} only passes through various memory buffers managed by this + object and L{MemoryIOSystem}. + + @see: L{socket.socket.recv} + """ + data = self._system._openFiles[self._fileno].writeBuffer.popleft() + + datagrams = [] + receiver = DatagramProtocol() + + def capture(datagram, address): + datagrams.append(datagram) + + receiver.datagramReceived = capture + + udp = RawUDPProtocol() + udp.addProto(12345, receiver) + + ip = IPProtocol() + ip.addProto(17, udp) + + mode = self._system._openFiles[self._fileno].tunnelMode + if (mode & TunnelFlags.IFF_TAP.value): + ether = EthernetProtocol() + ether.addProto(0x800, ip) + datagramReceived = ether.datagramReceived + else: + datagramReceived = lambda data: ip.datagramReceived( + data, None, None, None, None) + + dataHasPI = not (mode & TunnelFlags.IFF_NO_PI.value) + + if dataHasPI: + # datagramReceived can't handle the PI, get rid of it. + data = data[_PI_SIZE:] + + datagramReceived(data) + return datagrams[0][:nbytes] diff --git a/contrib/python/Twisted/py2/twisted/pair/tuntap.py b/contrib/python/Twisted/py2/twisted/pair/tuntap.py new file mode 100644 index 0000000000..69b3f9bfff --- /dev/null +++ b/contrib/python/Twisted/py2/twisted/pair/tuntap.py @@ -0,0 +1,433 @@ +# -*- test-case-name: twisted.pair.test.test_tuntap -*- +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +Support for Linux ethernet and IP tunnel devices. + +@see: U{https://en.wikipedia.org/wiki/TUN/TAP} +""" + +import os +import fcntl +import errno +import struct +import warnings + +from collections import namedtuple +from constantly import Flags, FlagConstant +from zope.interface import Attribute, Interface, implementer + +from twisted.python.util import FancyEqMixin, FancyStrMixin +from incremental import Version +from twisted.python.reflect import fullyQualifiedName +from twisted.python.deprecate import deprecated +from twisted.python import log +from twisted.internet import abstract, error, task, interfaces, defer +from twisted.pair import ethernet, raw + +__all__ = [ + "TunnelFlags", "TunnelAddress", "TuntapPort", + ] + + +_IFNAMSIZ = 16 +_TUNSETIFF = 0x400454ca +_TUNGETIFF = 0x800454d2 +_TUN_KO_PATH = b"/dev/net/tun" + + +class TunnelFlags(Flags): + """ + L{TunnelFlags} defines more flags which are used to configure the behavior + of a tunnel device. + + @cvar IFF_TUN: This indicates a I{tun}-type device. This type of tunnel + carries IP datagrams. This flag is mutually exclusive with C{IFF_TAP}. + + @cvar IFF_TAP: This indicates a I{tap}-type device. This type of tunnel + carries ethernet frames. This flag is mutually exclusive with C{IFF_TUN}. + + @cvar IFF_NO_PI: This indicates the I{protocol information} header will + B{not} be included in data read from the tunnel. + + @see: U{https://www.kernel.org/doc/Documentation/networking/tuntap.txt} + """ + IFF_TUN = FlagConstant(0x0001) + IFF_TAP = FlagConstant(0x0002) + + TUN_FASYNC = FlagConstant(0x0010) + TUN_NOCHECKSUM = FlagConstant(0x0020) + TUN_NO_PI = FlagConstant(0x0040) + TUN_ONE_QUEUE = FlagConstant(0x0080) + TUN_PERSIST = FlagConstant(0x0100) + TUN_VNET_HDR = FlagConstant(0x0200) + + IFF_NO_PI = FlagConstant(0x1000) + IFF_ONE_QUEUE = FlagConstant(0x2000) + IFF_VNET_HDR = FlagConstant(0x4000) + IFF_TUN_EXCL = FlagConstant(0x8000) + + + +@implementer(interfaces.IAddress) +class TunnelAddress(FancyStrMixin, FancyEqMixin, object): + """ + A L{TunnelAddress} represents the tunnel to which a L{TuntapPort} is bound. + """ + compareAttributes = ("_typeValue", "name") + showAttributes = (("type", lambda flag: flag.name), "name") + + @property + def _typeValue(self): + """ + Return the integer value of the C{type} attribute. Used to produce + correct results in the equality implementation. + """ + # Work-around for https://twistedmatrix.com/trac/ticket/6878 + return self.type.value + + + def __init__(self, type, name): + """ + @param type: Either L{TunnelFlags.IFF_TUN} or L{TunnelFlags.IFF_TAP}, + representing the type of this tunnel. + + @param name: The system name of the tunnel. + @type name: L{bytes} + """ + self.type = type + self.name = name + + + def __getitem__(self, index): + """ + Deprecated accessor for the tunnel name. Use attributes instead. + """ + warnings.warn( + "TunnelAddress.__getitem__ is deprecated since Twisted 14.0.0 " + "Use attributes instead.", category=DeprecationWarning, + stacklevel=2) + return ('TUNTAP', self.name)[index] + + + +class _TunnelDescription(namedtuple("_TunnelDescription", "fileno name")): + """ + Describe an existing tunnel. + + @ivar fileno: the file descriptor associated with the tunnel + @type fileno: L{int} + + @ivar name: the name of the tunnel + @type name: L{bytes} + """ + + + +class _IInputOutputSystem(Interface): + """ + An interface for performing some basic kinds of I/O (particularly that I/O + which might be useful for L{twisted.pair.tuntap}-using code). + """ + O_RDWR = Attribute("@see: L{os.O_RDWR}") + O_NONBLOCK = Attribute("@see: L{os.O_NONBLOCK}") + O_CLOEXEC = Attribute("@see: L{os.O_CLOEXEC}") + + def open(filename, flag, mode=0o777): + """ + @see: L{os.open} + """ + + + def ioctl(fd, opt, arg=None, mutate_flag=None): + """ + @see: L{fcntl.ioctl} + """ + + + def read(fd, limit): + """ + @see: L{os.read} + """ + + + def write(fd, data): + """ + @see: L{os.write} + """ + + + def close(fd): + """ + @see: L{os.close} + """ + + + def sendUDP(datagram, address): + """ + Send a datagram to a certain address. + + @param datagram: The payload of a UDP datagram to send. + @type datagram: L{bytes} + + @param address: The destination to which to send the datagram. + @type address: L{tuple} of (L{bytes}, L{int}) + + @return: The local address from which the datagram was sent. + @rtype: L{tuple} of (L{bytes}, L{int}) + """ + + + def receiveUDP(fileno, host, port): + """ + Return a socket which can be used to receive datagrams sent to the + given address. + + @param fileno: A file descriptor representing a tunnel device which the + datagram was either sent via or will be received via. + @type fileno: L{int} + + @param host: The IPv4 address at which the datagram will be received. + @type host: L{bytes} + + @param port: The UDP port number at which the datagram will be + received. + @type port: L{int} + + @return: A L{socket.socket} which can be used to receive the specified + datagram. + """ + + + +class _RealSystem(object): + """ + An interface to the parts of the operating system which L{TuntapPort} + relies on. This is most of an implementation of L{_IInputOutputSystem}. + """ + open = staticmethod(os.open) + read = staticmethod(os.read) + write = staticmethod(os.write) + close = staticmethod(os.close) + ioctl = staticmethod(fcntl.ioctl) + + O_RDWR = os.O_RDWR + O_NONBLOCK = os.O_NONBLOCK + # Introduced in Python 3.x + # Ubuntu 12.04, /usr/include/x86_64-linux-gnu/bits/fcntl.h + O_CLOEXEC = getattr(os, "O_CLOEXEC", 0o2000000) + + + +@implementer(interfaces.IListeningPort) +class TuntapPort(abstract.FileDescriptor): + """ + A Port that reads and writes packets from/to a TUN/TAP-device. + """ + maxThroughput = 256 * 1024 # Max bytes we read in one eventloop iteration + + def __init__(self, interface, proto, maxPacketSize=8192, reactor=None, + system=None): + if ethernet.IEthernetProtocol.providedBy(proto): + self.ethernet = 1 + self._mode = TunnelFlags.IFF_TAP + else: + self.ethernet = 0 + self._mode = TunnelFlags.IFF_TUN + assert raw.IRawPacketProtocol.providedBy(proto) + + if system is None: + system = _RealSystem() + self._system = system + + abstract.FileDescriptor.__init__(self, reactor) + self.interface = interface + self.protocol = proto + self.maxPacketSize = maxPacketSize + + logPrefix = self._getLogPrefix(self.protocol) + self.logstr = "%s (%s)" % (logPrefix, self._mode.name) + + + def __repr__(self): + args = (fullyQualifiedName(self.protocol.__class__),) + if self.connected: + args = args + ("",) + else: + args = args + ("not ",) + args = args + (self._mode.name, self.interface) + return "<%s %slistening on %s/%s>" % args + + + def startListening(self): + """ + Create and bind my socket, and begin listening on it. + + This must be called after creating a server to begin listening on the + specified tunnel. + """ + self._bindSocket() + self.protocol.makeConnection(self) + self.startReading() + + + def _openTunnel(self, name, mode): + """ + Open the named tunnel using the given mode. + + @param name: The name of the tunnel to open. + @type name: L{bytes} + + @param mode: Flags from L{TunnelFlags} with exactly one of + L{TunnelFlags.IFF_TUN} or L{TunnelFlags.IFF_TAP} set. + + @return: A L{_TunnelDescription} representing the newly opened tunnel. + """ + flags = ( + self._system.O_RDWR | self._system.O_CLOEXEC | + self._system.O_NONBLOCK) + config = struct.pack("%dsH" % (_IFNAMSIZ,), name, mode.value) + fileno = self._system.open(_TUN_KO_PATH, flags) + result = self._system.ioctl(fileno, _TUNSETIFF, config) + return _TunnelDescription(fileno, result[:_IFNAMSIZ].strip(b'\x00')) + + + def _bindSocket(self): + """ + Open the tunnel. + """ + log.msg( + format="%(protocol)s starting on %(interface)s", + protocol=self.protocol.__class__, + interface=self.interface) + try: + fileno, interface = self._openTunnel( + self.interface, self._mode | TunnelFlags.IFF_NO_PI) + except (IOError, OSError) as e: + raise error.CannotListenError(None, self.interface, e) + + self.interface = interface + self._fileno = fileno + + self.connected = 1 + + + def fileno(self): + return self._fileno + + + def doRead(self): + """ + Called when my socket is ready for reading. + """ + read = 0 + while read < self.maxThroughput: + try: + data = self._system.read(self._fileno, self.maxPacketSize) + except EnvironmentError as e: + if e.errno in (errno.EWOULDBLOCK, errno.EAGAIN, errno.EINTR): + return + else: + raise + except: + raise + read += len(data) + # TODO pkt.isPartial()? + try: + self.protocol.datagramReceived(data, partial=0) + except: + cls = fullyQualifiedName(self.protocol.__class__) + log.err( + None, + "Unhandled exception from %s.datagramReceived" % (cls,)) + + + def write(self, datagram): + """ + Write the given data as a single datagram. + + @param datagram: The data that will make up the complete datagram to be + written. + @type datagram: L{bytes} + """ + try: + return self._system.write(self._fileno, datagram) + except IOError as e: + if e.errno == errno.EINTR: + return self.write(datagram) + raise + + + def writeSequence(self, seq): + """ + Write a datagram constructed from a L{list} of L{bytes}. + + @param datagram: The data that will make up the complete datagram to be + written. + @type seq: L{list} of L{bytes} + """ + self.write(b"".join(seq)) + + + def stopListening(self): + """ + Stop accepting connections on this port. + + This will shut down my socket and call self.connectionLost(). + + @return: A L{Deferred} that fires when this port has stopped. + """ + self.stopReading() + if self.disconnecting: + return self._stoppedDeferred + elif self.connected: + self._stoppedDeferred = task.deferLater( + self.reactor, 0, self.connectionLost) + self.disconnecting = True + return self._stoppedDeferred + else: + return defer.succeed(None) + + + def loseConnection(self): + """ + Close this tunnel. Use L{TuntapPort.stopListening} instead. + """ + self.stopListening().addErrback(log.err) + + + def connectionLost(self, reason=None): + """ + Cleans up my socket. + + @param reason: Ignored. Do not use this. + """ + log.msg('(Tuntap %s Closed)' % self.interface) + abstract.FileDescriptor.connectionLost(self, reason) + self.protocol.doStop() + self.connected = 0 + self._system.close(self._fileno) + self._fileno = -1 + + + def logPrefix(self): + """ + Returns the name of my class, to prefix log entries with. + """ + return self.logstr + + + def getHost(self): + """ + Get the local address of this L{TuntapPort}. + + @return: A L{TunnelAddress} which describes the tunnel device to which + this object is bound. + @rtype: L{TunnelAddress} + """ + return TunnelAddress(self._mode, self.interface) + +TuntapPort.loseConnection = deprecated( + Version("Twisted", 14, 0, 0), + TuntapPort.stopListening)(TuntapPort.loseConnection) |