mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-23 00:11:09 -05:00
okay fine
This commit is contained in:
13
.venv/lib/python3.12/site-packages/twisted/pair/__init__.py
Normal file
13
.venv/lib/python3.12/site-packages/twisted/pair/__init__.py
Normal file
@@ -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
|
||||
"""
|
||||
59
.venv/lib/python3.12/site-packages/twisted/pair/ethernet.py
Normal file
59
.venv/lib/python3.12/site-packages/twisted/pair/ethernet.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# -*- 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 zope.interface import Interface, implementer
|
||||
|
||||
from twisted.internet import protocol
|
||||
from twisted.pair import raw
|
||||
|
||||
|
||||
class IEthernetProtocol(Interface):
|
||||
"""An interface for protocols that handle Ethernet frames"""
|
||||
|
||||
def addProto(num, proto):
|
||||
"""Add an IRawPacketProtocol protocol"""
|
||||
|
||||
def datagramReceived(data, partial):
|
||||
"""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,
|
||||
)
|
||||
78
.venv/lib/python3.12/site-packages/twisted/pair/ip.py
Normal file
78
.venv/lib/python3.12/site-packages/twisted/pair/ip.py
Normal file
@@ -0,0 +1,78 @@
|
||||
# -*- 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 socket
|
||||
import struct
|
||||
|
||||
from zope.interface import implementer
|
||||
|
||||
from twisted.internet import protocol
|
||||
from twisted.pair import raw
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
54
.venv/lib/python3.12/site-packages/twisted/pair/raw.py
Normal file
54
.venv/lib/python3.12/site-packages/twisted/pair/raw.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# 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(num, proto):
|
||||
"""
|
||||
Add a protocol on top of this one.
|
||||
"""
|
||||
|
||||
def datagramReceived(
|
||||
data,
|
||||
partial,
|
||||
source,
|
||||
dest,
|
||||
protocol,
|
||||
version,
|
||||
ihl,
|
||||
tos,
|
||||
tot_len,
|
||||
fragment_id,
|
||||
fragment_offset,
|
||||
dont_fragment,
|
||||
more_fragments,
|
||||
ttl,
|
||||
):
|
||||
"""
|
||||
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(num, proto):
|
||||
"""
|
||||
Add a protocol on top of this one.
|
||||
"""
|
||||
|
||||
def datagramReceived(data, partial, dest, source, protocol):
|
||||
"""
|
||||
An IP datagram has been received. Parse and process it.
|
||||
"""
|
||||
59
.venv/lib/python3.12/site-packages/twisted/pair/rawudp.py
Normal file
59
.venv/lib/python3.12/site-packages/twisted/pair/rawudp.py
Normal file
@@ -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 zope.interface import implementer
|
||||
|
||||
from twisted.internet import protocol
|
||||
from twisted.pair import raw
|
||||
|
||||
|
||||
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))
|
||||
@@ -0,0 +1 @@
|
||||
"pair tests"
|
||||
@@ -0,0 +1,255 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
from zope.interface import implementer
|
||||
|
||||
from twisted.pair import ethernet, raw
|
||||
from twisted.python import components
|
||||
from twisted.trial import unittest
|
||||
|
||||
|
||||
@implementer(raw.IRawPacketProtocol)
|
||||
class MyProtocol:
|
||||
def __init__(self, expecting):
|
||||
self.expecting = list(expecting)
|
||||
|
||||
def addProto(self, num, proto):
|
||||
"""
|
||||
Not implemented
|
||||
"""
|
||||
|
||||
def datagramReceived(self, data, partial, dest, source, protocol):
|
||||
assert self.expecting, "Got a packet when not expecting anymore."
|
||||
expect = self.expecting.pop(0)
|
||||
localVariables = locals()
|
||||
params = {
|
||||
"partial": partial,
|
||||
"dest": dest,
|
||||
"source": source,
|
||||
"protocol": protocol,
|
||||
}
|
||||
assert expect == (data, params), "Expected {!r}, got {!r}".format(
|
||||
expect, (data, params)
|
||||
)
|
||||
|
||||
|
||||
class EthernetTests(unittest.TestCase):
|
||||
def testPacketParsing(self):
|
||||
proto = ethernet.EthernetProtocol()
|
||||
p1 = MyProtocol(
|
||||
[
|
||||
(
|
||||
b"foobar",
|
||||
{
|
||||
"partial": 0,
|
||||
"dest": b"123456",
|
||||
"source": b"987654",
|
||||
"protocol": 0x0800,
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
proto.addProto(0x0800, p1)
|
||||
|
||||
proto.datagramReceived(b"123456987654\x08\x00foobar", partial=0)
|
||||
|
||||
assert not p1.expecting, (
|
||||
"Should not expect any more packets, but still want %r" % p1.expecting
|
||||
)
|
||||
|
||||
def testMultiplePackets(self):
|
||||
proto = ethernet.EthernetProtocol()
|
||||
p1 = MyProtocol(
|
||||
[
|
||||
(
|
||||
b"foobar",
|
||||
{
|
||||
"partial": 0,
|
||||
"dest": b"123456",
|
||||
"source": b"987654",
|
||||
"protocol": 0x0800,
|
||||
},
|
||||
),
|
||||
(
|
||||
b"quux",
|
||||
{
|
||||
"partial": 1,
|
||||
"dest": b"012345",
|
||||
"source": b"abcdef",
|
||||
"protocol": 0x0800,
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
proto.addProto(0x0800, p1)
|
||||
|
||||
proto.datagramReceived(b"123456987654\x08\x00foobar", partial=0)
|
||||
proto.datagramReceived(b"012345abcdef\x08\x00quux", partial=1)
|
||||
|
||||
assert not p1.expecting, (
|
||||
"Should not expect any more packets, but still want %r" % p1.expecting
|
||||
)
|
||||
|
||||
def testMultipleSameProtos(self):
|
||||
proto = ethernet.EthernetProtocol()
|
||||
p1 = MyProtocol(
|
||||
[
|
||||
(
|
||||
b"foobar",
|
||||
{
|
||||
"partial": 0,
|
||||
"dest": b"123456",
|
||||
"source": b"987654",
|
||||
"protocol": 0x0800,
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
p2 = MyProtocol(
|
||||
[
|
||||
(
|
||||
b"foobar",
|
||||
{
|
||||
"partial": 0,
|
||||
"dest": b"123456",
|
||||
"source": b"987654",
|
||||
"protocol": 0x0800,
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
proto.addProto(0x0800, p1)
|
||||
proto.addProto(0x0800, p2)
|
||||
|
||||
proto.datagramReceived(b"123456987654\x08\x00foobar", partial=0)
|
||||
|
||||
assert (
|
||||
not p1.expecting
|
||||
), "Should not expect any more packets, " "but still want {!r}".format(
|
||||
p1.expecting
|
||||
)
|
||||
assert (
|
||||
not p2.expecting
|
||||
), "Should not expect any more packets," " but still want {!r}".format(
|
||||
p2.expecting
|
||||
)
|
||||
|
||||
def testWrongProtoNotSeen(self):
|
||||
proto = ethernet.EthernetProtocol()
|
||||
p1 = MyProtocol([])
|
||||
proto.addProto(0x0801, p1)
|
||||
|
||||
proto.datagramReceived(b"123456987654\x08\x00foobar", partial=0)
|
||||
proto.datagramReceived(b"012345abcdef\x08\x00quux", partial=1)
|
||||
|
||||
def testDemuxing(self):
|
||||
proto = ethernet.EthernetProtocol()
|
||||
p1 = MyProtocol(
|
||||
[
|
||||
(
|
||||
b"foobar",
|
||||
{
|
||||
"partial": 0,
|
||||
"dest": b"123456",
|
||||
"source": b"987654",
|
||||
"protocol": 0x0800,
|
||||
},
|
||||
),
|
||||
(
|
||||
b"quux",
|
||||
{
|
||||
"partial": 1,
|
||||
"dest": b"012345",
|
||||
"source": b"abcdef",
|
||||
"protocol": 0x0800,
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
proto.addProto(0x0800, p1)
|
||||
|
||||
p2 = MyProtocol(
|
||||
[
|
||||
(
|
||||
b"quux",
|
||||
{
|
||||
"partial": 1,
|
||||
"dest": b"012345",
|
||||
"source": b"abcdef",
|
||||
"protocol": 0x0806,
|
||||
},
|
||||
),
|
||||
(
|
||||
b"foobar",
|
||||
{
|
||||
"partial": 0,
|
||||
"dest": b"123456",
|
||||
"source": b"987654",
|
||||
"protocol": 0x0806,
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
proto.addProto(0x0806, p2)
|
||||
|
||||
proto.datagramReceived(b"123456987654\x08\x00foobar", partial=0)
|
||||
proto.datagramReceived(b"012345abcdef\x08\x06quux", partial=1)
|
||||
proto.datagramReceived(b"123456987654\x08\x06foobar", partial=0)
|
||||
proto.datagramReceived(b"012345abcdef\x08\x00quux", partial=1)
|
||||
|
||||
assert not p1.expecting, (
|
||||
"Should not expect any more packets, but still want %r" % p1.expecting
|
||||
)
|
||||
assert not p2.expecting, (
|
||||
"Should not expect any more packets, but still want %r" % p2.expecting
|
||||
)
|
||||
|
||||
def testAddingBadProtos_WrongLevel(self):
|
||||
"""Adding a wrong level protocol raises an exception."""
|
||||
e = ethernet.EthernetProtocol()
|
||||
try:
|
||||
e.addProto(42, "silliness")
|
||||
except components.CannotAdapt:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError("addProto must raise an exception for bad protocols")
|
||||
|
||||
def testAddingBadProtos_TooSmall(self):
|
||||
"""Adding a protocol with a negative number raises an exception."""
|
||||
e = ethernet.EthernetProtocol()
|
||||
try:
|
||||
e.addProto(-1, MyProtocol([]))
|
||||
except TypeError as e:
|
||||
if e.args == ("Added protocol must be positive or zero",):
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
raise AssertionError("addProto must raise an exception for bad protocols")
|
||||
|
||||
def testAddingBadProtos_TooBig(self):
|
||||
"""Adding a protocol with a number >=2**16 raises an exception."""
|
||||
e = ethernet.EthernetProtocol()
|
||||
try:
|
||||
e.addProto(2**16, MyProtocol([]))
|
||||
except TypeError as e:
|
||||
if e.args == ("Added protocol must fit in 16 bits",):
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
raise AssertionError("addProto must raise an exception for bad protocols")
|
||||
|
||||
def testAddingBadProtos_TooBig2(self):
|
||||
"""Adding a protocol with a number >=2**16 raises an exception."""
|
||||
e = ethernet.EthernetProtocol()
|
||||
try:
|
||||
e.addProto(2**16 + 1, MyProtocol([]))
|
||||
except TypeError as e:
|
||||
if e.args == ("Added protocol must fit in 16 bits",):
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
raise AssertionError("addProto must raise an exception for bad protocols")
|
||||
490
.venv/lib/python3.12/site-packages/twisted/pair/test/test_ip.py
Normal file
490
.venv/lib/python3.12/site-packages/twisted/pair/test/test_ip.py
Normal file
@@ -0,0 +1,490 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
from __future__ import annotations
|
||||
|
||||
from zope import interface
|
||||
|
||||
from twisted.pair import ip, raw
|
||||
from twisted.python import components
|
||||
from twisted.trial import unittest
|
||||
|
||||
|
||||
@interface.implementer(raw.IRawDatagramProtocol)
|
||||
class MyProtocol:
|
||||
def __init__(self, expecting: list[tuple[bytes, dict[str, str | int]]]) -> None:
|
||||
self.expecting = list(expecting)
|
||||
|
||||
def datagramReceived(
|
||||
self,
|
||||
data: bytes,
|
||||
partial: int,
|
||||
source: str,
|
||||
dest: str,
|
||||
protocol: int,
|
||||
version: int,
|
||||
ihl: int,
|
||||
tos: int,
|
||||
tot_len: int,
|
||||
fragment_id: int,
|
||||
fragment_offset: int,
|
||||
dont_fragment: int,
|
||||
more_fragments: int,
|
||||
ttl: int,
|
||||
) -> None:
|
||||
assert self.expecting, "Got a packet when not expecting anymore."
|
||||
expectData, expectKw = self.expecting.pop(0)
|
||||
|
||||
expectKwKeys = list(sorted(expectKw.keys()))
|
||||
localVariables = locals()
|
||||
|
||||
for k in expectKwKeys:
|
||||
assert (
|
||||
expectKw[k] == localVariables[k]
|
||||
), f"Expected {k}={expectKw[k]!r}, got {localVariables[k]!r}"
|
||||
assert expectData == data, f"Expected {expectData!r}, got {data!r}"
|
||||
|
||||
def addProto(self, num: object, proto: object) -> None:
|
||||
# IRawDatagramProtocol.addProto
|
||||
pass
|
||||
|
||||
|
||||
class IPTests(unittest.TestCase):
|
||||
def testPacketParsing(self) -> None:
|
||||
proto = ip.IPProtocol()
|
||||
p1 = MyProtocol(
|
||||
[
|
||||
(
|
||||
b"foobar",
|
||||
{
|
||||
"partial": 0,
|
||||
"dest": "1.2.3.4",
|
||||
"source": "5.6.7.8",
|
||||
"protocol": 0x0F,
|
||||
"version": 4,
|
||||
"ihl": 20,
|
||||
"tos": 7,
|
||||
"tot_len": 20 + 6,
|
||||
"fragment_id": 0xDEAD,
|
||||
"fragment_offset": 0x1EEF,
|
||||
"dont_fragment": 0,
|
||||
"more_fragments": 1,
|
||||
"ttl": 0xC0,
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
proto.addProto(0x0F, p1)
|
||||
|
||||
proto.datagramReceived(
|
||||
b"\x54" # ihl version
|
||||
+ b"\x07" # tos
|
||||
+ b"\x00\x1a" # tot_len
|
||||
+ b"\xDE\xAD" # id
|
||||
+ b"\xBE\xEF" # frag_off
|
||||
+ b"\xC0" # ttl
|
||||
+ b"\x0F" # protocol
|
||||
+ b"FE" # checksum
|
||||
+ b"\x05\x06\x07\x08"
|
||||
+ b"\x01\x02\x03\x04"
|
||||
+ b"foobar",
|
||||
partial=0,
|
||||
dest="dummy",
|
||||
source="dummy",
|
||||
protocol="dummy",
|
||||
)
|
||||
|
||||
assert not p1.expecting, (
|
||||
"Should not expect any more packets, but still want %r" % p1.expecting
|
||||
)
|
||||
|
||||
def testMultiplePackets(self) -> None:
|
||||
proto = ip.IPProtocol()
|
||||
p1 = MyProtocol(
|
||||
[
|
||||
(
|
||||
b"foobar",
|
||||
{
|
||||
"partial": 0,
|
||||
"dest": "1.2.3.4",
|
||||
"source": "5.6.7.8",
|
||||
"protocol": 0x0F,
|
||||
"version": 4,
|
||||
"ihl": 20,
|
||||
"tos": 7,
|
||||
"tot_len": 20 + 6,
|
||||
"fragment_id": 0xDEAD,
|
||||
"fragment_offset": 0x1EEF,
|
||||
"dont_fragment": 0,
|
||||
"more_fragments": 1,
|
||||
"ttl": 0xC0,
|
||||
},
|
||||
),
|
||||
(
|
||||
b"quux",
|
||||
{
|
||||
"partial": 1,
|
||||
"dest": "5.4.3.2",
|
||||
"source": "6.7.8.9",
|
||||
"protocol": 0x0F,
|
||||
"version": 4,
|
||||
"ihl": 20,
|
||||
"tos": 7,
|
||||
"tot_len": 20 + 6,
|
||||
"fragment_id": 0xDEAD,
|
||||
"fragment_offset": 0x1EEF,
|
||||
"dont_fragment": 0,
|
||||
"more_fragments": 1,
|
||||
"ttl": 0xC0,
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
proto.addProto(0x0F, p1)
|
||||
proto.datagramReceived(
|
||||
b"\x54" # ihl version
|
||||
+ b"\x07" # tos
|
||||
+ b"\x00\x1a" # tot_len
|
||||
+ b"\xDE\xAD" # id
|
||||
+ b"\xBE\xEF" # frag_off
|
||||
+ b"\xC0" # ttl
|
||||
+ b"\x0F" # protocol
|
||||
+ b"FE" # checksum
|
||||
+ b"\x05\x06\x07\x08"
|
||||
+ b"\x01\x02\x03\x04"
|
||||
+ b"foobar",
|
||||
partial=0,
|
||||
dest="dummy",
|
||||
source="dummy",
|
||||
protocol="dummy",
|
||||
)
|
||||
proto.datagramReceived(
|
||||
b"\x54" # ihl version
|
||||
+ b"\x07" # tos
|
||||
+ b"\x00\x1a" # tot_len
|
||||
+ b"\xDE\xAD" # id
|
||||
+ b"\xBE\xEF" # frag_off
|
||||
+ b"\xC0" # ttl
|
||||
+ b"\x0F" # protocol
|
||||
+ b"FE" # checksum
|
||||
+ b"\x06\x07\x08\x09"
|
||||
+ b"\x05\x04\x03\x02"
|
||||
+ b"quux",
|
||||
partial=1,
|
||||
dest="dummy",
|
||||
source="dummy",
|
||||
protocol="dummy",
|
||||
)
|
||||
|
||||
assert not p1.expecting, (
|
||||
"Should not expect any more packets, but still want %r" % p1.expecting
|
||||
)
|
||||
|
||||
def testMultipleSameProtos(self) -> None:
|
||||
proto = ip.IPProtocol()
|
||||
p1 = MyProtocol(
|
||||
[
|
||||
(
|
||||
b"foobar",
|
||||
{
|
||||
"partial": 0,
|
||||
"dest": "1.2.3.4",
|
||||
"source": "5.6.7.8",
|
||||
"protocol": 0x0F,
|
||||
"version": 4,
|
||||
"ihl": 20,
|
||||
"tos": 7,
|
||||
"tot_len": 20 + 6,
|
||||
"fragment_id": 0xDEAD,
|
||||
"fragment_offset": 0x1EEF,
|
||||
"dont_fragment": 0,
|
||||
"more_fragments": 1,
|
||||
"ttl": 0xC0,
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
p2 = MyProtocol(
|
||||
[
|
||||
(
|
||||
b"foobar",
|
||||
{
|
||||
"partial": 0,
|
||||
"dest": "1.2.3.4",
|
||||
"source": "5.6.7.8",
|
||||
"protocol": 0x0F,
|
||||
"version": 4,
|
||||
"ihl": 20,
|
||||
"tos": 7,
|
||||
"tot_len": 20 + 6,
|
||||
"fragment_id": 0xDEAD,
|
||||
"fragment_offset": 0x1EEF,
|
||||
"dont_fragment": 0,
|
||||
"more_fragments": 1,
|
||||
"ttl": 0xC0,
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
proto.addProto(0x0F, p1)
|
||||
proto.addProto(0x0F, p2)
|
||||
|
||||
proto.datagramReceived(
|
||||
b"\x54" # ihl version
|
||||
+ b"\x07" # tos
|
||||
+ b"\x00\x1a" # tot_len
|
||||
+ b"\xDE\xAD" # id
|
||||
+ b"\xBE\xEF" # frag_off
|
||||
+ b"\xC0" # ttl
|
||||
+ b"\x0F" # protocol
|
||||
+ b"FE" # checksum
|
||||
+ b"\x05\x06\x07\x08"
|
||||
+ b"\x01\x02\x03\x04"
|
||||
+ b"foobar",
|
||||
partial=0,
|
||||
dest="dummy",
|
||||
source="dummy",
|
||||
protocol="dummy",
|
||||
)
|
||||
|
||||
assert not p1.expecting, (
|
||||
"Should not expect any more packets, but still want %r" % p1.expecting
|
||||
)
|
||||
assert not p2.expecting, (
|
||||
"Should not expect any more packets, but still want %r" % p2.expecting
|
||||
)
|
||||
|
||||
def testWrongProtoNotSeen(self) -> None:
|
||||
proto = ip.IPProtocol()
|
||||
p1 = MyProtocol([])
|
||||
proto.addProto(1, p1)
|
||||
|
||||
proto.datagramReceived(
|
||||
b"\x54" # ihl version
|
||||
+ b"\x07" # tos
|
||||
+ b"\x00\x1a" # tot_len
|
||||
+ b"\xDE\xAD" # id
|
||||
+ b"\xBE\xEF" # frag_off
|
||||
+ b"\xC0" # ttl
|
||||
+ b"\x0F" # protocol
|
||||
+ b"FE" # checksum
|
||||
+ b"\x05\x06\x07\x08"
|
||||
+ b"\x01\x02\x03\x04"
|
||||
+ b"foobar",
|
||||
partial=0,
|
||||
dest="dummy",
|
||||
source="dummy",
|
||||
protocol="dummy",
|
||||
)
|
||||
|
||||
def testDemuxing(self) -> None:
|
||||
proto = ip.IPProtocol()
|
||||
p1 = MyProtocol(
|
||||
[
|
||||
(
|
||||
b"foobar",
|
||||
{
|
||||
"partial": 0,
|
||||
"dest": "1.2.3.4",
|
||||
"source": "5.6.7.8",
|
||||
"protocol": 0x0F,
|
||||
"version": 4,
|
||||
"ihl": 20,
|
||||
"tos": 7,
|
||||
"tot_len": 20 + 6,
|
||||
"fragment_id": 0xDEAD,
|
||||
"fragment_offset": 0x1EEF,
|
||||
"dont_fragment": 0,
|
||||
"more_fragments": 1,
|
||||
"ttl": 0xC0,
|
||||
},
|
||||
),
|
||||
(
|
||||
b"quux",
|
||||
{
|
||||
"partial": 1,
|
||||
"dest": "5.4.3.2",
|
||||
"source": "6.7.8.9",
|
||||
"protocol": 0x0F,
|
||||
"version": 4,
|
||||
"ihl": 20,
|
||||
"tos": 7,
|
||||
"tot_len": 20 + 6,
|
||||
"fragment_id": 0xDEAD,
|
||||
"fragment_offset": 0x1EEF,
|
||||
"dont_fragment": 0,
|
||||
"more_fragments": 1,
|
||||
"ttl": 0xC0,
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
proto.addProto(0x0F, p1)
|
||||
|
||||
p2 = MyProtocol(
|
||||
[
|
||||
(
|
||||
b"quux",
|
||||
{
|
||||
"partial": 1,
|
||||
"dest": "5.4.3.2",
|
||||
"source": "6.7.8.9",
|
||||
"protocol": 0x0A,
|
||||
"version": 4,
|
||||
"ihl": 20,
|
||||
"tos": 7,
|
||||
"tot_len": 20 + 6,
|
||||
"fragment_id": 0xDEAD,
|
||||
"fragment_offset": 0x1EEF,
|
||||
"dont_fragment": 0,
|
||||
"more_fragments": 1,
|
||||
"ttl": 0xC0,
|
||||
},
|
||||
),
|
||||
(
|
||||
b"foobar",
|
||||
{
|
||||
"partial": 0,
|
||||
"dest": "1.2.3.4",
|
||||
"source": "5.6.7.8",
|
||||
"protocol": 0x0A,
|
||||
"version": 4,
|
||||
"ihl": 20,
|
||||
"tos": 7,
|
||||
"tot_len": 20 + 6,
|
||||
"fragment_id": 0xDEAD,
|
||||
"fragment_offset": 0x1EEF,
|
||||
"dont_fragment": 0,
|
||||
"more_fragments": 1,
|
||||
"ttl": 0xC0,
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
proto.addProto(0x0A, p2)
|
||||
|
||||
proto.datagramReceived(
|
||||
b"\x54" # ihl version
|
||||
+ b"\x07" # tos
|
||||
+ b"\x00\x1a" # tot_len
|
||||
+ b"\xDE\xAD" # id
|
||||
+ b"\xBE\xEF" # frag_off
|
||||
+ b"\xC0" # ttl
|
||||
+ b"\x0A" # protocol
|
||||
+ b"FE" # checksum
|
||||
+ b"\x06\x07\x08\x09"
|
||||
+ b"\x05\x04\x03\x02"
|
||||
+ b"quux",
|
||||
partial=1,
|
||||
dest="dummy",
|
||||
source="dummy",
|
||||
protocol="dummy",
|
||||
)
|
||||
proto.datagramReceived(
|
||||
b"\x54" # ihl version
|
||||
+ b"\x07" # tos
|
||||
+ b"\x00\x1a" # tot_len
|
||||
+ b"\xDE\xAD" # id
|
||||
+ b"\xBE\xEF" # frag_off
|
||||
+ b"\xC0" # ttl
|
||||
+ b"\x0F" # protocol
|
||||
+ b"FE" # checksum
|
||||
+ b"\x05\x06\x07\x08"
|
||||
+ b"\x01\x02\x03\x04"
|
||||
+ b"foobar",
|
||||
partial=0,
|
||||
dest="dummy",
|
||||
source="dummy",
|
||||
protocol="dummy",
|
||||
)
|
||||
proto.datagramReceived(
|
||||
b"\x54" # ihl version
|
||||
+ b"\x07" # tos
|
||||
+ b"\x00\x1a" # tot_len
|
||||
+ b"\xDE\xAD" # id
|
||||
+ b"\xBE\xEF" # frag_off
|
||||
+ b"\xC0" # ttl
|
||||
+ b"\x0F" # protocol
|
||||
+ b"FE" # checksum
|
||||
+ b"\x06\x07\x08\x09"
|
||||
+ b"\x05\x04\x03\x02"
|
||||
+ b"quux",
|
||||
partial=1,
|
||||
dest="dummy",
|
||||
source="dummy",
|
||||
protocol="dummy",
|
||||
)
|
||||
proto.datagramReceived(
|
||||
b"\x54" # ihl version
|
||||
+ b"\x07" # tos
|
||||
+ b"\x00\x1a" # tot_len
|
||||
+ b"\xDE\xAD" # id
|
||||
+ b"\xBE\xEF" # frag_off
|
||||
+ b"\xC0" # ttl
|
||||
+ b"\x0A" # protocol
|
||||
+ b"FE" # checksum
|
||||
+ b"\x05\x06\x07\x08"
|
||||
+ b"\x01\x02\x03\x04"
|
||||
+ b"foobar",
|
||||
partial=0,
|
||||
dest="dummy",
|
||||
source="dummy",
|
||||
protocol="dummy",
|
||||
)
|
||||
|
||||
assert not p1.expecting, (
|
||||
"Should not expect any more packets, but still want %r" % p1.expecting
|
||||
)
|
||||
assert not p2.expecting, (
|
||||
"Should not expect any more packets, but still want %r" % p2.expecting
|
||||
)
|
||||
|
||||
def testAddingBadProtos_WrongLevel(self) -> None:
|
||||
"""Adding a wrong level protocol raises an exception."""
|
||||
e = ip.IPProtocol()
|
||||
try:
|
||||
e.addProto(42, "silliness")
|
||||
except components.CannotAdapt:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError("addProto must raise an exception for bad protocols")
|
||||
|
||||
def testAddingBadProtos_TooSmall(self) -> None:
|
||||
"""Adding a protocol with a negative number raises an exception."""
|
||||
e = ip.IPProtocol()
|
||||
try:
|
||||
e.addProto(-1, MyProtocol([]))
|
||||
except TypeError as e:
|
||||
if e.args == ("Added protocol must be positive or zero",):
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
raise AssertionError("addProto must raise an exception for bad protocols")
|
||||
|
||||
def testAddingBadProtos_TooBig(self) -> None:
|
||||
"""Adding a protocol with a number >=2**32 raises an exception."""
|
||||
e = ip.IPProtocol()
|
||||
try:
|
||||
e.addProto(2**32, MyProtocol([]))
|
||||
except TypeError as e:
|
||||
if e.args == ("Added protocol must fit in 32 bits",):
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
raise AssertionError("addProto must raise an exception for bad protocols")
|
||||
|
||||
def testAddingBadProtos_TooBig2(self) -> None:
|
||||
"""Adding a protocol with a number >=2**32 raises an exception."""
|
||||
e = ip.IPProtocol()
|
||||
try:
|
||||
e.addProto(2**32 + 1, MyProtocol([]))
|
||||
except TypeError as e:
|
||||
if e.args == ("Added protocol must fit in 32 bits",):
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
raise AssertionError("addProto must raise an exception for bad protocols")
|
||||
@@ -0,0 +1,351 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
from __future__ import annotations
|
||||
|
||||
from twisted.internet import protocol
|
||||
from twisted.pair import rawudp
|
||||
|
||||
#
|
||||
from twisted.trial import unittest
|
||||
|
||||
|
||||
class MyProtocol(protocol.DatagramProtocol):
|
||||
def __init__(self, expecting: list[tuple[bytes, bytes, int]]) -> None:
|
||||
self.expecting = list(expecting)
|
||||
|
||||
def datagramReceived(self, data: bytes, peer: tuple[bytes, int]) -> None:
|
||||
(host, port) = peer
|
||||
assert self.expecting, "Got a packet when not expecting anymore."
|
||||
expectData, expectHost, expectPort = self.expecting.pop(0)
|
||||
|
||||
assert expectData == data, "Expected data {!r}, got {!r}".format(
|
||||
expectData, data
|
||||
)
|
||||
assert expectHost == host, "Expected host {!r}, got {!r}".format(
|
||||
expectHost, host
|
||||
)
|
||||
assert expectPort == port, "Expected port %d=0x%04x, got %d=0x%04x" % (
|
||||
expectPort,
|
||||
expectPort,
|
||||
port,
|
||||
port,
|
||||
)
|
||||
|
||||
|
||||
class RawUDPTests(unittest.TestCase):
|
||||
def testPacketParsing(self) -> None:
|
||||
proto = rawudp.RawUDPProtocol()
|
||||
p1 = MyProtocol(
|
||||
[
|
||||
(b"foobar", b"testHost", 0x43A2),
|
||||
]
|
||||
)
|
||||
proto.addProto(0xF00F, p1)
|
||||
|
||||
proto.datagramReceived(
|
||||
b"\x43\xA2" # source
|
||||
b"\xf0\x0f" # dest
|
||||
b"\x00\x06" # len
|
||||
b"\xDE\xAD" # check
|
||||
b"foobar",
|
||||
partial=0,
|
||||
dest=b"dummy",
|
||||
source=b"testHost",
|
||||
protocol=b"dummy",
|
||||
version=b"dummy",
|
||||
ihl=b"dummy",
|
||||
tos=b"dummy",
|
||||
tot_len=b"dummy",
|
||||
fragment_id=b"dummy",
|
||||
fragment_offset=b"dummy",
|
||||
dont_fragment=b"dummy",
|
||||
more_fragments=b"dummy",
|
||||
ttl=b"dummy",
|
||||
)
|
||||
|
||||
assert not p1.expecting, (
|
||||
"Should not expect any more packets, but still want %r" % p1.expecting
|
||||
)
|
||||
|
||||
def testMultiplePackets(self) -> None:
|
||||
proto = rawudp.RawUDPProtocol()
|
||||
p1 = MyProtocol(
|
||||
[
|
||||
(b"foobar", b"testHost", 0x43A2),
|
||||
(b"quux", b"otherHost", 0x33FE),
|
||||
]
|
||||
)
|
||||
proto.addProto(0xF00F, p1)
|
||||
proto.datagramReceived(
|
||||
b"\x43\xA2" # source
|
||||
b"\xf0\x0f" # dest
|
||||
b"\x00\x06" # len
|
||||
b"\xDE\xAD" # check
|
||||
b"foobar",
|
||||
partial=0,
|
||||
dest=b"dummy",
|
||||
source=b"testHost",
|
||||
protocol=b"dummy",
|
||||
version=b"dummy",
|
||||
ihl=b"dummy",
|
||||
tos=b"dummy",
|
||||
tot_len=b"dummy",
|
||||
fragment_id=b"dummy",
|
||||
fragment_offset=b"dummy",
|
||||
dont_fragment=b"dummy",
|
||||
more_fragments=b"dummy",
|
||||
ttl=b"dummy",
|
||||
)
|
||||
proto.datagramReceived(
|
||||
b"\x33\xFE" # source
|
||||
b"\xf0\x0f" # dest
|
||||
b"\x00\x05" # len
|
||||
b"\xDE\xAD" # check
|
||||
b"quux",
|
||||
partial=0,
|
||||
dest=b"dummy",
|
||||
source=b"otherHost",
|
||||
protocol=b"dummy",
|
||||
version=b"dummy",
|
||||
ihl=b"dummy",
|
||||
tos=b"dummy",
|
||||
tot_len=b"dummy",
|
||||
fragment_id=b"dummy",
|
||||
fragment_offset=b"dummy",
|
||||
dont_fragment=b"dummy",
|
||||
more_fragments=b"dummy",
|
||||
ttl=b"dummy",
|
||||
)
|
||||
|
||||
assert not p1.expecting, (
|
||||
"Should not expect any more packets, but still want %r" % p1.expecting
|
||||
)
|
||||
|
||||
def testMultipleSameProtos(self) -> None:
|
||||
proto = rawudp.RawUDPProtocol()
|
||||
p1 = MyProtocol(
|
||||
[
|
||||
(b"foobar", b"testHost", 0x43A2),
|
||||
]
|
||||
)
|
||||
|
||||
p2 = MyProtocol(
|
||||
[
|
||||
(b"foobar", b"testHost", 0x43A2),
|
||||
]
|
||||
)
|
||||
|
||||
proto.addProto(0xF00F, p1)
|
||||
proto.addProto(0xF00F, p2)
|
||||
|
||||
proto.datagramReceived(
|
||||
b"\x43\xA2" # source
|
||||
b"\xf0\x0f" # dest
|
||||
b"\x00\x06" # len
|
||||
b"\xDE\xAD" # check
|
||||
b"foobar",
|
||||
partial=0,
|
||||
dest=b"dummy",
|
||||
source=b"testHost",
|
||||
protocol=b"dummy",
|
||||
version=b"dummy",
|
||||
ihl=b"dummy",
|
||||
tos=b"dummy",
|
||||
tot_len=b"dummy",
|
||||
fragment_id=b"dummy",
|
||||
fragment_offset=b"dummy",
|
||||
dont_fragment=b"dummy",
|
||||
more_fragments=b"dummy",
|
||||
ttl=b"dummy",
|
||||
)
|
||||
|
||||
assert not p1.expecting, (
|
||||
"Should not expect any more packets, but still want %r" % p1.expecting
|
||||
)
|
||||
assert not p2.expecting, (
|
||||
"Should not expect any more packets, but still want %r" % p2.expecting
|
||||
)
|
||||
|
||||
def testWrongProtoNotSeen(self) -> None:
|
||||
proto = rawudp.RawUDPProtocol()
|
||||
p1 = MyProtocol([])
|
||||
proto.addProto(1, p1)
|
||||
|
||||
proto.datagramReceived(
|
||||
b"\x43\xA2" # source
|
||||
b"\xf0\x0f" # dest
|
||||
b"\x00\x06" # len
|
||||
b"\xDE\xAD" # check
|
||||
b"foobar",
|
||||
partial=0,
|
||||
dest=b"dummy",
|
||||
source=b"testHost",
|
||||
protocol=b"dummy",
|
||||
version=b"dummy",
|
||||
ihl=b"dummy",
|
||||
tos=b"dummy",
|
||||
tot_len=b"dummy",
|
||||
fragment_id=b"dummy",
|
||||
fragment_offset=b"dummy",
|
||||
dont_fragment=b"dummy",
|
||||
more_fragments=b"dummy",
|
||||
ttl=b"dummy",
|
||||
)
|
||||
|
||||
def testDemuxing(self) -> None:
|
||||
proto = rawudp.RawUDPProtocol()
|
||||
p1 = MyProtocol(
|
||||
[
|
||||
(b"foobar", b"testHost", 0x43A2),
|
||||
(b"quux", b"otherHost", 0x33FE),
|
||||
]
|
||||
)
|
||||
proto.addProto(0xF00F, p1)
|
||||
|
||||
p2 = MyProtocol(
|
||||
[
|
||||
(b"quux", b"otherHost", 0xA401),
|
||||
(b"foobar", b"testHost", 0xA302),
|
||||
]
|
||||
)
|
||||
proto.addProto(0xB050, p2)
|
||||
|
||||
proto.datagramReceived(
|
||||
b"\xA4\x01" # source
|
||||
b"\xB0\x50" # dest
|
||||
b"\x00\x05" # len
|
||||
b"\xDE\xAD" # check
|
||||
b"quux",
|
||||
partial=0,
|
||||
dest=b"dummy",
|
||||
source=b"otherHost",
|
||||
protocol=b"dummy",
|
||||
version=b"dummy",
|
||||
ihl=b"dummy",
|
||||
tos=b"dummy",
|
||||
tot_len=b"dummy",
|
||||
fragment_id=b"dummy",
|
||||
fragment_offset=b"dummy",
|
||||
dont_fragment=b"dummy",
|
||||
more_fragments=b"dummy",
|
||||
ttl=b"dummy",
|
||||
)
|
||||
proto.datagramReceived(
|
||||
b"\x43\xA2" # source
|
||||
b"\xf0\x0f" # dest
|
||||
b"\x00\x06" # len
|
||||
b"\xDE\xAD" # check
|
||||
b"foobar",
|
||||
partial=0,
|
||||
dest=b"dummy",
|
||||
source=b"testHost",
|
||||
protocol=b"dummy",
|
||||
version=b"dummy",
|
||||
ihl=b"dummy",
|
||||
tos=b"dummy",
|
||||
tot_len=b"dummy",
|
||||
fragment_id=b"dummy",
|
||||
fragment_offset=b"dummy",
|
||||
dont_fragment=b"dummy",
|
||||
more_fragments=b"dummy",
|
||||
ttl=b"dummy",
|
||||
)
|
||||
proto.datagramReceived(
|
||||
b"\x33\xFE" # source
|
||||
b"\xf0\x0f" # dest
|
||||
b"\x00\x05" # len
|
||||
b"\xDE\xAD" # check
|
||||
b"quux",
|
||||
partial=0,
|
||||
dest=b"dummy",
|
||||
source=b"otherHost",
|
||||
protocol=b"dummy",
|
||||
version=b"dummy",
|
||||
ihl=b"dummy",
|
||||
tos=b"dummy",
|
||||
tot_len=b"dummy",
|
||||
fragment_id=b"dummy",
|
||||
fragment_offset=b"dummy",
|
||||
dont_fragment=b"dummy",
|
||||
more_fragments=b"dummy",
|
||||
ttl=b"dummy",
|
||||
)
|
||||
proto.datagramReceived(
|
||||
b"\xA3\x02" # source
|
||||
b"\xB0\x50" # dest
|
||||
b"\x00\x06" # len
|
||||
b"\xDE\xAD" # check
|
||||
b"foobar",
|
||||
partial=0,
|
||||
dest=b"dummy",
|
||||
source=b"testHost",
|
||||
protocol=b"dummy",
|
||||
version=b"dummy",
|
||||
ihl=b"dummy",
|
||||
tos=b"dummy",
|
||||
tot_len=b"dummy",
|
||||
fragment_id=b"dummy",
|
||||
fragment_offset=b"dummy",
|
||||
dont_fragment=b"dummy",
|
||||
more_fragments=b"dummy",
|
||||
ttl=b"dummy",
|
||||
)
|
||||
|
||||
assert not p1.expecting, (
|
||||
"Should not expect any more packets, but still want %r" % p1.expecting
|
||||
)
|
||||
assert not p2.expecting, (
|
||||
"Should not expect any more packets, but still want %r" % p2.expecting
|
||||
)
|
||||
|
||||
def testAddingBadProtos_WrongLevel(self) -> None:
|
||||
"""Adding a wrong level protocol raises an exception."""
|
||||
e = rawudp.RawUDPProtocol()
|
||||
try:
|
||||
e.addProto(42, "silliness")
|
||||
except TypeError as e:
|
||||
if e.args == ("Added protocol must be an instance of DatagramProtocol",):
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
raise AssertionError("addProto must raise an exception for bad protocols")
|
||||
|
||||
def testAddingBadProtos_TooSmall(self) -> None:
|
||||
"""Adding a protocol with a negative number raises an exception."""
|
||||
e = rawudp.RawUDPProtocol()
|
||||
try:
|
||||
e.addProto(-1, protocol.DatagramProtocol())
|
||||
except TypeError as e:
|
||||
if e.args == ("Added protocol must be positive or zero",):
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
raise AssertionError("addProto must raise an exception for bad protocols")
|
||||
|
||||
def testAddingBadProtos_TooBig(self) -> None:
|
||||
"""Adding a protocol with a number >=2**16 raises an exception."""
|
||||
e = rawudp.RawUDPProtocol()
|
||||
try:
|
||||
e.addProto(2**16, protocol.DatagramProtocol())
|
||||
except TypeError as e:
|
||||
if e.args == ("Added protocol must fit in 16 bits",):
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
raise AssertionError("addProto must raise an exception for bad protocols")
|
||||
|
||||
def testAddingBadProtos_TooBig2(self) -> None:
|
||||
"""Adding a protocol with a number >=2**16 raises an exception."""
|
||||
e = rawudp.RawUDPProtocol()
|
||||
try:
|
||||
e.addProto(2**16 + 1, protocol.DatagramProtocol())
|
||||
except TypeError as e:
|
||||
if e.args == ("Added protocol must fit in 16 bits",):
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
raise AssertionError("addProto must raise an exception for bad protocols")
|
||||
1387
.venv/lib/python3.12/site-packages/twisted/pair/test/test_tuntap.py
Normal file
1387
.venv/lib/python3.12/site-packages/twisted/pair/test/test_tuntap.py
Normal file
File diff suppressed because it is too large
Load Diff
555
.venv/lib/python3.12/site-packages/twisted/pair/testing.py
Normal file
555
.venv/lib/python3.12/site-packages/twisted/pair/testing.py
Normal file
@@ -0,0 +1,555 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tools for automated testing of L{twisted.pair}-based applications.
|
||||
"""
|
||||
|
||||
import socket
|
||||
import struct
|
||||
from collections import deque
|
||||
from errno import EAGAIN, EBADF, EINTR, EINVAL, ENOBUFS, ENOSYS, EPERM, EWOULDBLOCK
|
||||
from functools import wraps
|
||||
|
||||
from zope.interface import implementer
|
||||
|
||||
from twisted.internet.protocol import DatagramProtocol
|
||||
from twisted.pair.ethernet import EthernetProtocol
|
||||
from twisted.pair.ip import IPProtocol
|
||||
from twisted.pair.rawudp import RawUDPProtocol
|
||||
from twisted.pair.tuntap import _IFNAMSIZ, _TUNSETIFF, TunnelFlags, _IInputOutputSystem
|
||||
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:
|
||||
"""
|
||||
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 OSError(EINTR, "Interrupted system call")
|
||||
|
||||
if len(datagram) > self.SEND_BUFFER_SIZE:
|
||||
raise OSError(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 OSError(EPERM, "Operation not permitted")
|
||||
return original(self, *args, **kwargs)
|
||||
|
||||
return permissionChecker
|
||||
|
||||
|
||||
@implementer(_IInputOutputSystem)
|
||||
class MemoryIOSystem:
|
||||
"""
|
||||
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 = {"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 OSError(EBADF, "Bad file descriptor")
|
||||
|
||||
if request != _TUNSETIFF:
|
||||
raise OSError(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:
|
||||
"""
|
||||
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]
|
||||
423
.venv/lib/python3.12/site-packages/twisted/pair/tuntap.py
Normal file
423
.venv/lib/python3.12/site-packages/twisted/pair/tuntap.py
Normal file
@@ -0,0 +1,423 @@
|
||||
# -*- 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 errno
|
||||
import fcntl
|
||||
import os
|
||||
import platform
|
||||
import struct
|
||||
import warnings
|
||||
from collections import namedtuple
|
||||
from typing import Tuple
|
||||
|
||||
from zope.interface import Attribute, Interface, implementer
|
||||
|
||||
from constantly import FlagConstant, Flags
|
||||
from incremental import Version
|
||||
|
||||
from twisted.internet import abstract, defer, error, interfaces, task
|
||||
from twisted.pair import ethernet, raw
|
||||
from twisted.python import log
|
||||
from twisted.python.deprecate import deprecated
|
||||
from twisted.python.reflect import fullyQualifiedName
|
||||
from twisted.python.util import FancyEqMixin, FancyStrMixin
|
||||
|
||||
__all__ = [
|
||||
"TunnelFlags",
|
||||
"TunnelAddress",
|
||||
"TuntapPort",
|
||||
]
|
||||
|
||||
|
||||
_IFNAMSIZ = 16
|
||||
if (
|
||||
platform.machine() == "parisc"
|
||||
or platform.machine().startswith("ppc")
|
||||
or platform.machine().startswith("sparc")
|
||||
): # pragma: no coverage
|
||||
# We don't have CI for parisc, hence no coverage is expected.
|
||||
_TUNSETIFF = 0x800454CA
|
||||
_TUNGETIFF = 0x400454D2
|
||||
else:
|
||||
_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):
|
||||
"""
|
||||
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:
|
||||
"""
|
||||
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 = f"{logPrefix} ({self._mode.name})"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
args: Tuple[str, ...] = (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 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 OSError as e:
|
||||
if e.errno in (errno.EWOULDBLOCK, errno.EAGAIN, errno.EINTR):
|
||||
return
|
||||
else:
|
||||
raise
|
||||
except BaseException:
|
||||
raise
|
||||
read += len(data)
|
||||
# TODO pkt.isPartial()?
|
||||
try:
|
||||
self.protocol.datagramReceived(data, partial=0)
|
||||
except BaseException:
|
||||
cls = fullyQualifiedName(self.protocol.__class__)
|
||||
log.err(None, f"Unhandled exception from {cls}.datagramReceived")
|
||||
|
||||
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 OSError 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 seq: 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)
|
||||
|
||||
@deprecated(Version("Twisted", 14, 0, 0), stopListening)
|
||||
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)
|
||||
Reference in New Issue
Block a user