• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
17import unittest
18
19from errno import *
20from socket import *
21from scapy import all as scapy
22
23import multinetwork_base
24import net_test
25import packets
26import tcp_metrics
27
28
29TCPOPT_FASTOPEN = 34
30TCP_FASTOPEN_CONNECT = 30
31
32
33class 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
131if __name__ == "__main__":
132  unittest.main()
133