You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
133 lines
4.5 KiB
133 lines
4.5 KiB
#!/usr/bin/python
|
|
#
|
|
# Copyright 2017 The Android Open Source Project
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import unittest
|
|
|
|
from errno import *
|
|
from socket import *
|
|
from scapy import all as scapy
|
|
|
|
import multinetwork_base
|
|
import net_test
|
|
import packets
|
|
import tcp_metrics
|
|
|
|
|
|
TCPOPT_FASTOPEN = 34
|
|
TCP_FASTOPEN_CONNECT = 30
|
|
|
|
|
|
class TcpFastOpenTest(multinetwork_base.MultiNetworkBaseTest):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super(TcpFastOpenTest, cls).setUpClass()
|
|
cls.tcp_metrics = tcp_metrics.TcpMetrics()
|
|
|
|
def TFOClientSocket(self, version, netid):
|
|
s = net_test.TCPSocket(net_test.GetAddressFamily(version))
|
|
net_test.DisableFinWait(s)
|
|
self.SelectInterface(s, netid, "mark")
|
|
s.setsockopt(IPPROTO_TCP, TCP_FASTOPEN_CONNECT, 1)
|
|
return s
|
|
|
|
def assertSocketNotConnected(self, sock):
|
|
self.assertRaisesErrno(ENOTCONN, sock.getpeername)
|
|
|
|
def assertSocketConnected(self, sock):
|
|
sock.getpeername() # No errors? Socket is alive and connected.
|
|
|
|
def clearTcpMetrics(self, version, netid):
|
|
saddr = self.MyAddress(version, netid)
|
|
daddr = self.GetRemoteAddress(version)
|
|
self.tcp_metrics.DelMetrics(saddr, daddr)
|
|
with self.assertRaisesErrno(ESRCH):
|
|
print(self.tcp_metrics.GetMetrics(saddr, daddr))
|
|
|
|
def assertNoTcpMetrics(self, version, netid):
|
|
saddr = self.MyAddress(version, netid)
|
|
daddr = self.GetRemoteAddress(version)
|
|
with self.assertRaisesErrno(ENOENT):
|
|
self.tcp_metrics.GetMetrics(saddr, daddr)
|
|
|
|
def CheckConnectOption(self, version):
|
|
ip_layer = {4: scapy.IP, 6: scapy.IPv6}[version]
|
|
netid = self.RandomNetid()
|
|
s = self.TFOClientSocket(version, netid)
|
|
|
|
self.clearTcpMetrics(version, netid)
|
|
|
|
# Connect the first time.
|
|
remoteaddr = self.GetRemoteAddress(version)
|
|
with self.assertRaisesErrno(EINPROGRESS):
|
|
s.connect((remoteaddr, 53))
|
|
self.assertSocketNotConnected(s)
|
|
|
|
# Expect a SYN handshake with an empty TFO option.
|
|
myaddr = self.MyAddress(version, netid)
|
|
port = s.getsockname()[1]
|
|
self.assertNotEqual(0, port)
|
|
desc, syn = packets.SYN(53, version, myaddr, remoteaddr, port, seq=None)
|
|
syn.getlayer("TCP").options = [(TCPOPT_FASTOPEN, "")]
|
|
msg = "Fastopen connect: expected %s" % desc
|
|
syn = self.ExpectPacketOn(netid, msg, syn)
|
|
syn = ip_layer(str(syn))
|
|
|
|
# Receive a SYN+ACK with a TFO cookie and expect the connection to proceed
|
|
# as normal.
|
|
desc, synack = packets.SYNACK(version, remoteaddr, myaddr, syn)
|
|
synack.getlayer("TCP").options = [
|
|
(TCPOPT_FASTOPEN, "helloT"), ("NOP", None), ("NOP", None)]
|
|
self.ReceivePacketOn(netid, synack)
|
|
synack = ip_layer(str(synack))
|
|
desc, ack = packets.ACK(version, myaddr, remoteaddr, synack)
|
|
msg = "First connect: got SYN+ACK, expected %s" % desc
|
|
self.ExpectPacketOn(netid, msg, ack)
|
|
self.assertSocketConnected(s)
|
|
s.close()
|
|
desc, rst = packets.RST(version, myaddr, remoteaddr, synack)
|
|
msg = "Closing client socket, expecting %s" % desc
|
|
self.ExpectPacketOn(netid, msg, rst)
|
|
|
|
# Connect to the same destination again. Expect the connect to succeed
|
|
# without sending a SYN packet.
|
|
s = self.TFOClientSocket(version, netid)
|
|
s.connect((remoteaddr, 53))
|
|
self.assertSocketNotConnected(s)
|
|
self.ExpectNoPacketsOn(netid, "Second TFO connect, expected no packets")
|
|
|
|
# Issue a write and expect a SYN with data.
|
|
port = s.getsockname()[1]
|
|
s.send(net_test.UDP_PAYLOAD)
|
|
desc, syn = packets.SYN(53, version, myaddr, remoteaddr, port, seq=None)
|
|
t = syn.getlayer(scapy.TCP)
|
|
t.options = [ (TCPOPT_FASTOPEN, "helloT"), ("NOP", None), ("NOP", None)]
|
|
t.payload = scapy.Raw(net_test.UDP_PAYLOAD)
|
|
msg = "TFO write, expected %s" % desc
|
|
self.ExpectPacketOn(netid, msg, syn)
|
|
|
|
@unittest.skipUnless(net_test.LINUX_VERSION >= (4, 9, 0), "not yet backported")
|
|
def testConnectOptionIPv4(self):
|
|
self.CheckConnectOption(4)
|
|
|
|
@unittest.skipUnless(net_test.LINUX_VERSION >= (4, 9, 0), "not yet backported")
|
|
def testConnectOptionIPv6(self):
|
|
self.CheckConnectOption(6)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|