#!/usr/bin/python # # Copyright 2019 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import unittest from errno import * # pylint: disable=wildcard-import from socket import * # pylint: disable=wildcard-import import ctypes import fcntl import os import random import select import termios import threading import time from scapy import all as scapy import multinetwork_base import net_test import packets SOL_TCP = net_test.SOL_TCP SHUT_RD = net_test.SHUT_RD SHUT_WR = net_test.SHUT_WR SHUT_RDWR = net_test.SHUT_RDWR SIOCINQ = termios.FIONREAD SIOCOUTQ = termios.TIOCOUTQ TEST_PORT = 5555 # Following constants are SOL_TCP level options and arguments. # They are defined in linux-kernel: include/uapi/linux/tcp.h # SOL_TCP level options. TCP_REPAIR = 19 TCP_REPAIR_QUEUE = 20 TCP_QUEUE_SEQ = 21 # TCP_REPAIR_{OFF, ON} is an argument to TCP_REPAIR. TCP_REPAIR_OFF = 0 TCP_REPAIR_ON = 1 # TCP_{NO, RECV, SEND}_QUEUE is an argument to TCP_REPAIR_QUEUE. TCP_NO_QUEUE = 0 TCP_RECV_QUEUE = 1 TCP_SEND_QUEUE = 2 # This test is aiming to ensure tcp keep alive offload works correctly # when it fetches tcp information from kernel via tcp repair mode. class TcpRepairTest(multinetwork_base.MultiNetworkBaseTest): def assertSocketNotConnected(self, sock): self.assertRaisesErrno(ENOTCONN, sock.getpeername) def assertSocketConnected(self, sock): sock.getpeername() # No errors? Socket is alive and connected. def createConnectedSocket(self, version, netid): s = net_test.TCPSocket(net_test.GetAddressFamily(version)) net_test.DisableFinWait(s) self.SelectInterface(s, netid, "mark") remotesockaddr = self.GetRemoteSocketAddress(version) remoteaddr = self.GetRemoteAddress(version) self.assertRaisesErrno(EINPROGRESS, s.connect, (remotesockaddr, TEST_PORT)) self.assertSocketNotConnected(s) myaddr = self.MyAddress(version, netid) port = s.getsockname()[1] self.assertNotEqual(0, port) desc, expect_syn = packets.SYN(TEST_PORT, version, myaddr, remoteaddr, port, seq=None) msg = "socket connect: expected %s" % desc syn = self.ExpectPacketOn(netid, msg, expect_syn) synack_desc, synack = packets.SYNACK(version, remoteaddr, myaddr, syn) synack.getlayer("TCP").seq = random.getrandbits(32) synack.getlayer("TCP").window = 14400 self.ReceivePacketOn(netid, synack) desc, ack = packets.ACK(version, myaddr, remoteaddr, synack) msg = "socket connect: got SYN+ACK, expected %s" % desc ack = self.ExpectPacketOn(netid, msg, ack) self.last_sent = ack self.last_received = synack return s def receiveFin(self, netid, version, sock): self.assertSocketConnected(sock) remoteaddr = self.GetRemoteAddress(version) myaddr = self.MyAddress(version, netid) desc, fin = packets.FIN(version, remoteaddr, myaddr, self.last_sent) self.ReceivePacketOn(netid, fin) self.last_received = fin def sendData(self, netid, version, sock, payload): sock.send(payload) remoteaddr = self.GetRemoteAddress(version) myaddr = self.MyAddress(version, netid) desc, send = packets.ACK(version, myaddr, remoteaddr, self.last_received, payload) self.last_sent = send def receiveData(self, netid, version, payload): remoteaddr = self.GetRemoteAddress(version) myaddr = self.MyAddress(version, netid) desc, received = packets.ACK(version, remoteaddr, myaddr, self.last_sent, payload) ack_desc, ack = packets.ACK(version, myaddr, remoteaddr, received) self.ReceivePacketOn(netid, received) time.sleep(0.1) self.ExpectPacketOn(netid, "expecting %s" % ack_desc, ack) self.last_sent = ack self.last_received = received # Test the behavior of NO_QUEUE. Expect incoming data will be stored into # the queue, but socket cannot be read/written in NO_QUEUE. def testTcpRepairInNoQueue(self): for version in [4, 5, 6]: self.tcpRepairInNoQueueTest(version) def tcpRepairInNoQueueTest(self, version): netid = self.RandomNetid() sock = self.createConnectedSocket(version, netid) sock.setsockopt(SOL_TCP, TCP_REPAIR, TCP_REPAIR_ON) # In repair mode with NO_QUEUE, writes fail... self.assertRaisesErrno(EINVAL, sock.send, "write test") # remote data is coming. TEST_RECEIVED = net_test.UDP_PAYLOAD self.receiveData(netid, version, TEST_RECEIVED) # In repair mode with NO_QUEUE, read fail... self.assertRaisesErrno(EPERM, sock.recv, 4096) sock.setsockopt(SOL_TCP, TCP_REPAIR, TCP_REPAIR_OFF) readData = sock.recv(4096) self.assertEqual(readData, TEST_RECEIVED) sock.close() # Test whether tcp read/write sequence number can be fetched correctly # by TCP_QUEUE_SEQ. def testGetSequenceNumber(self): for version in [4, 5, 6]: self.GetSequenceNumberTest(version) def GetSequenceNumberTest(self, version): netid = self.RandomNetid() sock = self.createConnectedSocket(version, netid) # test write queue sequence number sequence_before = self.GetWriteSequenceNumber(version, sock) expect_sequence = self.last_sent.getlayer("TCP").seq self.assertEqual(sequence_before & 0xffffffff, expect_sequence) TEST_SEND = net_test.UDP_PAYLOAD self.sendData(netid, version, sock, TEST_SEND) sequence_after = self.GetWriteSequenceNumber(version, sock) self.assertEqual(sequence_before + len(TEST_SEND), sequence_after) # test read queue sequence number sequence_before = self.GetReadSequenceNumber(version, sock) expect_sequence = self.last_received.getlayer("TCP").seq + 1 self.assertEqual(sequence_before & 0xffffffff, expect_sequence) TEST_READ = net_test.UDP_PAYLOAD self.receiveData(netid, version, TEST_READ) sequence_after = self.GetReadSequenceNumber(version, sock) self.assertEqual(sequence_before + len(TEST_READ), sequence_after) sock.close() def GetWriteSequenceNumber(self, version, sock): sock.setsockopt(SOL_TCP, TCP_REPAIR, TCP_REPAIR_ON) sock.setsockopt(SOL_TCP, TCP_REPAIR_QUEUE, TCP_SEND_QUEUE) sequence = sock.getsockopt(SOL_TCP, TCP_QUEUE_SEQ) sock.setsockopt(SOL_TCP, TCP_REPAIR_QUEUE, TCP_NO_QUEUE) sock.setsockopt(SOL_TCP, TCP_REPAIR, TCP_REPAIR_OFF) return sequence def GetReadSequenceNumber(self, version, sock): sock.setsockopt(SOL_TCP, TCP_REPAIR, TCP_REPAIR_ON) sock.setsockopt(SOL_TCP, TCP_REPAIR_QUEUE, TCP_RECV_QUEUE) sequence = sock.getsockopt(SOL_TCP, TCP_QUEUE_SEQ) sock.setsockopt(SOL_TCP, TCP_REPAIR_QUEUE, TCP_NO_QUEUE) sock.setsockopt(SOL_TCP, TCP_REPAIR, TCP_REPAIR_OFF) return sequence # Test whether tcp repair socket can be poll()'ed correctly # in mutiple threads at the same time. def testMultiThreadedPoll(self): for version in [4, 5, 6]: self.PollWhenShutdownTest(version) self.PollWhenReceiveFinTest(version) def PollRepairSocketInMultipleThreads(self, netid, version, expected): sock = self.createConnectedSocket(version, netid) sock.setsockopt(SOL_TCP, TCP_REPAIR, TCP_REPAIR_ON) multiThreads = [] for i in [0, 1]: thread = SocketExceptionThread(sock, lambda sk: self.fdSelect(sock, expected)) thread.start() self.assertTrue(thread.is_alive()) multiThreads.append(thread) return sock, multiThreads def assertThreadsStopped(self, multiThreads, msg) : for thread in multiThreads: if (thread.is_alive()): thread.join(1) if (thread.is_alive()): thread.stop() raise AssertionError(msg) def PollWhenShutdownTest(self, version): netid = self.RandomNetid() expected = select.POLLIN sock, multiThreads = self.PollRepairSocketInMultipleThreads(netid, version, expected) # Test shutdown RD. sock.shutdown(SHUT_RD) self.assertThreadsStopped(multiThreads, "poll fail during SHUT_RD") sock.close() expected = None sock, multiThreads = self.PollRepairSocketInMultipleThreads(netid, version, expected) # Test shutdown WR. sock.shutdown(SHUT_WR) self.assertThreadsStopped(multiThreads, "poll fail during SHUT_WR") sock.close() expected = select.POLLIN | select.POLLHUP sock, multiThreads = self.PollRepairSocketInMultipleThreads(netid, version, expected) # Test shutdown RDWR. sock.shutdown(SHUT_RDWR) self.assertThreadsStopped(multiThreads, "poll fail during SHUT_RDWR") sock.close() def PollWhenReceiveFinTest(self, version): netid = self.RandomNetid() expected = select.POLLIN sock, multiThreads = self.PollRepairSocketInMultipleThreads(netid, version, expected) self.receiveFin(netid, version, sock) self.assertThreadsStopped(multiThreads, "poll fail during FIN") sock.close() # Test whether socket idle can be detected by SIOCINQ and SIOCOUTQ. def testSocketIdle(self): for version in [4, 5, 6]: self.readQueueIdleTest(version) self.writeQueueIdleTest(version) def readQueueIdleTest(self, version): netid = self.RandomNetid() sock = self.createConnectedSocket(version, netid) buf = ctypes.c_int() fcntl.ioctl(sock, SIOCINQ, buf) self.assertEqual(buf.value, 0) TEST_RECV_PAYLOAD = net_test.UDP_PAYLOAD self.receiveData(netid, version, TEST_RECV_PAYLOAD) fcntl.ioctl(sock, SIOCINQ, buf) self.assertEqual(buf.value, len(TEST_RECV_PAYLOAD)) sock.close() def writeQueueIdleTest(self, version): netid = self.RandomNetid() # Setup a connected socket, write queue is empty. sock = self.createConnectedSocket(version, netid) buf = ctypes.c_int() fcntl.ioctl(sock, SIOCOUTQ, buf) self.assertEqual(buf.value, 0) # Change to repair mode with SEND_QUEUE, writing some data to the queue. sock.setsockopt(SOL_TCP, TCP_REPAIR, TCP_REPAIR_ON) TEST_SEND_PAYLOAD = net_test.UDP_PAYLOAD sock.setsockopt(SOL_TCP, TCP_REPAIR_QUEUE, TCP_SEND_QUEUE) self.sendData(netid, version, sock, TEST_SEND_PAYLOAD) fcntl.ioctl(sock, SIOCOUTQ, buf) self.assertEqual(buf.value, len(TEST_SEND_PAYLOAD)) sock.close() # Setup a connected socket again. netid = self.RandomNetid() sock = self.createConnectedSocket(version, netid) # Send out some data and don't receive ACK yet. self.sendData(netid, version, sock, TEST_SEND_PAYLOAD) fcntl.ioctl(sock, SIOCOUTQ, buf) self.assertEqual(buf.value, len(TEST_SEND_PAYLOAD)) # Receive response ACK. remoteaddr = self.GetRemoteAddress(version) myaddr = self.MyAddress(version, netid) desc_ack, ack = packets.ACK(version, remoteaddr, myaddr, self.last_sent) self.ReceivePacketOn(netid, ack) fcntl.ioctl(sock, SIOCOUTQ, buf) self.assertEqual(buf.value, 0) sock.close() def fdSelect(self, sock, expected): READ_ONLY = select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR | select.POLLNVAL p = select.poll() p.register(sock, READ_ONLY) events = p.poll(500) for fd,event in events: if fd == sock.fileno(): self.assertEqual(event, expected) else: raise AssertionError("unexpected poll fd") class SocketExceptionThread(threading.Thread): def __init__(self, sock, operation): self.exception = None super(SocketExceptionThread, self).__init__() self.daemon = True self.sock = sock self.operation = operation def stop(self): self._Thread__stop() def run(self): try: self.operation(self.sock) except (IOError, AssertionError) as e: self.exception = e if __name__ == '__main__': unittest.main()