-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
tfo: Include tfo plugin (using only dnstcp connect)
- Loading branch information
Showing
3 changed files
with
202 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters