xref: /aosp_15_r20/kernel/tests/net/test/tun_twister.py (revision 2f2c4c7ab4226c71756b9c31670392fdd6887c4f)
1*2f2c4c7aSAndroid Build Coastguard Worker# Copyright 2017 The Android Open Source Project
2*2f2c4c7aSAndroid Build Coastguard Worker#
3*2f2c4c7aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*2f2c4c7aSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*2f2c4c7aSAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*2f2c4c7aSAndroid Build Coastguard Worker#
7*2f2c4c7aSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0
8*2f2c4c7aSAndroid Build Coastguard Worker#
9*2f2c4c7aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*2f2c4c7aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*2f2c4c7aSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*2f2c4c7aSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*2f2c4c7aSAndroid Build Coastguard Worker# limitations under the License.
14*2f2c4c7aSAndroid Build Coastguard Worker"""A utility for "twisting" packets on a tun/tap interface.
15*2f2c4c7aSAndroid Build Coastguard Worker
16*2f2c4c7aSAndroid Build Coastguard WorkerTunTwister and TapTwister echo packets on a tun/tap while swapping the source
17*2f2c4c7aSAndroid Build Coastguard Workerand destination at the ethernet and IP layers. This allows sockets to
18*2f2c4c7aSAndroid Build Coastguard Workereffectively loop back packets through the full networking stack, avoiding any
19*2f2c4c7aSAndroid Build Coastguard Workershortcuts the kernel may take for actual IP loopback. Additionally, users can
20*2f2c4c7aSAndroid Build Coastguard Workerinspect each packet to assert testing invariants.
21*2f2c4c7aSAndroid Build Coastguard Worker"""
22*2f2c4c7aSAndroid Build Coastguard Worker
23*2f2c4c7aSAndroid Build Coastguard Workerimport os
24*2f2c4c7aSAndroid Build Coastguard Workerimport select
25*2f2c4c7aSAndroid Build Coastguard Workerimport threading
26*2f2c4c7aSAndroid Build Coastguard Workerfrom scapy import all as scapy
27*2f2c4c7aSAndroid Build Coastguard Worker
28*2f2c4c7aSAndroid Build Coastguard Worker
29*2f2c4c7aSAndroid Build Coastguard Workerclass TunTwister(object):
30*2f2c4c7aSAndroid Build Coastguard Worker  """TunTwister transports traffic travelling twixt two terminals.
31*2f2c4c7aSAndroid Build Coastguard Worker
32*2f2c4c7aSAndroid Build Coastguard Worker  TunTwister is a context manager that will read packets from a tun file
33*2f2c4c7aSAndroid Build Coastguard Worker  descriptor, swap the source and dest of the IP header, and write them back.
34*2f2c4c7aSAndroid Build Coastguard Worker  To use this class, tests also need to set up routing so that packets will be
35*2f2c4c7aSAndroid Build Coastguard Worker  routed to the tun interface.
36*2f2c4c7aSAndroid Build Coastguard Worker
37*2f2c4c7aSAndroid Build Coastguard Worker  Two sockets can communicate with each other through a TunTwister as if they
38*2f2c4c7aSAndroid Build Coastguard Worker  were each connecting to a remote endpoint. Both sockets will have the
39*2f2c4c7aSAndroid Build Coastguard Worker  perspective that the address of the other is a remote address.
40*2f2c4c7aSAndroid Build Coastguard Worker
41*2f2c4c7aSAndroid Build Coastguard Worker  Packet inspection can be done with a validator function. This can be any
42*2f2c4c7aSAndroid Build Coastguard Worker  function that takes a scapy packet object as its only argument. Exceptions
43*2f2c4c7aSAndroid Build Coastguard Worker  raised by your validator function will be re-raised on the main thread to fail
44*2f2c4c7aSAndroid Build Coastguard Worker  your tests.
45*2f2c4c7aSAndroid Build Coastguard Worker
46*2f2c4c7aSAndroid Build Coastguard Worker  NOTE: Exceptions raised by a validator function will supercede exceptions
47*2f2c4c7aSAndroid Build Coastguard Worker  raised in the context.
48*2f2c4c7aSAndroid Build Coastguard Worker
49*2f2c4c7aSAndroid Build Coastguard Worker  EXAMPLE:
50*2f2c4c7aSAndroid Build Coastguard Worker    def testFeatureFoo(self):
51*2f2c4c7aSAndroid Build Coastguard Worker      my_tun = MakeTunInterface()
52*2f2c4c7aSAndroid Build Coastguard Worker      # Set up routing so packets go to my_tun.
53*2f2c4c7aSAndroid Build Coastguard Worker
54*2f2c4c7aSAndroid Build Coastguard Worker      def ValidatePortNumber(packet):
55*2f2c4c7aSAndroid Build Coastguard Worker        self.assertEqual(8080, packet.getlayer(scapy.UDP).sport)
56*2f2c4c7aSAndroid Build Coastguard Worker        self.assertEqual(8080, packet.getlayer(scapy.UDP).dport)
57*2f2c4c7aSAndroid Build Coastguard Worker
58*2f2c4c7aSAndroid Build Coastguard Worker      with TunTwister(tun_fd=my_tun, validator=ValidatePortNumber):
59*2f2c4c7aSAndroid Build Coastguard Worker        sock = socket(AF_INET, SOCK_DGRAM, 0)
60*2f2c4c7aSAndroid Build Coastguard Worker        sock.bind(("0.0.0.0", 8080))
61*2f2c4c7aSAndroid Build Coastguard Worker        sock.settimeout(1.0)
62*2f2c4c7aSAndroid Build Coastguard Worker        sock.sendto("hello", ("1.2.3.4", 8080))
63*2f2c4c7aSAndroid Build Coastguard Worker        data, addr = sock.recvfrom(1024)
64*2f2c4c7aSAndroid Build Coastguard Worker        self.assertEqual(b"hello", data)
65*2f2c4c7aSAndroid Build Coastguard Worker        self.assertEqual(("1.2.3.4", 8080), addr)
66*2f2c4c7aSAndroid Build Coastguard Worker  """
67*2f2c4c7aSAndroid Build Coastguard Worker
68*2f2c4c7aSAndroid Build Coastguard Worker  # Hopefully larger than any packet.
69*2f2c4c7aSAndroid Build Coastguard Worker  _READ_BUF_SIZE = 2048
70*2f2c4c7aSAndroid Build Coastguard Worker  _POLL_TIMEOUT_SEC = 2.0
71*2f2c4c7aSAndroid Build Coastguard Worker  _POLL_FAST_TIMEOUT_MS = 100
72*2f2c4c7aSAndroid Build Coastguard Worker
73*2f2c4c7aSAndroid Build Coastguard Worker  def __init__(self, fd=None, validator=None):
74*2f2c4c7aSAndroid Build Coastguard Worker    """Construct a TunTwister.
75*2f2c4c7aSAndroid Build Coastguard Worker
76*2f2c4c7aSAndroid Build Coastguard Worker    The TunTwister will listen on the given TUN fd.
77*2f2c4c7aSAndroid Build Coastguard Worker    The validator is called for each packet *before* twisting. The packet is
78*2f2c4c7aSAndroid Build Coastguard Worker    passed in as a scapy packet object, and is the only argument passed to the
79*2f2c4c7aSAndroid Build Coastguard Worker    validator.
80*2f2c4c7aSAndroid Build Coastguard Worker
81*2f2c4c7aSAndroid Build Coastguard Worker    Args:
82*2f2c4c7aSAndroid Build Coastguard Worker      fd: File descriptor of a TUN interface.
83*2f2c4c7aSAndroid Build Coastguard Worker      validator: Function taking one scapy packet object argument.
84*2f2c4c7aSAndroid Build Coastguard Worker    """
85*2f2c4c7aSAndroid Build Coastguard Worker    self._fd = fd
86*2f2c4c7aSAndroid Build Coastguard Worker    # Use a pipe to signal the thread to exit.
87*2f2c4c7aSAndroid Build Coastguard Worker    self._signal_read, self._signal_write = os.pipe()
88*2f2c4c7aSAndroid Build Coastguard Worker    self._thread = threading.Thread(target=self._RunLoop, name="TunTwister")
89*2f2c4c7aSAndroid Build Coastguard Worker    self._validator = validator
90*2f2c4c7aSAndroid Build Coastguard Worker    self._error = None
91*2f2c4c7aSAndroid Build Coastguard Worker
92*2f2c4c7aSAndroid Build Coastguard Worker  def __enter__(self):
93*2f2c4c7aSAndroid Build Coastguard Worker    self._thread.start()
94*2f2c4c7aSAndroid Build Coastguard Worker
95*2f2c4c7aSAndroid Build Coastguard Worker  def __exit__(self, *args):
96*2f2c4c7aSAndroid Build Coastguard Worker    # Signal thread exit.
97*2f2c4c7aSAndroid Build Coastguard Worker    os.write(self._signal_write, b"bye")
98*2f2c4c7aSAndroid Build Coastguard Worker    os.close(self._signal_write)
99*2f2c4c7aSAndroid Build Coastguard Worker    self._thread.join(TunTwister._POLL_TIMEOUT_SEC)
100*2f2c4c7aSAndroid Build Coastguard Worker    os.close(self._signal_read)
101*2f2c4c7aSAndroid Build Coastguard Worker    if self._thread.is_alive():
102*2f2c4c7aSAndroid Build Coastguard Worker      raise RuntimeError("Timed out waiting for thread exit")
103*2f2c4c7aSAndroid Build Coastguard Worker    # Re-raise any error thrown from our thread.
104*2f2c4c7aSAndroid Build Coastguard Worker    if isinstance(self._error, Exception):
105*2f2c4c7aSAndroid Build Coastguard Worker      raise self._error  # pylint: disable=raising-bad-type
106*2f2c4c7aSAndroid Build Coastguard Worker
107*2f2c4c7aSAndroid Build Coastguard Worker  def _RunLoop(self):
108*2f2c4c7aSAndroid Build Coastguard Worker    """Twist packets until exit signal."""
109*2f2c4c7aSAndroid Build Coastguard Worker    try:
110*2f2c4c7aSAndroid Build Coastguard Worker      while True:
111*2f2c4c7aSAndroid Build Coastguard Worker        read_fds, _, _ = select.select([self._fd, self._signal_read], [], [],
112*2f2c4c7aSAndroid Build Coastguard Worker                                       TunTwister._POLL_TIMEOUT_SEC)
113*2f2c4c7aSAndroid Build Coastguard Worker        if self._signal_read in read_fds:
114*2f2c4c7aSAndroid Build Coastguard Worker          self._Flush()
115*2f2c4c7aSAndroid Build Coastguard Worker          return
116*2f2c4c7aSAndroid Build Coastguard Worker        if self._fd in read_fds:
117*2f2c4c7aSAndroid Build Coastguard Worker          self._ProcessPacket()
118*2f2c4c7aSAndroid Build Coastguard Worker    except Exception as e:  # pylint: disable=broad-except
119*2f2c4c7aSAndroid Build Coastguard Worker      self._error = e
120*2f2c4c7aSAndroid Build Coastguard Worker
121*2f2c4c7aSAndroid Build Coastguard Worker  def _Flush(self):
122*2f2c4c7aSAndroid Build Coastguard Worker    """Ensure no packets are left in the buffer."""
123*2f2c4c7aSAndroid Build Coastguard Worker    p = select.poll()
124*2f2c4c7aSAndroid Build Coastguard Worker    p.register(self._fd, select.POLLIN)
125*2f2c4c7aSAndroid Build Coastguard Worker    while p.poll(TunTwister._POLL_FAST_TIMEOUT_MS):
126*2f2c4c7aSAndroid Build Coastguard Worker      self._ProcessPacket()
127*2f2c4c7aSAndroid Build Coastguard Worker
128*2f2c4c7aSAndroid Build Coastguard Worker  def _ProcessPacket(self):
129*2f2c4c7aSAndroid Build Coastguard Worker    """Read, twist, and write one packet on the tun/tap."""
130*2f2c4c7aSAndroid Build Coastguard Worker    # TODO: Handle EAGAIN "errors".
131*2f2c4c7aSAndroid Build Coastguard Worker    bytes_in = os.read(self._fd, TunTwister._READ_BUF_SIZE)
132*2f2c4c7aSAndroid Build Coastguard Worker    packet = self.DecodePacket(bytes_in)
133*2f2c4c7aSAndroid Build Coastguard Worker    # the user may wish to filter certain packets, such as
134*2f2c4c7aSAndroid Build Coastguard Worker    # Ethernet multicast packets
135*2f2c4c7aSAndroid Build Coastguard Worker    if self._DropPacket(packet):
136*2f2c4c7aSAndroid Build Coastguard Worker      return
137*2f2c4c7aSAndroid Build Coastguard Worker
138*2f2c4c7aSAndroid Build Coastguard Worker    if self._validator:
139*2f2c4c7aSAndroid Build Coastguard Worker      self._validator(packet)
140*2f2c4c7aSAndroid Build Coastguard Worker    packet = self.TwistPacket(packet)
141*2f2c4c7aSAndroid Build Coastguard Worker    os.write(self._fd, packet.build())
142*2f2c4c7aSAndroid Build Coastguard Worker
143*2f2c4c7aSAndroid Build Coastguard Worker  def _DropPacket(self, packet):
144*2f2c4c7aSAndroid Build Coastguard Worker    """Determine whether to drop the provided packet by inspection"""
145*2f2c4c7aSAndroid Build Coastguard Worker    return False
146*2f2c4c7aSAndroid Build Coastguard Worker
147*2f2c4c7aSAndroid Build Coastguard Worker  @classmethod
148*2f2c4c7aSAndroid Build Coastguard Worker  def DecodePacket(cls, bytes_in):
149*2f2c4c7aSAndroid Build Coastguard Worker    """Decode a byte array into a scapy object."""
150*2f2c4c7aSAndroid Build Coastguard Worker    return cls._DecodeIpPacket(bytes_in)
151*2f2c4c7aSAndroid Build Coastguard Worker
152*2f2c4c7aSAndroid Build Coastguard Worker  @classmethod
153*2f2c4c7aSAndroid Build Coastguard Worker  def TwistPacket(cls, packet):
154*2f2c4c7aSAndroid Build Coastguard Worker    """Swap the src and dst in the IP header."""
155*2f2c4c7aSAndroid Build Coastguard Worker    ip_type = type(packet)
156*2f2c4c7aSAndroid Build Coastguard Worker    if ip_type not in (scapy.IP, scapy.IPv6):
157*2f2c4c7aSAndroid Build Coastguard Worker      raise TypeError("Expected an IPv4 or IPv6 packet.")
158*2f2c4c7aSAndroid Build Coastguard Worker    packet.src, packet.dst = packet.dst, packet.src
159*2f2c4c7aSAndroid Build Coastguard Worker    packet = ip_type(packet.build())  # Fix the IP checksum.
160*2f2c4c7aSAndroid Build Coastguard Worker    return packet
161*2f2c4c7aSAndroid Build Coastguard Worker
162*2f2c4c7aSAndroid Build Coastguard Worker  @staticmethod
163*2f2c4c7aSAndroid Build Coastguard Worker  def _DecodeIpPacket(packet_bytes):
164*2f2c4c7aSAndroid Build Coastguard Worker    """Decode 'packet_bytes' as an IPv4 or IPv6 scapy object."""
165*2f2c4c7aSAndroid Build Coastguard Worker    ip_ver = (ord(packet_bytes[0]) & 0xF0) >> 4
166*2f2c4c7aSAndroid Build Coastguard Worker    if ip_ver == 4:
167*2f2c4c7aSAndroid Build Coastguard Worker      return scapy.IP(packet_bytes)
168*2f2c4c7aSAndroid Build Coastguard Worker    elif ip_ver == 6:
169*2f2c4c7aSAndroid Build Coastguard Worker      return scapy.IPv6(packet_bytes)
170*2f2c4c7aSAndroid Build Coastguard Worker    else:
171*2f2c4c7aSAndroid Build Coastguard Worker      raise ValueError("packet_bytes is not a valid IPv4 or IPv6 packet")
172*2f2c4c7aSAndroid Build Coastguard Worker
173*2f2c4c7aSAndroid Build Coastguard Worker
174*2f2c4c7aSAndroid Build Coastguard Workerclass TapTwister(TunTwister):
175*2f2c4c7aSAndroid Build Coastguard Worker  """Test util for tap interfaces.
176*2f2c4c7aSAndroid Build Coastguard Worker
177*2f2c4c7aSAndroid Build Coastguard Worker  TapTwister works just like TunTwister, except it operates on tap interfaces
178*2f2c4c7aSAndroid Build Coastguard Worker  instead of tuns. Ethernet headers will have their sources and destinations
179*2f2c4c7aSAndroid Build Coastguard Worker  swapped in addition to IP headers.
180*2f2c4c7aSAndroid Build Coastguard Worker  """
181*2f2c4c7aSAndroid Build Coastguard Worker
182*2f2c4c7aSAndroid Build Coastguard Worker  @staticmethod
183*2f2c4c7aSAndroid Build Coastguard Worker  def _IsMulticastPacket(eth_pkt):
184*2f2c4c7aSAndroid Build Coastguard Worker    return int(eth_pkt.dst.split(":")[0], 16) & 0x1
185*2f2c4c7aSAndroid Build Coastguard Worker
186*2f2c4c7aSAndroid Build Coastguard Worker  def __init__(self, fd=None, validator=None, drop_multicast=True):
187*2f2c4c7aSAndroid Build Coastguard Worker    """Construct a TapTwister.
188*2f2c4c7aSAndroid Build Coastguard Worker
189*2f2c4c7aSAndroid Build Coastguard Worker    TapTwister works just like TunTwister, but handles both ethernet and IP
190*2f2c4c7aSAndroid Build Coastguard Worker    headers.
191*2f2c4c7aSAndroid Build Coastguard Worker
192*2f2c4c7aSAndroid Build Coastguard Worker    Args:
193*2f2c4c7aSAndroid Build Coastguard Worker      fd: File descriptor of a TAP interface.
194*2f2c4c7aSAndroid Build Coastguard Worker      validator: Function taking one scapy packet object argument.
195*2f2c4c7aSAndroid Build Coastguard Worker      drop_multicast: Drop Ethernet multicast packets
196*2f2c4c7aSAndroid Build Coastguard Worker    """
197*2f2c4c7aSAndroid Build Coastguard Worker    super(TapTwister, self).__init__(fd=fd, validator=validator)
198*2f2c4c7aSAndroid Build Coastguard Worker    self._drop_multicast = drop_multicast
199*2f2c4c7aSAndroid Build Coastguard Worker
200*2f2c4c7aSAndroid Build Coastguard Worker  def _DropPacket(self, packet):
201*2f2c4c7aSAndroid Build Coastguard Worker    return self._drop_multicast and self._IsMulticastPacket(packet)
202*2f2c4c7aSAndroid Build Coastguard Worker
203*2f2c4c7aSAndroid Build Coastguard Worker  @classmethod
204*2f2c4c7aSAndroid Build Coastguard Worker  def DecodePacket(cls, bytes_in):
205*2f2c4c7aSAndroid Build Coastguard Worker    return scapy.Ether(bytes_in)
206*2f2c4c7aSAndroid Build Coastguard Worker
207*2f2c4c7aSAndroid Build Coastguard Worker  @classmethod
208*2f2c4c7aSAndroid Build Coastguard Worker  def TwistPacket(cls, packet):
209*2f2c4c7aSAndroid Build Coastguard Worker    """Swap the src and dst in the ethernet and IP headers."""
210*2f2c4c7aSAndroid Build Coastguard Worker    packet.src, packet.dst = packet.dst, packet.src
211*2f2c4c7aSAndroid Build Coastguard Worker    ip_layer = packet.payload
212*2f2c4c7aSAndroid Build Coastguard Worker    twisted_ip_layer = super(TapTwister, cls).TwistPacket(ip_layer)
213*2f2c4c7aSAndroid Build Coastguard Worker    packet.payload = twisted_ip_layer
214*2f2c4c7aSAndroid Build Coastguard Worker    return packet
215