• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3
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 os
26import packets
27import tcp_metrics
28
29
30TCPOPT_FASTOPEN = 34
31TCP_FASTOPEN_CONNECT = 30
32BH_TIMEOUT_SYSCTL = "/proc/sys/net/ipv4/tcp_fastopen_blackhole_timeout_sec"
33
34
35class TcpFastOpenTest(multinetwork_base.MultiNetworkBaseTest):
36
37  @classmethod
38  def setUpClass(cls):
39    super(TcpFastOpenTest, cls).setUpClass()
40    cls.tcp_metrics = tcp_metrics.TcpMetrics()
41
42  def TFOClientSocket(self, version, netid):
43    s = net_test.TCPSocket(net_test.GetAddressFamily(version))
44    net_test.DisableFinWait(s)
45    self.SelectInterface(s, netid, "mark")
46    s.setsockopt(IPPROTO_TCP, TCP_FASTOPEN_CONNECT, 1)
47    return s
48
49  def assertSocketNotConnected(self, sock):
50    self.assertRaisesErrno(ENOTCONN, sock.getpeername)
51
52  def assertSocketConnected(self, sock):
53    sock.getpeername()  # No errors? Socket is alive and connected.
54
55  def clearTcpMetrics(self, version, netid):
56    saddr = self.MyAddress(version, netid)
57    daddr = self.GetRemoteAddress(version)
58    self.tcp_metrics.DelMetrics(saddr, daddr)
59    with self.assertRaisesErrno(ESRCH):
60      print(self.tcp_metrics.GetMetrics(saddr, daddr))
61
62  def assertNoTcpMetrics(self, version, netid):
63    saddr = self.MyAddress(version, netid)
64    daddr = self.GetRemoteAddress(version)
65    with self.assertRaisesErrno(ENOENT):
66      self.tcp_metrics.GetMetrics(saddr, daddr)
67
68  def clearBlackhole(self):
69    # Prior to 4.15 this sysctl is not namespace aware.
70    if net_test.LINUX_VERSION < (4, 15, 0) and not os.path.exists(BH_TIMEOUT_SYSCTL):
71      return
72    timeout = self.GetSysctl(BH_TIMEOUT_SYSCTL)
73
74    # Write to timeout to clear any pre-existing blackhole condition
75    self.SetSysctl(BH_TIMEOUT_SYSCTL, timeout)
76
77  def CheckConnectOption(self, version):
78    ip_layer = {4: scapy.IP, 6: scapy.IPv6}[version]
79    netid = self.RandomNetid()
80    s = self.TFOClientSocket(version, netid)
81
82    self.clearTcpMetrics(version, netid)
83    self.clearBlackhole()
84
85    # Connect the first time.
86    remoteaddr = self.GetRemoteAddress(version)
87    with self.assertRaisesErrno(EINPROGRESS):
88      s.connect((remoteaddr, 53))
89    self.assertSocketNotConnected(s)
90
91    # Expect a SYN handshake with an empty TFO option.
92    myaddr = self.MyAddress(version, netid)
93    port = s.getsockname()[1]
94    self.assertNotEqual(0, port)
95    desc, syn = packets.SYN(53, version, myaddr, remoteaddr, port, seq=None)
96    syn.getlayer("TCP").options = [(TCPOPT_FASTOPEN, "")]
97    msg = "Fastopen connect: expected %s" % desc
98    syn = self.ExpectPacketOn(netid, msg, syn)
99    syn = ip_layer(bytes(syn))
100
101    # Receive a SYN+ACK with a TFO cookie and expect the connection to proceed
102    # as normal.
103    desc, synack = packets.SYNACK(version, remoteaddr, myaddr, syn)
104    synack.getlayer("TCP").options = [
105        (TCPOPT_FASTOPEN, "helloT"), ("NOP", None), ("NOP", None)]
106    self.ReceivePacketOn(netid, synack)
107    synack = ip_layer(bytes(synack))
108    desc, ack = packets.ACK(version, myaddr, remoteaddr, synack)
109    msg = "First connect: got SYN+ACK, expected %s" % desc
110    self.ExpectPacketOn(netid, msg, ack)
111    self.assertSocketConnected(s)
112    s.close()
113    desc, rst = packets.RST(version, myaddr, remoteaddr, synack)
114    msg = "Closing client socket, expecting %s" % desc
115    self.ExpectPacketOn(netid, msg, rst)
116
117    # Connect to the same destination again. Expect the connect to succeed
118    # without sending a SYN packet.
119    s = self.TFOClientSocket(version, netid)
120    s.connect((remoteaddr, 53))
121    self.assertSocketNotConnected(s)
122    self.ExpectNoPacketsOn(netid, "Second TFO connect, expected no packets")
123
124    # Issue a write and expect a SYN with data.
125    port = s.getsockname()[1]
126    s.send(net_test.UDP_PAYLOAD)
127    desc, syn = packets.SYN(53, version, myaddr, remoteaddr, port, seq=None)
128    t = syn.getlayer(scapy.TCP)
129    t.options = [ (TCPOPT_FASTOPEN, "helloT"), ("NOP", None), ("NOP", None)]
130    t.payload = scapy.Raw(net_test.UDP_PAYLOAD)
131    msg = "TFO write, expected %s" % desc
132    self.ExpectPacketOn(netid, msg, syn)
133
134  def testConnectOptionIPv4(self):
135    self.CheckConnectOption(4)
136
137  def testConnectOptionIPv6(self):
138    self.CheckConnectOption(6)
139
140
141if __name__ == "__main__":
142  unittest.main()
143