---
title: 'Virtual Private Networks'
source: 'https://academia.sh/en/courses/network-security/virtual-private-networks'
course: 'Network Security'
language: en
updated: '2026-09-26T03:02:54+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - "Counts the directly and transitively reached items by the tunnel's landing zone, separately across three placements"
    - 'Compares the concentrator load of full tunnel versus split tunnel and the connections each leaves outside inspection'
    - 'Measures the scope of verification: how many hours and how many connections one proof covers'
---

# Virtual Private Networks

The access scope a tunnel opens is run across three placements: how many items the remote endpoint reaches directly when it lands in the internal zone, how many items fall out of scope when it lands in a separate zone and in a zone per role; how many connections a split tunnel leaves outside inspection, and how many connections a single verification is enough to count as proven subject identity.

Every defense counted so far either split traffic into zones, routed it through a single point, logged it,
or dropped it. Their shared assumption was that the connecting party stood outside the network. A tunnel
removes that assumption: once a remote endpoint is verified, it takes an address from the internal network
and becomes a node in the zone graph. From that moment on, distance is not a security property.

Three things are measured. Which zone the tunnel lands in when it opens and how many items are reachable
from there; how many connections a split tunnel leaves outside the tunnel; and how many connections a
single verification is enough to count as proven subject identity. Tunneling and private connectivity in
cloud networks were covered in another course; the axis here is not the connection's establishment but its
scope after establishment.

- **PD29.** The three remote endpoint classes, item counts, connection and session counts belong to a
  fictional zonal measurement network. No real tunnel is established; what is measured is the model built
  with `node`.
- **PD30.** Encryption, key exchange, and the tunnel protocol are not modeled. It is assumed that the
  tunnel is established and that traffic inside it cannot be read from outside; what that assurance rests
  on is outside this course.
- **PD31.** Restrictions inside a zone are disregarded: an endpoint that lands in a zone reaches all of
  that zone's items. Direct access is counted as the landing zone plus the targets of the rules that leave
  it.
- **PD32.** Session length is taken as eight hours, and every session is assumed to run its full length.
  Re-verification, session renewal, and idle timeout are not modeled.
- **PD33.** The split between corp-bound and external-bound connections is marked by hand; every run
  produces the same numbers.

## The Zone the Tunnel Lands In

A single configuration item of the remote-access tunnel decides the whole scope: which zone the remote
endpoint takes its address from. The easiest path is to land it in the zone where employees already sit,
because that zone's rules are already written and no new line is needed. The model compares three
placements: landing in the internal zone, landing in a single remote-access zone, and landing each endpoint
class in its own zone.

```js
// tunnel.mjs — the zone the tunnel lands in, its access scope, and connection distribution; a model, not a real tunnel
export const ITEM = { dmz: 4, branch: 120, field: 60, app: 12, data: 6, management: 5, backup: 3 };
const MANAGEMENT = ['dmz', 'branch', 'field', 'app', 'data', 'backup'].map((b) => ['management', b]);
export const BASE = [['branch', 'dmz'], ['branch', 'app'], ['field', 'dmz'], ['dmz', 'app'],
  ['app', 'data'], ['data', 'backup'], ...MANAGEMENT];
// [class, endpoint count, corp-bound connections, external-bound connections, sessions in period]
export const ENDPOINT = [
  ['field laptop', 40, 26400, 5100, 880], ['remote worker', 25, 18600, 92000, 550],
  ['vendor', 6, 740, 0, 24],
];
// which zone an endpoint class lands in, and which rules get written from that zone
export const PLACEMENT = {
  flat: { land: () => 'branch', extra: [] },
  'single remote zone': { land: () => 'remote', extra: [['remote', 'dmz'], ['remote', 'app']] },
  'zone per role': {
    land: (s) => ({ 'field laptop': 'remote-field', 'remote worker': 'remote-office', vendor: 'remote-vendor' })[s],
    extra: [['remote-field', 'dmz'], ['remote-office', 'dmz'], ['remote-office', 'app'], ['remote-vendor', 'dmz']],
  },
};
export function reached(edges, start) {
  const seen = new Set(), queue = [start];
  while (queue.length) {
    const b = queue.shift();
    for (const [x, y] of edges) if (x === b && !seen.has(y)) { seen.add(y); queue.push(y); }
  }
  return seen;
}
export const itemsOf = (set) => [...set].reduce((a, b) => a + (ITEM[b] ?? 0), 0);
export const scope = (y, s) => {              // items an endpoint class can reach while the tunnel is open
  const edges = [...BASE, ...y.extra], start = y.land(s);
  const near = new Set([start, ...edges.filter(([x]) => x === start).map(([, b]) => b)]);
  const all = reached(edges, start); all.add(start);
  return { zone: [...all].filter((b) => ITEM[b]).length, direct: itemsOf(near), transitive: itemsOf(all) };
};
export const TOTAL_ITEMS = itemsOf(Object.keys(ITEM));
export const SESSION_HOURS = 8;               // length of a session opened by a single verification
export const sum = (i) => ENDPOINT.reduce((a, u) => a + u[i], 0);
```

The model gives two measures, and the two must not be confused. Directly reached items sum the landing
zone's own items with the targets of the rules that leave that zone; transitively reached items walk the
whole graph. The difference is what taking one more step depends on: directly reached items are in the
remote endpoint's own hands, transitive ones require an application flow to be used somewhere along the
way. The Network Segmentation and the Demilitarized Zone lesson counted the same distinction as the first hop versus transitive items.

```js
// run1.mjs — the zone and items an endpoint class can reach while the tunnel is open, across three placements
import { ENDPOINT, PLACEMENT, BASE, scope, TOTAL_ITEMS } from './tunnel.mjs';

console.log(`inside network ${TOTAL_ITEMS} items, ${BASE.length} zone rules\n`);
console.log('columns: direct / transitive items reached per endpoint class');
console.log(`${'placement'.padEnd(19)} ${'extra rules'.padStart(11)}` + ENDPOINT.map((u) => u[0].padStart(16)).join(''));
for (const [name, y] of Object.entries(PLACEMENT))
  console.log(`${name.padEnd(19)} ${String(y.extra.length).padStart(11)}` +
    ENDPOINT.map((u) => { const k = scope(y, u[0]); return `${k.direct}/${k.transitive}`.padStart(16); }).join(''));
console.log(`\n${'placement'.padEnd(19)} ${'extra rules'.padStart(11)} ${'widest direct'.padStart(17)} ` +
  `${'items out of scope'.padStart(19)} ${'closed-path share'.padStart(18)}`);
for (const [name, y] of Object.entries(PLACEMENT)) {
  const en = Math.max(...ENDPOINT.map((u) => scope(y, u[0]).direct));
  console.log(`${name.padEnd(19)} ${String(y.extra.length).padStart(11)} ${String(en).padStart(17)} ` +
    `${String(TOTAL_ITEMS - en).padStart(19)} ${`${((TOTAL_ITEMS - en) * 100 / TOTAL_ITEMS).toFixed(1)}%`.padStart(18)}`);
}
const r = PLACEMENT['zone per role'];
console.log('\nitems reached per class under zone-per-role:');
for (const u of ENDPOINT) console.log(`  ${u[0].padEnd(14)} ${u[1]} endpoints  ${String(scope(r, u[0]).direct).padStart(4)} direct, ${String(scope(r, u[0]).transitive).padStart(3)} transitive items`);
```

```
inside network 210 items, 12 zone rules

columns: direct / transitive items reached per endpoint class
placement           extra rules    field laptop   remote worker          vendor
flat                          0         136/145         136/145         136/145
single remote zone            2           16/25           16/25           16/25
zone per role                 4            4/25           16/25            4/25

placement           extra rules     widest direct  items out of scope  closed-path share
flat                          0               136                  74              35.2%
single remote zone            2                16                 194              92.4%
zone per role                 4                16                 194              92.4%

items reached per class under zone-per-role:
  field laptop   40 endpoints     4 direct,  25 transitive items
  remote worker  25 endpoints    16 direct,  25 transitive items
  vendor         6 endpoints     4 direct,  25 transitive items
```

The first row is what writing no rules at all costs. When the tunnel lands in the branch zone, the remote
endpoint stands on the same flat plane as that zone's hundred and twenty items and also inherits the rules
that leave the branch zone: directly reached items are a hundred and thirty-six, transitively reached items
a hundred and forty-five. Two-thirds of a two-hundred-and-ten-item network sits behind a single tunnel. The
vendor endpoint and the field laptop endpoint get the same number, because scope is decided not by who the
endpoint is but by which zone it lands in.

The second row is what two rule lines cost. When remote endpoints land in their own zone, directly reached
items drop to sixteen, transitively reached items to twenty-five; items out of scope climb from
seventy-four to a hundred and ninety-four. Two lines move a hundred and twenty items out of scope. This is
the highest-yield move in this lesson, and its cost is two lines.

The third row adds two more lines and does not change total scope at all: transitive access is still
twenty-five. What changes is direct access per class. The field laptop and the vendor endpoint now reach
four items directly instead of sixteen; the remote worker's scope stays the same, because its work is in
the app zone. Zone per role does not narrow total scope — **it leaves the widest scope to the fewest
endpoints**. Forty-six of the seventy-one endpoints now see four items instead of sixteen.

The shared lesson of these three rows is that the tunnel itself is not a security item. The same tunnel is
established, the same verification is performed, and traffic inside is carried the same way in all three
placements. The only thing that decides the measure is which zone the remote endpoint takes its address
from and which rules are written from that zone. A tunnel is an access mechanism; its scope is a
segmentation decision, and when no segmentation decision is made, the default scope is the widest one.

The cost of the placements shows up in the rule line, but there is one more item, and it is not in the
table: which class the remote endpoint belongs to must be determined on every session. In the flat
placement that lands in the internal zone, this question is never asked. In the zone-per-role placement, a
mapping is made on every session, and the information that mapping rests on is not something the endpoint
declares itself but an attribute that verification returns. The real cost of the four rule lines is
maintaining that mapping: an endpoint that changes class changes scope without the rule changing.

## Split Tunnel and the Scope of Verification

```js
// run2.mjs — full tunnel versus split tunnel: concentrator load, out-of-inspection connections, and the scope of verification
import { ENDPOINT, SESSION_HOURS, sum } from './tunnel.mjs';

console.log(`${'endpoint class'.padEnd(15)} ${'eps'.padStart(3)} ${'sessions'.padStart(8)} ${'corp-bound'.padStart(13)} ` +
  `${'external-bound'.padStart(14)} ${'full tunnel'.padStart(12)} ${'split'.padStart(8)} ${'unproven per session'.padStart(22)}`);
for (const [name, eps, corp, ext, sessions] of ENDPOINT)
  console.log(`${name.padEnd(15)} ${String(eps).padStart(3)} ${String(sessions).padStart(8)} ${String(corp).padStart(13)} ` +
    `${String(ext).padStart(14)} ${String(corp + ext).padStart(12)} ${String(corp).padStart(8)} ` +
    `${(corp / sessions).toFixed(1).padStart(22)}`);
const [eps, corp, ext, sessions] = [sum(1), sum(2), sum(3), sum(4)];
console.log(`\n${'measure'.padEnd(42)} ${'full tunnel'.padStart(11)} ${'split tunnel'.padStart(14)}`);
for (const [name, full, split] of [
  ['connections through the concentrator', corp + ext, corp],
  ['connections outside inspection', 0, ext],
  ['endpoints straddling both networks at once', 0, eps],
  ['sessions opened by a single verification', sessions, sessions],
  ['hours covered by one verification', SESSION_HOURS, SESSION_HOURS],
  ['connections covered by one verification', Math.round((corp + ext) / sessions), Math.round(corp / sessions)],
]) console.log(`${name.padEnd(42)} ${String(full).padStart(11)} ${String(split).padStart(14)}`);
console.log(`\nconnections cut when the tunnel drops  full ${corp + ext}  split ${corp}`);
console.log(`${sessions} verifications happen in the period; ${corp + ext} connections' subject identity rests on these ${sessions} proofs`);
```

```
endpoint class  eps sessions    corp-bound external-bound  full tunnel    split   unproven per session
field laptop     40      880         26400           5100        31500    26400                   30.0
remote worker    25      550         18600          92000       110600    18600                   33.8
vendor            6       24           740              0          740      740                   30.8

measure                                    full tunnel   split tunnel
connections through the concentrator            142840          45740
connections outside inspection                       0          97100
endpoints straddling both networks at once           0             71
sessions opened by a single verification          1454           1454
hours covered by one verification                    8              8
connections covered by one verification             98             31

connections cut when the tunnel drops  full 142840  split 45740
1454 verifications happen in the period; 142840 connections' subject identity rests on these 1454 proofs
```

Full tunnel routes all traffic through the concentrator: a hundred and forty-two thousand eight hundred
and forty connections, zero outside inspection. Split tunnel routes only the corp-bound portion, and the
load drops to forty-five thousand — but ninety-seven thousand one hundred connections pass through no
inspection at all. Where that number comes from shows up in the second row: the remote worker class's
external-bound connections alone are ninety-two thousand. The entire source of split tunnel cutting the
load to a third is this set that it removes from inspection.

The third row is split tunnel's real item. In split tunnel, seventy-one endpoints stand in both networks
at once: holding an address from the internal network and connected directly to the external network.
There is no rule set between the two networks; the endpoint itself is a node facing both sides. Every
boundary drawn in the zone graph intersects at these seventy-one points. In full tunnel this number is
zero — its cost is that the ninety-seven thousand outbound connections also pass through the corporate
exit, and the concentrator carries three times the load.

The last three rows return to the measure itself. A thousand four hundred and fifty-four verifications
happen in the period, and once the tunnel opens, it is not asked again for eight hours. In full tunnel, one
verification counts an average of ninety-eight connections as proven; in split tunnel, thirty-one. Of the
forty-eight claims counted in the first lesson, three were subject identities that no generation could
prove; the tunnel proves it once and leans on that single proof for the next ninety-seven connections. If
the proof turns out wrong — if the endpoint is in someone else's hands — the network has no place to
notice this for eight hours, because every connection inside the tunnel appears as part of a verified
session.

There is one more detail in the first of the columns. The unproven-connections-per-session count sits
around thirty in all three classes: thirty, thirty-three point eight, and thirty point eight. The number
not changing across classes shows that the measure has nothing to do with endpoint count or volume of
work. What decides it is session length; an eight-hour session ties every piece of work done in that time
to a single proof. Halving the session length halves this number too, and in exchange doubles the number
of verifications — its cost is asking for identity two thousand nine hundred and eight times in the period
instead of a thousand four hundred and fifty-four.

The connections-cut-when-the-tunnel-drops column is the last item of the cost. The concentrator belongs to
the same class as the inspection points measured in the previous lesson: a visibility gain and an
availability debt taken on in the same move. In full tunnel the debt is a hundred and forty-two thousand
connections, in split tunnel forty-five thousand. The choice is not between the two; it is over which
number gets written on which side.

Site-to-site tunnels repeat the same measure at a different scale. In a remote-access tunnel a single
endpoint lands in a zone; in a site-to-site tunnel an entire network lands. A tunnel established between a
branch network and the central zone collapses the two networks into a single edge in the zone graph, and
which items that edge covers still depends on the rules written. When no rule is written, the two networks
become a single flat plane; the measure is still directly reached items, and this time the branch
network's item count is added not to the remote endpoint's scope but to the other side's.

## Summary

- When the tunnel lands in the internal zone, the remote endpoint reaches 136 items directly and 145
  transitively: two-thirds of a 210-item network. Scope depends not on who the endpoint is but on which
  zone it lands in.
- A separate remote-access zone, with 2 rule lines, brings direct access down to 16 and transitive access
  down to 25; items out of scope climb from 74 to 194. This is the highest-yield move.
- Zone per role adds 2 more lines and does not change total scope: it leaves the widest scope to the
  fewest endpoints. 46 of 71 endpoints see 4 items instead of 16.
- Split tunnel brings the concentrator load down from 142,840 to 45,740; in exchange, 97,100 connections
  pass through no inspection at all, and 71 endpoints stand in both networks at once.
- 1,454 verifications happen in the period and one verification covers 8 hours: in full tunnel an average
  of 98, in split tunnel 31 connections rest on a single proof. If the proof turns out wrong, there is no
  place for the network to notice for eight hours.

## Next Step

Six lessons of writing defenses have each come down to the same thing: a rule list. Zone boundaries are
fifteen lines, remote-access zones are four lines, inspection points and block lists have their own lines.
All of these lines sit side by side in a single box, and when a packet arrives, they are not all looked at
at once — they are looked at **in order**, and the first match wins. The next lesson's question is not
which rule is written but which one comes first: for a known set of flows, the first matching rule is
found for each flow, and how many flows' outcomes change when the list's order changes is counted.
