Home | History | Annotate | Download | only in test
      1 #!/usr/bin/python
      2 #
      3 # Copyright 2017 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 # http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 
     17 import unittest
     18 
     19 from errno import *
     20 from socket import *
     21 from scapy import all as scapy
     22 
     23 import multinetwork_base
     24 import net_test
     25 import packets
     26 import tcp_metrics
     27 
     28 
     29 TCPOPT_FASTOPEN = 34
     30 TCP_FASTOPEN_CONNECT = 30
     31 
     32 
     33 class TcpFastOpenTest(multinetwork_base.MultiNetworkBaseTest):
     34 
     35   @classmethod
     36   def setUpClass(cls):
     37     super(TcpFastOpenTest, cls).setUpClass()
     38     cls.tcp_metrics = tcp_metrics.TcpMetrics()
     39 
     40   def TFOClientSocket(self, version, netid):
     41     s = net_test.TCPSocket(net_test.GetAddressFamily(version))
     42     net_test.DisableFinWait(s)
     43     self.SelectInterface(s, netid, "mark")
     44     s.setsockopt(IPPROTO_TCP, TCP_FASTOPEN_CONNECT, 1)
     45     return s
     46 
     47   def assertSocketNotConnected(self, sock):
     48     self.assertRaisesErrno(ENOTCONN, sock.getpeername)
     49 
     50   def assertSocketConnected(self, sock):
     51     sock.getpeername()  # No errors? Socket is alive and connected.
     52 
     53   def clearTcpMetrics(self, version, netid):
     54     saddr = self.MyAddress(version, netid)
     55     daddr = self.GetRemoteAddress(version)
     56     self.tcp_metrics.DelMetrics(saddr, daddr)
     57     with self.assertRaisesErrno(ESRCH):
     58       print self.tcp_metrics.GetMetrics(saddr, daddr)
     59 
     60   def assertNoTcpMetrics(self, version, netid):
     61     saddr = self.MyAddress(version, netid)
     62     daddr = self.GetRemoteAddress(version)
     63     with self.assertRaisesErrno(ENOENT):
     64       self.tcp_metrics.GetMetrics(saddr, daddr)
     65 
     66   def CheckConnectOption(self, version):
     67     ip_layer = {4: scapy.IP, 6: scapy.IPv6}[version]
     68     netid = self.RandomNetid()
     69     s = self.TFOClientSocket(version, netid)
     70 
     71     self.clearTcpMetrics(version, netid)
     72 
     73     # Connect the first time.
     74     remoteaddr = self.GetRemoteAddress(version)
     75     with self.assertRaisesErrno(EINPROGRESS):
     76       s.connect((remoteaddr, 53))
     77     self.assertSocketNotConnected(s)
     78 
     79     # Expect a SYN handshake with an empty TFO option.
     80     myaddr = self.MyAddress(version, netid)
     81     port = s.getsockname()[1]
     82     self.assertNotEqual(0, port)
     83     desc, syn = packets.SYN(53, version, myaddr, remoteaddr, port, seq=None)
     84     syn.getlayer("TCP").options = [(TCPOPT_FASTOPEN, "")]
     85     msg = "Fastopen connect: expected %s" % desc
     86     syn = self.ExpectPacketOn(netid, msg, syn)
     87     syn = ip_layer(str(syn))
     88 
     89     # Receive a SYN+ACK with a TFO cookie and expect the connection to proceed
     90     # as normal.
     91     desc, synack = packets.SYNACK(version, remoteaddr, myaddr, syn)
     92     synack.getlayer("TCP").options = [
     93         (TCPOPT_FASTOPEN, "helloT"), ("NOP", None), ("NOP", None)]
     94     self.ReceivePacketOn(netid, synack)
     95     synack = ip_layer(str(synack))
     96     desc, ack = packets.ACK(version, myaddr, remoteaddr, synack)
     97     msg = "First connect: got SYN+ACK, expected %s" % desc
     98     self.ExpectPacketOn(netid, msg, ack)
     99     self.assertSocketConnected(s)
    100     s.close()
    101     desc, rst = packets.RST(version, myaddr, remoteaddr, synack)
    102     msg = "Closing client socket, expecting %s" % desc
    103     self.ExpectPacketOn(netid, msg, rst)
    104 
    105     # Connect to the same destination again. Expect the connect to succeed
    106     # without sending a SYN packet.
    107     s = self.TFOClientSocket(version, netid)
    108     s.connect((remoteaddr, 53))
    109     self.assertSocketNotConnected(s)
    110     self.ExpectNoPacketsOn(netid, "Second TFO connect, expected no packets")
    111 
    112     # Issue a write and expect a SYN with data.
    113     port = s.getsockname()[1]
    114     s.send(net_test.UDP_PAYLOAD)
    115     desc, syn = packets.SYN(53, version, myaddr, remoteaddr, port, seq=None)
    116     t = syn.getlayer(scapy.TCP)
    117     t.options = [ (TCPOPT_FASTOPEN, "helloT"), ("NOP", None), ("NOP", None)]
    118     t.payload = scapy.Raw(net_test.UDP_PAYLOAD)
    119     msg = "TFO write, expected %s" % desc
    120     self.ExpectPacketOn(netid, msg, syn)
    121 
    122   @unittest.skipUnless(net_test.LINUX_VERSION >= (4, 9, 0), "not yet backported")
    123   def testConnectOptionIPv4(self):
    124     self.CheckConnectOption(4)
    125 
    126   @unittest.skipUnless(net_test.LINUX_VERSION >= (4, 9, 0), "not yet backported")
    127   def testConnectOptionIPv6(self):
    128     self.CheckConnectOption(6)
    129 
    130 
    131 if __name__ == "__main__":
    132   unittest.main()
    133