Skip to content

Commit

Permalink
tfo: Include tfo plugin (using only dnstcp connect)
Browse files Browse the repository at this point in the history
  • Loading branch information
irl committed Jan 21, 2018
1 parent 6315b47 commit 7fe668a
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 1 deletion.
98 changes: 98 additions & 0 deletions doc/plugins/tfo.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
TFO Plugin
==========

TCP Fast Open (TFO) is an extension to speed up the opening of successive
Transmission Control Protocol (TCP) connections between two endpoints. It works
by using a TFO cookie (a TCP option), which is a cryptographic cookie stored on
the client and set upon the initial connection with the server. [RFC7413]_

When the client later reconnects, it sends the initial SYN packet along with
the TFO cookie data to authenticate itself. If successful, the server may start
sending data to the client even before the reception of the final ACK packet of
the three-way handshake, skipping that way a round-trip delay and lowering the
latency in the start of data transmission.

The TFO plugin for PATHspider aims to detect connectivity breakage due to the
the use of TCP Fast Open, implementation of TCP Fast Open, and TFO
implementation anomalies.

Usage Example
-------------

.. note:: The path given to the example list of web servers is taken from a
Debian GNU/Linux installation and may differ on your computer. These
are the same examples that can be found in the `examples/` directory
of the source distribution.

To use the TFO plugin, specify ``tfo`` as the plugin to use on the command-line:

.. code-block:: shell
pspdr measure -i eth0 tfo </usr/share/doc/pathspider/examples/webtest.ndjson >results.ndjson
This will run three DNS queries over TCP for each job input, one
without using TCP Fast Open and two using it, with the first connection using
it used to prime the system to make a 0-RTT connection on the second.

Supported Connection Modes
--------------------------

This plugin supports the following connection modes:

* dnstcp - Performs a DNS query using TCP

To use an alternative connection mode, add the ``--connect`` argument to the
invocation of PATHspider:

.. code-block:: shell
pspdr measure -i eth0 tfo --connect tcp </usr/share/doc/pathspider/examples/webtest.ndjson >results.ndjson
Output Conditions
-----------------

The following conditions are generated for the TFO plugin:

tfo.connectivity.Y
~~~~~~~~~~~~~~~~~~

For each connection that was observed by PATHspider, a connectivity condition
will be generated to indicate whether or not connectivity was successful using
TFO validated against a connection not using TFO.

Y may have the following values:

* works - Both connections succeeded
* broken - Baseline connection succeeded where experimental connection failed
* offline - Both connections failed
* transient - Baseline connection failed where experimental connection
succeeded (this can be used to give an indication of transient failure rates
included in the "broken" set)

tfo.cookie.X
~~~~~~~~~~~~

For each connection that was observed to have a response by PATHspider, a
condition is generated to show whether the TFO cookie was received on response packets.

X can have two values, "received" or "not_received", idicating whther the client
received a TFO cookie after sending the Fast Open Cookie Request option.


tfo.syndata.X
~~~~~~~~~~~~~

For each connection that was observed to have a response by PATHspider, a
condition is generated to show whether the TFO cookie was acknowledged when
resending to the tested host.

* acked - The server sends a SYN-ACK acknowledging the TFO cookie
* not_acked - The server sends a SYN-ACK not acknowledging the TFO cookie
* failed - The server does not send a SYN-ACK

Notes
-----

* TCP Fast Open is set using a socket option options. Through passive
observation it should be possible to verify that TCP Fast Open is indeed
being used.
103 changes: 103 additions & 0 deletions pathspider/plugins/tfo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import socket
import struct

from dnslib import DNSQuestion
from dnslib import QTYPE

import pathspider.base
from pathspider.base import PluggableSpider
from pathspider.base import CONN_OK
from pathspider.base import CONN_FAILED
from pathspider.base import CONN_TIMEOUT
from pathspider.desync import DesynchronizedSpider
from pathspider.helpers.dns import connect_dns_tcp
from pathspider.helpers.dns import PSDNSRecord
from pathspider.helpers.http import connect_http
from pathspider.helpers.http import connect_https
from pathspider.chains.basic import BasicChain
from pathspider.chains.tcp import TCPChain
from pathspider.chains.tfo import TFOChain

CURLOPT_TCP_FASTOPEN = 244


class TFO(DesynchronizedSpider, PluggableSpider):

name = "tfo"
description = "TCP Fast Open"
version = pathspider.base.__version__
chains = [BasicChain, TCPChain, TFOChain]
# TODO: Once cURL supports retrieving the source port for TCP fast open,
# http and https connections would be supported.
# https://github.com/curl/curl/issues/1332
connect_supported = ["dnstcp"]

def conn_no_tfo(self, job, config): # pylint: disable=unused-argument
if self.args.connect == "http":
return connect_http(self.source, job, self.args.timeout)
elif self.args.connect == "dnstcp":
return connect_dns_tcp(self.source, job, self.args.timeout)
else:
raise RuntimeError("Unknown connection mode specified")

def conn_tfo(self, job, config): # pylint: disable=unused-argument
if self.args.connect == "http":
curlopts = {CURLOPT_TCP_FASTOPEN: 1}
return connect_http(self.source, job, self.args.timeout, curlopts)
elif self.args.connect == "https":
curlopts = {CURLOPT_TCP_FASTOPEN: 1}
return connect_https(self.source, job, self.args.timeout, curlopts)
elif self.args.connect == "dnstcp":
try:
q = PSDNSRecord(q=DNSQuestion(job['domain'], QTYPE.A))
data = q.pack()
data = struct.pack("!H", len(data)) + data
if ':' in job['dip']:
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
sock.bind((self.source[1], 0))
else:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((self.source[0], 0))
# TODO: In non-blocking mode, this will always raise an EINPROGRESS
# Should perform a blocking select afterwards, if it doesn't become available for
# read then should fail it
#sock.settimeout(self.args.timeout)
sock.sendto(data, socket.MSG_FASTOPEN, (job['dip'], job['dp'])) # pylint: disable=no-member
sp = sock.getsockname()[1]
sock.close()
return {'sp': sp, 'spdr_state': CONN_OK}
except TimeoutError:
return {
'sp': sock.getsockname()[1],
'spdr_state': CONN_TIMEOUT
}
except TypeError: # Caused by not having a v4/v6 address when trying to bind
return {'sp': 0, 'spdr_state': CONN_FAILED}
except OSError:
return {'sp': 0, 'spdr_state': CONN_FAILED}
else:
raise RuntimeError("Unknown connection mode specified")

connections = [conn_no_tfo, conn_tfo, conn_tfo]

def combine_flows(self, flows):
conditions = []

conditions.append(self.combine_connectivity(
flows[0]['spdr_state'] == CONN_OK,
flows[2]['spdr_state'] == CONN_OK))
if flows[2]['spdr_state'] == CONN_OK and flows[2]['observed']:
if flows[2]['tfo_synclen']:
conditions.append('tfo.cookie.received')
if flows[2]['tfo_ack'] - flows[2]['tfo_seq'] == flows[2][
'tfo_dlen'] + 1:
conditions.append('tfo.syndata.acked')
elif (flows[2]['tfo_ack'] - flows[2]['tfo_seq'] == 1
) and flows[2]['tfo_dlen'] > 0:
conditions.append('tfo.syndata.not_acked')
elif flows[2]['tfo_ack'] == 0:
conditions.append('tfo.syndata.failed')
else:
conditions.append('tfo.cookie.not_received')

return conditions
2 changes: 1 addition & 1 deletion pathspider/tests/test_plugin_load.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def test_plugin_load():
except ImportError:
raise nose.SkipTest

expected_names = set(['ECN', 'DSCP', 'UDPZero', 'DNSResolv', 'H2', 'EvilBit', 'MSS'])
expected_names = set(['ECN', 'DSCP', 'UDPZero', 'DNSResolv', 'H2', 'EvilBit', 'MSS', 'TFO'])
names = set()

for plugin in pathspider.cmd.measure.plugins:
Expand Down

0 comments on commit 7fe668a

Please sign in to comment.