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/testing.py | |
parent | 523f645a83a0ec97a0332dbc3863bb354c92a328 (diff) | |
download | ydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz |
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py2/twisted/pair/testing.py')
-rw-r--r-- | contrib/python/Twisted/py2/twisted/pair/testing.py | 572 |
1 files changed, 572 insertions, 0 deletions
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] |