Skip to content

Commit

Permalink
Expanded path tracing during middlebox detection
Browse files Browse the repository at this point in the history
  • Loading branch information
TheJokr committed May 7, 2020
1 parent f181662 commit 0f4f8f3
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 35 deletions.
49 changes: 33 additions & 16 deletions tcpreq/tests/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from abc import abstractmethod
from typing import Generic, ClassVar, Awaitable, List, Tuple, Deque, Union, Optional
from typing import Generic, ClassVar, Awaitable, Iterator, List, Tuple, Deque, Union, Optional
import time
import math
import operator
Expand Down Expand Up @@ -127,30 +127,47 @@ async def run(self) -> TestResult:

def _detect_mboxes(self, info: str, check_data: bytes = None, *, win: bool = True,
ack: bool = True, up: bool = True, opts: bool = True) -> Optional[TestResult]:
"""Check for middlebox interference using (overwritten) _quote_modified predicate."""
"""Check for middlebox interference using (overwritten) _quote_diff predicate."""
info = f" ({info})"

result = None
res_stat = 0
mbox_hop = self._HOP_LIMIT + 1

for icmp in self.quote_queue:
mbox_hop = decode_ttl(icmp.quote, icmp.hops, self._HOP_LIMIT,
win=win, ack=ack, up=up, opts=opts)
self._path.append((mbox_hop, icmp.icmp_src.compressed))
hop = icmp.hop = decode_ttl(icmp.quote, icmp.hop, self._HOP_LIMIT,
win=win, ack=ack, up=up, opts=opts)
hop_unk = hop == 0

# Diff check is only necessary if it could improve the result
# I.e., if we don't have any result yet (result is None),
# or if hop is closer than the mbox the current result is based on
if (hop_unk and result is not None) or hop >= mbox_hop:
continue

if not self._quote_modified(icmp, data=check_data) or (mbox_hop == 0 and res_stat >= 1):
diff = self._quote_diff(icmp, data=check_data)
if diff is None: # quote matches sent segment
continue

reason = "Middlebox interference detected"
reason += " at or before hop {0}" if mbox_hop > 0 else " at unknown hop"
reason += " ({1})"
result = TestResult(self, TEST_UNK, 1, reason.format(mbox_hop, info))
reason += " at unknown hop" if hop_unk else f" at or before hop {hop}"
reason += info
result = TestResult(self, TEST_UNK, 1, reason, custom={"diff": diff})

if mbox_hop > 0:
break
else:
res_stat = 1
if not hop_unk:
mbox_hop = hop

path_gen: Iterator[Tuple[int, str]] = ((icmp.hop, icmp.icmp_src.compressed)
for icmp in self.quote_queue)
if mbox_hop <= self._HOP_LIMIT:
path_gen = filter(lambda x: 1 <= x[0] <= mbox_hop, path_gen)

# Use extend instead of new list here so result is updated as well
self._path.extend(path_gen)
self._path.sort(key=operator.itemgetter(0))

return result

def _quote_modified(self, icmp: ICMPQuote[IPAddressType], *, data: bytes = None) -> bool:
def _quote_diff(self, icmp: ICMPQuote[IPAddressType], *, data: bytes = None) \
-> Optional[Tuple[str, str]]:
"""Determine whether the quoted segment has been modified along the path."""
return False
return None # not modified
48 changes: 29 additions & 19 deletions tcpreq/tests/checksum.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Awaitable, List, Tuple, Optional
from typing import Awaitable, Iterator, List, Tuple, Optional
import sys
import time
import operator
Expand Down Expand Up @@ -91,37 +91,47 @@ async def run(self) -> TestResult:
del res

# Check path for middlebox interference (e.g., checksum corrected)
# Custom variant of BaseTest._detect_mboxes to distinguish between
# corrected and modified checksums
await asyncio.sleep(10, loop=self._loop)
res_stat = 0
mbox_hop = self._HOP_LIMIT + 1

for icmp in self.quote_queue:
mbox_hop = decode_ttl(icmp.quote, icmp.hops, self._HOP_LIMIT)
self._path.append((mbox_hop, icmp.icmp_src.compressed))
hop = icmp.hop = decode_ttl(icmp.quote, icmp.hop, self._HOP_LIMIT)
hop_unk = hop == 0

if (hop_unk and res_stat >= 2) or (hop >= mbox_hop and res_stat >= 4):
continue

# None if not corrected, false if not enough data to verify, true if corrected
verified = self._checksum_corrected(icmp, checksum=cs)
if verified is None:
continue
if mbox_hop > 0:
if not verified and res_stat >= 3:
elif not verified:
if (hop_unk and res_stat >= 1) or (hop >= mbox_hop and res_stat >= 3):
continue
elif res_stat >= 1 + verified:
continue

reason = "Middlebox interference detected"
reason += " at or before hop {0}" if mbox_hop > 0 else " at unknown hop"
reason += " at unknown hop" if hop_unk else f" at or before hop {hop}"
reason += " (checksum corrected)" if verified else " (checksum modified)"
result = TestResult(self, TEST_UNK, 1, reason.format(mbox_hop))
result = TestResult(self, TEST_UNK, 1, reason,
custom={"diff": (cs.hex(), icmp.quote[16:18].hex())})

if mbox_hop > 0:
if verified:
break
else:
res_stat = 3
else:
res_stat = 1 + verified
del res_stat
# 1 for new result, 1 for verified, 2 for not hop_unk
res_stat = 1 + (not hop_unk) << 1 + verified
if res_stat >= 3:
mbox_hop = hop

path_gen: Iterator[Tuple[int, str]] = ((icmp.hop, icmp.icmp_src.compressed)
for icmp in self.quote_queue)
if mbox_hop <= self._HOP_LIMIT:
path_gen = filter(lambda x: 1 <= x[0] <= mbox_hop, path_gen)

self._path.extend(path_gen)
self._path.sort(key=operator.itemgetter(0))
del res_stat, mbox_hop, path_gen

if result is not None:
return result

Expand Down Expand Up @@ -182,8 +192,8 @@ async def run(self) -> TestResult:
await self.send(ack_res.make_reset(self.src, self.dst))
return result

def _checksum_corrected(self, icmp: ICMPQuote[IPAddressType],
*, checksum: bytes) -> Optional[bool]:
def _checksum_corrected(self, icmp: ICMPQuote[IPAddressType], *, checksum: bytes) \
-> Optional[bool]:
qlen = len(icmp.quote)
if qlen < 18:
# Checksum not included in quote
Expand Down

0 comments on commit 0f4f8f3

Please sign in to comment.