xref: /aosp_15_r20/kernel/tests/net/test/tcp_fastopen_test.py (revision 2f2c4c7ab4226c71756b9c31670392fdd6887c4f)
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    timeout = self.GetSysctl(BH_TIMEOUT_SYSCTL)
70
71    # Write to timeout to clear any pre-existing blackhole condition
72    self.SetSysctl(BH_TIMEOUT_SYSCTL, timeout)
73
74  def CheckConnectOption(self, version):
75    ip_layer = {4: scapy.IP, 6: scapy.IPv6}[version]
76    netid = self.RandomNetid()
77    s = self.TFOClientSocket(version, netid)
78
79    self.clearTcpMetrics(version, netid)
80    self.clearBlackhole()
81
82    # Connect the first time.
83    remoteaddr = self.GetRemoteAddress(version)
84    with self.assertRaisesErrno(EINPROGRESS):
85      s.connect((remoteaddr, 53))
86    self.assertSocketNotConnected(s)
87
88    # Expect a SYN handshake with an empty TFO option.
89    myaddr = self.MyAddress(version, netid)
90    port = s.getsockname()[1]
91    self.assertNotEqual(0, port)
92    desc, syn = packets.SYN(53, version, myaddr, remoteaddr, port, seq=None)
93    syn.getlayer("TCP").options = [(TCPOPT_FASTOPEN, "")]
94    msg = "Fastopen connect: expected %s" % desc
95    syn = self.ExpectPacketOn(netid, msg, syn)
96    syn = ip_layer(bytes(syn))
97
98    # Receive a SYN+ACK with a TFO cookie and expect the connection to proceed
99    # as normal.
100    desc, synack = packets.SYNACK(version, remoteaddr, myaddr, syn)
101    synack.getlayer("TCP").options = [
102        (TCPOPT_FASTOPEN, "helloT"), ("NOP", None), ("NOP", None)]
103    self.ReceivePacketOn(netid, synack)
104    synack = ip_layer(bytes(synack))
105    desc, ack = packets.ACK(version, myaddr, remoteaddr, synack)
106    msg = "First connect: got SYN+ACK, expected %s" % desc
107    self.ExpectPacketOn(netid, msg, ack)
108    self.assertSocketConnected(s)
109    s.close()
110    desc, rst = packets.RST(version, myaddr, remoteaddr, synack)
111    msg = "Closing client socket, expecting %s" % desc
112    self.ExpectPacketOn(netid, msg, rst)
113
114    # Connect to the same destination again. Expect the connect to succeed
115    # without sending a SYN packet.
116    s = self.TFOClientSocket(version, netid)
117    s.connect((remoteaddr, 53))
118    self.assertSocketNotConnected(s)
119    self.ExpectNoPacketsOn(netid, "Second TFO connect, expected no packets")
120
121    # Issue a write and expect a SYN with data.
122    port = s.getsockname()[1]
123    s.send(net_test.UDP_PAYLOAD)
124    desc, syn = packets.SYN(53, version, myaddr, remoteaddr, port, seq=None)
125    t = syn.getlayer(scapy.TCP)
126    t.options = [ (TCPOPT_FASTOPEN, "helloT"), ("NOP", None), ("NOP", None)]
127    t.payload = scapy.Raw(net_test.UDP_PAYLOAD)
128    msg = "TFO write, expected %s" % desc
129    self.ExpectPacketOn(netid, msg, syn)
130    s.close()
131
132  def testConnectOptionIPv4(self):
133    self.CheckConnectOption(4)
134
135  def testConnectOptionIPv6(self):
136    self.CheckConnectOption(6)
137
138
139if __name__ == "__main__":
140  unittest.main()
141