Syllabus Lesson 100 of 156 · Packets, DNS and IP addresses
Packets, DNS and IP addresses

IP-based tracking

Why it matters Blocking cookies does not stop a site from seeing your IP address, and some trackers use "IP address plus browser" as a stand-in ID. In this lab you measure how well that works over a simulated week, and you will see it is both weaker and more dangerous than it sounds.

An IP address is a bit like a street address for a block of flats. It gets packets to the building, but several people may live there, and people move.

Why IP addresses are shared

There are not enough old-style (IPv4) addresses for every device, so networks share them.

NAT: Network Address Translation: a home router or office gateway lets many devices share one public IP address. Every phone and laptop at home looks like the same address to websites.

Carrier-grade NAT: the same trick done by a mobile network on a large scale: thousands of phones share a small pool of addresses, and your phone may get a different one each time it connects.

User agent: a short text your browser sends with every request, naming the browser and system, such as Brook/31.0 (Laptop; OS-A). Many people send exactly the same one.

So a tracker that combines the two into one weak ID, like 203.0.113.7 | Brook/31.0 (Laptop; OS-A), can go wrong in two ways:

  • Splitting: one person appears under many IDs, because their phone moves between home Wi-Fi, the office and the mobile network, or their home address changes.
  • Merging: different people share one ID, because they share an office or a household and use the same popular browser.

Merging is the dangerous one for privacy: a tracker may wrongly pin your housemate's browsing on you, or reveal that someone in your home looked at something sensitive.

The practice data

watched.leg5a_network.simulate_week(seed) gives a week of visits to one news site. Each visit is a dict:

{'day': 0, 'hour': 10, 'person': 'ana', 'device': 'laptop',
 'network': 'office', 'ip': '192.0.2.31',
 'user_agent': 'Brook/31.0 (Laptop; OS-A)'}

The site itself only sees ip and user_agent. The person field is the truth that the simulator knows and a real site never does; you use it only to score the weak ID. Days run from 0 (Monday) to 6 (Sunday).

To count different things, a Python set is handy: it keeps each value once.

seen = set()
for v in visits:
    seen.add(v["ip"])
print(len(seen))   # how many different IPs

To find the most common item, count with a dict and take the biggest count with max(counts.values()).

In other languages

Building a weak ID and counting distinct values looks much the same everywhere.

// JavaScript
const weakId = v => `${v.ip} | ${v.user_agent}`;
const ids = new Set(week.filter(v => v.person === "ana").map(weakId));
console.log(ids.size);
// Go
ids := map[string]bool{}
for _, v := range week {
    if v.Person == "ana" {
        ids[v.IP+" | "+v.UserAgent] = true
    }
}
fmt.Println(len(ids))
// PHP
$ids = [];
foreach ($week as $v) {
    if ($v['person'] === 'ana') { $ids[$v['ip'] . ' | ' . $v['user_agent']] = true; }
}
echo count($ids);

The defence

For you: a VPN or relay replaces your IP with a shared one, and keeping your browser up to date keeps your user agent common. For the sites you build: do not treat an IP address as an identity. Shorten or drop IP addresses in logs as soon as you can (for example keep only the first three parts of an IPv4 address), because a raw IP plus a time can point to one household.

Recap

  • An IP address belongs to a connection, often shared by a household, an office or thousands of phones.
  • IP plus user agent splits one person into many IDs and merges different people into one.
  • Merging can wrongly expose someone else's browsing, which is why IP-based IDs are risky as well as weak.
  • In your own logs, shorten or drop IP addresses early.
Your turn

WEEK is one simulated week of visits. Write four functions:

  1. weak_id(visit): return one text combining visit["ip"] and visit["user_agent"], such as ip + " | " + user_agent. Two visits must get the same ID exactly when both parts match.
  2. ids_for_person(week, person): return how many different weak IDs that person's visits carried (0 if none).
  3. people_behind_id(week): return a dict mapping each weak ID to how many different people made visits under it.
  4. stability(week, person): return the share (0.0 to 1.0) of that person's visits made under their single most common weak ID, or 0.0 if they have no visits.
The printing at the bottom is written for you. Tests use other simulated weeks, so the answer cannot be hard-coded.

Spotted a problem in this lesson? Report it

Code · runs in your browser
Output