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