Experimenting with MPTCP using raw sockets
Although Multipath TCP is already available on several platforms (Linux, FreeBSD, iOS11), applications like Tracebox (or Mobile Tracebox) are still a convenient choice for users eager to experiment with the new protocol without installing the full MPTCP stack. These tools (along with zmap, tcpexposure, etc) allow to forge custom packets (e.g. using raw sockets) emulating newer extensions or protocols.
For instance Tracebox can highlight middleboxes interfering with MPTCP by sending MP_CAPABLE Syn with increasing TTL and collecting ICMP Time Exceeded messages from intermediate routers. Even when they don’t respond or don’t quote the full TCP header, Syn Ack without MP_CAPABLE option received from well known MPTCP servers can still reveal interference. When path is clean from middleboxes, MP_CAPABLE Syn can also be used to assess if a server adopts MPTCP.
Unfortunately middleboxes can be very subtle: e.g. they can be completely transparent to MP_CAPABLE packets but still interfere with ADD_ADDR and DSS or they can fictitiously support all options carried by Syn.
This leads to the need of more articulated MPTCP tests: in this post we describe a test (included in Mobile Tracebox), that uses raw sockets to establish a MPTCP connection, exchange data and also associate a second subflow.
In the first part we detail for every step how options should be correctly crafted for MPTCP experiment to succeed, in the second part we explore further scenarios (e.g. options not perfectly compliant with the protocol) to see how a MPTCP stack reacts to them. This can benefit development of similar application, avoiding pitfalls when dealing at low level with MPTCP, but also can help to better understand how the protocol concretely works. The figure summarizes packets exchanged between A, our client running the test, and B, a MPTCP-enabled server (multipath-tcp.org).
MPTCP-compliant scenario
We report the output of Mobile Tracebox (only interesting header fields are included).
0: 192.168.42.7 [TCP Syn] TCP::SourcePort(24d2) TCP::Option_MPTCP(00811000000000000000)
64: 130.104.230.45 [TCP Syn Ack] TCP::Option_MPTCP (00810c4d5dfc94d0a464)
0: 192.168.42.7 [TCP Ack] TCP::SourcePort(24d2) TCP::Option_MPTCP(008110000000000000000c4d5dfc94d0a464)
64: *
0: 192.168.42.7 [TCP Ack 72 bytes] TCP::SourcePort(24d2) TCP::SeqNumber(01300001) TCP::Option_MPTCP(2004fb4e435d0000000100483aca) TCP::Payload ("GET / HTTP/1.1...")
64: 130.104.230.45 [TCP Ack] TCP::AckNumber(01300001) TCP::Option_MPTCP(3608200106a8308f000102163efffec5c815)
64: 130.104.230.45 [TCP Ack] TCP::AckNumber(01300049) TCP::Option_MPTCP(2001fb4e43a5)
0: 192.168.1.102 [TCP Syn] TCP::SourcePort (cefc) TCP::Option_MPTCP(10023a03caf210000000)
64: 130.104.230.45 [TCP Syn Ack] TCP::Option_MPTCP (100256c7a377b2e33fdaa29163c5)
All fields are in hexadecimal format: we can easily acknowledge the MPTCP option subtype from the first digit. A full trace of the packets exchanged during the probe is also reported.
18:48:32.485197 IP client1.9426 > mptcp.info.ucl.ac.be.http: Flags [S], seq 19922944, win 65535, options [mptcp capable csum {0x1000000000000000}], length 0
18:48:32.573554 IP mptcp.info.ucl.ac.be.http > client1.9426: Flags [S.], seq 3005334072, ack 19922945, win 28800, options [mss 1452,mptcp capable csum {0xc4d5dfc94d0a464}], length 0
18:48:32.573792 IP client1.9426 > mptcp.info.ucl.ac.be.http: Flags [.], ack 1, win 65535, options [mptcp capable csum {0x1000000000000000,0xc4d5dfc94d0a464}], length 0
18:48:35.577198 IP client1.9426 > mptcp.info.ucl.ac.be.http: Flags [.], seq 1:73, ack 1, win 65535, options [mptcp dss seq 4216210269 subseq 1 len 72 csum 0x3aca], length 72: HTTP: GET / HTTP/1.1
18:48:35.664046 IP mptcp.info.ucl.ac.be.http > client1.9426: Flags [.], ack 1, win 28800, options [mptcp add-addr id 8 mptcp.info.ucl.ac.be,mptcp dss ack 4216210269], length 0
18:48:35.664556 IP mptcp.info.ucl.ac.be.http > client1.9426: Flags [.], ack 73, win 28800, options [mptcp dss ack 4216210341], length 0
18:48:35.666894 IP mptcp.info.ucl.ac.be.http > client1.9426: Flags [P.], seq 1:503, ack 73, win 28800, options [mptcp dss ack 4216210341 seq 2447520560 subseq 1 len 502 csum 0xc36b], length 502: HTTP: HTTP/1.1 200 OK
18:48:38.670543 IP client2.52988 > mptcp.info.ucl.ac.be.http: Flags [S], seq 1793048487, win 65535, options [mptcp join id 2 token 0x3a03caf2 nonce 0x10000000], length 0
18:48:38.756268 IP mptcp.info.ucl.ac.be.http > client2.52988: Flags [S.], seq 1665111958, ack 1793048488, win 28800, options [mss 1452,mptcp join id 2 hmac 0x56c7a377b2e33fda nonce 0xa29163c5], length 0
The test uses two client’s addresses (192.168.42.7, client1 – 192.168.1.102, client2) for the two subflows, but it’s still possible to use the same address just with different source ports.
Everything starts with a Syn carrying MP_CAPABLE option (subtype 0x0) with flags A (Checksum required) and H (use of HMAC-SHA1 as crypto algorithm) and a 64 bits key chosen by the client (0x1000000000000000). Server replies with a MP_CAPABLE Syn Ack containing same flags and its key: client takes note of server’s key to echo it on MP_CAPABLE Ack (but also to forge the subsequent MP_JOIN).
If the clients attempts to send a MP_JOIN message at this point MPTCP stack will discard the new subflow with a Rst, since no data has been actually exchanged on the first subflow. This means we have to send a packet with a real payload and a DSS option. To avoid the server dumping our packet or simply closing the connection payload must be a real HTTP request.
GET / HTTP/1.1
Host: blog.multipath-tcp.org
Connection: keep-alive
We also have to assemble a compliant DSS Option (subtype 0x2): we set flags to 0x4 (data sequence number of 32 bits), Subflow Sequence Number to 1, Data-Level Length to the length of our TCP payload (72 bytes); Data Sequence Number is generated from the SHA-1 hash of the client’s key; finally a DSS checksum has to be calculated on payload and DSS pseudo-header. Server answers with 2 packets, the first carries an ADD_ADDR option (subtype 0x3) advertising server IPv6 address: this is a symptom that MPTCP stack has acknowledged that we are speaking MPTCP language. The second contains a DSS Option: we can see how sent data is acked on both TCP and MPTCP levels.
Note
To avoid DSS checksum calculation we can use a Data-Level Length greater than the actual TCP payload length: in this case the packet will be accepted but DSS checksum will not be evaluated waiting for the next TCP segment (packet will be acked at TCP level but not MPTCP level).
After data exchange has taken place on the first subflow we can finally use a second subflow to join MPTCP connection. We send a new Syn from different source address and port (or just different port) with a MP_JOIN option (subtype 0x2) carrying a Token obtained from the key sent by server in its MP_CAPABLE Syn Ack (the first 32 bits of the SHA-1 hash of server’s key) and a Random number; Address Id is obviously set to 2. The server answers with a MP_JOIN Syn Ack (carrying a Hash-based Message Authentication Code and a Random number), sign that our MPTCP experiment has succeeded.
Other scenarios
Another advantage of raw sockets is that we can send packets not perfectly compliant with the protocol simulating how MPTCP stack reacts to possible malfunctioning or tricky middlebox interference.
Invalid MP_CAPABLE Key
In this scenario the client echoes a wrong server’s key in MP_CAPABLE Ack: this inconsistency is ignored and communication proceeds well on both TCP and MPTCP level on the first subflow. Also MP_JOIN still succeeds as long as the token is calculated from the correct server’s key.
0: 192.168.42.7 [TCP Syn] TCP::SourcePort(fac4) TCP::Option_MPTCP(00811000000000000000)
64: 130.104.230.45 [TCP Syn Ack] TCP::Option_MPTCP (0081422b61826574250e)
0: 192.168.42.7 [TCP Ack] TCP::SourcePort(fac4) TCP::Option_MPTCP(008110000000000000002000000000000000)
64: *
0: 192.168.42.7 [TCP Ack 72 bytes] TCP::SourcePort(fac4) TCP::SeqNumber(01300001) TCP::Option_MPTCP(2004fb4e435d0000000100483aca) TCP::Payload ("GET / HTTP/1.1...")
64: 130.104.230.45 [TCP Ack] TCP::AckNumber(01300001) TCP::Option_MPTCP(3608200106a8308f000102163efffec5c815)
64: 130.104.230.45 [TCP Ack] TCP::AckNumber(01300049) TCP::Option_MPTCP(2001fb4e43a5)
0: 192.168.1.102 [TCP Syn] TCP::SourcePort (cc2e) TCP::Option_MPTCP(1002531ed1b010000000)
64: 130.104.230.45 [TCP Syn Ack] TCP::Option_MPTCP (1002ea5ea69620cd23fc2c3feb6a)
No DSS Checksum, despite requested by counterpart
In the next scenario client sends a DSS option without checksum, although server has requested DSS checksum in its MP_CAPABLE Syn Ack: server replies with a Rst terminating the subflow, but the subsequent MP_JOIN still succeeds.
0: 192.168.42.7 [TCP Syn] TCP::SourcePort(c49a) TCP::Option_MPTCP(00011000000000000000)
64: 130.104.230.45 [TCP Syn Ack] TCP::Option_MPTCP (008106ef03ac4b958a2f)
0: 192.168.42.7 [TCP Ack] TCP::SourcePort(c49a) TCP::Option_MPTCP(0001100000000000000006ef03ac4b958a2f)
64: *
0: 192.168.42.7 [TCP Ack 72 bytes] TCP::SourcePort(c49a) TCP::SeqNumber(01300001) TCP::Option_MPTCP(2004fb4e435d000000010048) TCP::Payload ("GET / HTTP/1.1...")
64: 130.104.230.45 [TCP Ack] TCP::AckNumber(01300001) TCP::Option_MPTCP(3608200106a8308f000102163efffec5c815)
64: 130.104.230.45 [TCP Rst Ack] TCP::AckNumber(01300001) TCP::Option_MPTCP(2001fb4e435d)
0: 192.168.1.102 [TCP Syn] TCP::SourcePort (6bf0) TCP::Option_MPTCP(100252ad76cd10000000)
64: 130.104.230.45 [TCP Syn Ack] TCP::Option_MPTCP (10028ce03d14fa336cdae211d46d)
Bad DSS Checksum (MP_FAIL)
In another scenario a wrong DSS Checksum is sent, in this case the server correctly acknowledges data at TCP level, but sends a MP_FAIL (subtype 0x6) option causing fall back to a single subflow. Obviously subsequent MP_JOIN Syn will be rejected.
0: 192.168.42.7 [TCP Syn] TCP::SourcePort(f1ba) TCP::Option_MPTCP(00811000000000000000)
64: 130.104.230.45 [TCP Syn Ack] TCP::Option_MPTCP (0081b144931ac84d865a)
0: 192.168.42.7 [TCP Ack] TCP::SourcePort(f1ba) TCP::Option_MPTCP(00811000000000000000b144931ac84d865a)
64: *
0: 192.168.42.7 [TCP Ack 72 bytes] TCP::SourcePort(f1ba) TCP::SeqNumber(01300001) TCP::Option_MPTCP(2004fb4e435d0000000100480100) TCP::Payload ("GET / HTTP/1.1...")
64: 130.104.230.45 [TCP Ack] TCP::AckNumber(01300001) TCP::Option_MPTCP(3608200106a8308f000102163efffec5c815)
64: 130.104.230.45 [TCP Ack] TCP::AckNumber(01300049) TCP::Option_MPTCP(60008710f99bfb4e435d) TCP::Option_MPTCP(2001fb4e435d)
0: 192.168.1.102 [TCP Syn] TCP::SourcePort (ebbb) TCP::Option_MPTCP(10023d41ba9910000000)
64: 130.104.230.45 [TCP Rst Ack] -TCP::Option_MPTCP
Fall back without MP_FAIL
Fall back can also occur when client sets DSS Data-Level Length to 0 (“infinite mapping”): in this scenario server acknowledges data at TCP and MPTCP level and doesn’t send any MP_FAIL (since this case is interpreted as a choice by the client and not an anomalous event like a wrong DSS checksum), but fall back is still evident when client attempts to associate a new sufblow and MP_JOIN is not accepted.
0: 192.168.42.7 [TCP Syn] TCP::SourcePort(de0a) TCP::Option_MPTCP(00811000000000000000)
64: 130.104.230.45 [TCP Syn Ack] TCP::Option_MPTCP (0081b6e7a1b307358b82)
0: 192.168.42.7 [TCP Ack] TCP::SourcePort(de0a) TCP::Option_MPTCP(00811000000000000000b6e7a1b307358b82)
64: *
0: 192.168.42.7 [TCP Ack 72 bytes] TCP::SourcePort(de0a) TCP::SeqNumber(01300001) TCP::Option_MPTCP(2004fb4e435d0000000100000000) TCP::Payload ("GET / HTTP/1.1...")
64: 130.104.230.45 [TCP Ack] TCP::AckNumber(01300001) TCP::Option_MPTCP(3608200106a8308f000102163efffec5c815)
64: 130.104.230.45 [TCP Ack] TCP::AckNumber(01300049) TCP::Option_MPTCP(2001fb4e43a5)
0: 192.168.1.102 [TCP Syn] TCP::SourcePort (2c6d) TCP::Option_MPTCP(10020edf2fd810000000)
64: 130.104.230.45 [TCP Rst Ack] -TCP::Option_MPTCP
Bad MP_JOIN Token
In the last scenario tested the client sends a wrong Token in MP_JOIN Syn. The server unsurprisingly replies with a Rst.
0: 192.168.42.7 [TCP Syn] TCP::SourcePort(1594) TCP::Option_MPTCP(00811000000000000000)
64: 130.104.230.45 [TCP Syn Ack] TCP::Option_MPTCP (008191d0ae47af67a0f2)
0: 192.168.42.7 [TCP Ack] TCP::SourcePort(1594) TCP::Option_MPTCP(0081100000000000000091d0ae47af67a0f2)
64: *
0: 192.168.42.7 [TCP Ack 72 bytes] TCP::SourcePort(1594) TCP::SeqNumber(01300001) TCP::Option_MPTCP(2004fb4e435d0000000100483aca) TCP::Payload ("GET / HTTP/1.1...")
64: 130.104.230.45 [TCP Ack] TCP::AckNumber(01300001) TCP::Option_MPTCP(3608200106a8308f000102163efffec5c815)
64: 130.104.230.45 [TCP Ack] TCP::AckNumber(01300049) TCP::Option_MPTCP(2001fb4e43a5)
0: 192.168.1.102 [TCP Syn] TCP::SourcePort (d26e) TCP::Option_MPTCP(10020200000010000000)
64: 130.104.230.45 [TCP Rst Ack] -TCP::Option_MPTCP
Mobile Tracebox
The MPTCP test described in the first part has been included in the new version of Mobile Tracebox Screenshots show how to select destination address and the appropriate probe. To avoid a full traceroute on every packet sent, minimum TTL can be conveniently set to 64.
Since raw sockets are needed this probe is available only on rooted Android devices.