Skip to content
academia.sh

Lesson 12 / 14

VPN Technologies

A tunnel is a routing arrangement: the tunnel selector decides which destinations it lets inside, and a flow that is not selected exits without ever seeing the boundary. Across forty flows, four selectors and two tunnel configurations measure false admit and false deny separately.

Contents

The course’s four lessons so far used the boundary in the same direction throughout: to separate the outside from the inside. Firewall types, the rule chain, the zone graph, and the detection mechanism all took the distinction between “what is inside” and “what is outside” as given, and built their decision on top of that distinction.

But what if an outside endpoint has to be let in? A remote employee’s machine, an entire segment at another site, or a cluster inside a cloud provider — none of these is physically inside, and yet they need to count as inside. VPN (virtual private network) is the name of the technology class that does this job. This lesson’s question is not what the tunnel protects: it is which flows the tunnel lets inside, and what happens to the ones it does not.

A Tunnel Is a Routing Decision

What can be done once the tunnel is established — the scope of access — was measured in the Perimeter Defense topic of the Cybersecurity curriculum and is not repeated here. What is measured here is the decision before the tunnel is established, and that decision is not a security decision but a routing decision.

The software running at the remote end answers a single question for every flow to be sent: does this flow go through the tunnel, or through the local exit? If the answer is yes, the flow is wrapped and carried to the concentrator at the other end of the tunnel; it is opened there and enters the organization’s boundary from that point. If the answer is no, the flow is never wrapped, exits directly through the remote end’s own connection, and never sees the organization’s boundary at all.

The distinction between these two paths carries the entire lesson. A flow going through the tunnel is subject to the rule chain. A flow not going through the tunnel is subject to no rule at all — because it never passes through the place where the rule was written.

The Tunnel Selector

The structure that makes this decision is called the tunnel selector. A selector is not a rule chain; it produces no action, it only defines a set: which destinations enter the tunnel.

# taught dump , not executed

tunnel definition A
  configuration : remote access
  selector      : destination zone { int }
  assigned zone : int
  not selected  : local exit

tunnel definition B
  configuration : site-to-site
  selector      : destination zone { int, dmz, mgmt }
  assigned zone : (none; source zone is preserved)
  not selected  : local exit

A setup where the selector covers every destination is called full tunnel; one that leaves part of them out is called a split tunnel. The split tunnel’s rationale is carrying capacity: routing all of the remote end’s traffic through the concentrator means routing flows with no relevance at all through it too. What this lesson asks is not whether the rationale holds, but in which direction its cost is paid.

A Selector Is a Set of Paths

Seeing where the selector sits makes the rest of the lesson readable. In the Switching and Routing course, a packet’s path was determined by the next hop, chosen by looking at its destination. The tunnel selector sits in exactly the same place: the tunnel interface is one of the next-hop candidates, and the selector says for which destinations that candidate wins. This is why the selector is evaluated before the rule chain — the rule chain waits for the packet to reach the boundary; the selector decides whether the packet sets out toward the boundary at all.

This has two consequences. First, the selector’s resolution is routing’s resolution: it looks at the destination. A distinction like “this port through the tunnel, that port through the local exit” requires telling apart two flows going to the same destination, and a set of paths cannot express that. Second, the selector sits at the remote end. The concentrator sends it when the tunnel is established, but what determines the path of a non-selected flow is the remote end’s own routing table. Loosening the selector at the remote end is a configuration flaw, and it gives no indication at all on the boundary side: the boundary never sees that flow in the first place.

Two Configurations: Site-to-Site and Remote Access

The difference between the two tunnel configurations is not in the selector; it is in the identity of the source.

In a site-to-site tunnel, both ends are real zones. One site’s internal segment connects to another’s; every flow arrives with its own zone, and the boundary evaluates it by that zone. The tunnel only changes the path; it does not change who the flow is.

In a remote-access tunnel, the remote end has no zone of its own. It is a single machine, it connects from a variable location, and the boundary does not know which zone to place it in. This is why the tunnel assigns it a zone: every flow coming out of the tunnel enters the boundary with the zone the concentrator gives it. The assignment is not a convenience; it is a necessity — the rule chain reads the source criterion, and it needs something to read.

The measurement’s assumptions:

  • NS51 — The forty flows are produced from the course’s shared fixture; each flow’s source, destination, and port come from the fixture. The oracle is the policy function, known because we wrote the fixture. policy is not changed in this lesson.
  • NS52 — A flow entering the tunnel is opened at the concentrator and enters the boundary from there; the boundary evaluates it with policy. In the remote-access configuration, evaluation is done on a record whose source has been replaced with the assigned zone. What is changed is the record, not the function.
  • NS53 — A flow not entering the tunnel goes out through the remote end’s local exit. If its destination is the external zone it arrives; if it is headed for one of the internal zones it never arrives at all. These flows never pass through the rule chain.
  • NS54 — Four selectors are compared: full tunnel and three split sets. The selectors look only at the destination zone; they do not use the oracle.
  • NS55 — In the remote-access configuration, the assigned zone is a second axis and is measured on its own; the selector is held fixed at full tunnel in that measurement.
  • NS56 — The set is forty flows; the smallest measurable difference is 1/40 = 0.025. A smaller difference cannot be defended with this set.

Measurement

"""A tunnel is a routing decision: the selector decides which destinations come inside.

Part 1 - four tunnel selectors, in two tunnel configurations.
Part 2 - what the zone a remote-access tunnel assigns does on its own.
"""
SEED = 20260811
ZONES = ("ext", "dmz", "int", "mgmt")


def generator(seed):
    d = seed % 2147483646 + 1

    def r(n):
        nonlocal d
        d = (d * 48271) % 2147483647
        return d % n
    return r


def flows(count=40, seed=SEED):
    r, out = generator(seed), []
    for i in range(count):
        out.append({"no": i + 1, "source": ZONES[r(4)],
                    "dest": ZONES[r(4)],
                    "port": (80, 443, 22, 3306, 8080)[r(5)]})
    return out


def policy(a):
    """Intent: only the DMZ web entry from outside; nobody from outside to mgmt."""
    if a["source"] == "ext":
        return a["dest"] == "dmz" and a["port"] in (80, 443)
    if a["source"] == "dmz":
        return a["dest"] == "int" and a["port"] == 3306
    if a["source"] == "mgmt":
        return True
    return a["dest"] != "mgmt"


def diff(events, intent, device):
    d = {"correct_pass": 0, "correct_block": 0, "false_admit": 0, "false_deny": 0}
    for o in events:
        n, g = intent(o), device(o)
        if n and g:
            d["correct_pass"] += 1
        elif not n and not g:
            d["correct_block"] += 1
        elif g:
            d["false_admit"] += 1
        else:
            d["false_deny"] += 1
    return d


SELECTORS = {"full tunnel": lambda a: True,
             "split: int": lambda a: a["dest"] == "int",
             "split: int, dmz": lambda a: a["dest"] in ("int", "dmz"),
             "split: except ext": lambda a: a["dest"] != "ext"}


def site_to_site(selector):
    """Both ends are real zones; the tunnel only picks the path."""
    def run(a):
        if selector(a):
            return policy(a)
        return a["dest"] == "ext"      # a flow not selected exits directly
    return run


def remote_access(selector, assigned="int"):
    """The remote end has no zone of its own; the tunnel assigns it one."""
    def run(a):
        if selector(a):
            return policy({**a, "source": assigned})
        return a["dest"] == "ext"
    return run


ak = flows()
print(f"flow {len(ak)} | intent passes "
      f"{sum(1 for a in ak if policy(a))} | destination external "
      f"{sum(1 for a in ak if a['dest'] == 'ext')}")
print()
print(f"{'tunnel selector':<20s} {'selected':>8s} {'site-to-site':>25s}"
      f"{'remote access':>26s}")
print(f"{'':20s} {'':8s} {'correct':>8s} {'f.admit':>8s} {'f.deny':>8s}"
      f" {'correct':>8s} {'f.admit':>8s} {'f.deny':>8s}")
for name, s in SELECTORS.items():
    x, y = diff(ak, policy, site_to_site(s)), diff(ak, policy, remote_access(s))
    print(f"{name:<20s} {sum(1 for a in ak if s(a)):8d} "
          f"{x['correct_pass'] + x['correct_block']:8d} {x['false_admit']:8d} "
          f"{x['false_deny']:8d} {y['correct_pass'] + y['correct_block']:8d} "
          f"{y['false_admit']:8d} {y['false_deny']:8d}")

print()
print(f"{'assigned zone':<14s} {'correct':>6s} {'false admit':>12s} "
      f"{'false deny':>12s}   (full tunnel, remote access)")
for assigned in ZONES:
    d = diff(ak, policy, remote_access(SELECTORS["full tunnel"], assigned))
    print(f"{assigned:<14s} {d['correct_pass'] + d['correct_block']:6d} "
          f"{d['false_admit']:12d} {d['false_deny']:12d}")

print()
print(f"{'not selected':<20s} {'count':>5s} {'exits directly':>15s} "
      f"{'unreachable':>11s}")
for name, s in SELECTORS.items():
    outside = [a for a in ak if not s(a)]
    print(f"{name:<20s} {len(outside):5d} "
          f"{sum(1 for a in outside if a['dest'] == 'ext'):15d} "
          f"{sum(1 for a in outside if a['dest'] != 'ext'):11d}")
flow 40 | intent passes 22 | destination external 11

tunnel selector      selected              site-to-site             remote access
                               correct  f.admit   f.deny  correct  f.admit   f.deny
full tunnel                40       40        0        0       25       11        4
split: int                 10       24        6       10       21        9       10
split: int, dmz            18       30        6        4       25       11        4
split: except ext          29       34        6        0       25       11        4

assigned zone  correct  false admit   false deny   (full tunnel, remote access)
ext                22            0           18
dmz                20            0           20
int                25           11            4
mgmt               22           18            0

not selected         count  exits directly unreachable
full tunnel              0               0           0
split: int              30              11          19
split: int, dmz         22              11          11
split: except ext       11              11           0

Where an Unselected Flow Goes

The upper table’s first row is a baseline. In a site-to-site full tunnel, all forty flows go through the tunnel, each enters the boundary with its own zone, and the result is 40 correct, 0 false admit, 0 false deny. Here the tunnel added nothing to policy and took nothing away; it only carried the remote segment in front of the boundary.

As the selector narrows, both directions start speaking at once, and the two do not come from the same source.

False deny comes from flows not taken into the tunnel whose destination is an internal zone. They try to go by the local exit, fail to reach the organization, and intent would have passed a portion of them. 10 for the int selector, 4 for int, dmz, 0 for except ext. Widening the selector directly fixes this direction.

False admit, however, stays fixed at 6 no matter how wide the selector gets. Its source is in the lower table’s last row: in all three split configurations, 11 of the flows not entering the tunnel have an external destination, and these eleven really do arrive through the local exit. The oracle would have blocked 6 of them — because policy does not pass every flow with an external destination. Widening the selector from int to except ext never touches these six, because none of the three selectors takes an external destination into the tunnel.

The pattern is this: in a split tunnel, false deny is born from the selector’s narrowness, and false admit is born from what is left outside the selector never seeing the rule chain at all. The first closes as the selector is widened; the second does not — closing it requires the selector to take in the external destination too, meaning abandoning the split tunnel and returning to full tunnel. The except ext row shows this exactly: false deny is zeroed, false admit is still 6.

The size of the numbers is also worth noting. In a set of forty flows, the smallest measurable difference is 1/40 = 0.025. The fixed six false admits amount to 0.150 in this set; the int selector’s ten false denies amount to 0.250; both are well above the band. Every nonzero row difference in the table is at least two flows, that is, 0.050; no reading rests on a single flow. What the measurement claims, however, is not the numbers themselves but that the two directions come from separate sources: one from the narrowness of the selector, the other from what is left outside the selector never passing through the audit at all.

What the Assigned Zone Does on Its Own

The three columns on the right measure the same selectors in the remote-access configuration, and the table changes: even at full tunnel, 25 correct, 11 false admit, 4 false deny. Although all forty flows go through the tunnel, the result degrades, because in this configuration flows enter the boundary not with their own zone but with the zone the tunnel assigns. The rule chain reads the source criterion, and it now reads the same value for every one of them.

The middle table opens up this whole axis. With the assigned zone int, 11 false admit, 4 false deny. If the assignment is pulled to ext, false admit drops to 0 and false deny climbs to 18: no flow coming from the tunnel is trusted at all. If the assignment is opened to mgmt, false admit is 18, false deny is 0: every flow has been placed in the widest zone. dmz, meanwhile, worsens both directions at once — 0 and 20 — because the path policy gives the DMZ is a narrow one and does not match the real distribution of remote ends.

None of the four rows gives zero-zero. The reason is structural: the forty flows come from four separate source zones, and a single assignment collects them into a single zone. Information carrying a distinction, once mapped to a single value, cannot be recovered. The remote-access tunnel’s cost is not the bytes it carries but the distinction it erases.

The only way to recover the erased distinction is to stop the assignment from being a single value: set up a separate tunnel for each privilege set and assign each tunnel its own zone. The measurement says this directly — flows from four zones under four separate assignments produce four separate rows, and none of the rows carries another’s error. The cost shows up on the rule side: as many concentrator interfaces as tunnels, as many source values as interfaces, and that many separate branches in the rule chain. The course already said that counting rules does not measure policy; here the reverse is also true — avoiding a rule does not measure policy either, it just closes off a distinction cheaply.

Summary

  • Before it is established, a tunnel is a routing decision: the tunnel selector determines which destinations are let inside, and an unselected flow never passes through the rule chain.
  • In a site-to-site full tunnel, forty flows give 40 correct, 0 false admit, 0 false deny; the tunnel adds nothing to policy.
  • In a split tunnel, false deny comes from the selector’s narrowness and drops 10 → 4 → 0 as it widens; false admit stays fixed at 6 across all three split configurations, because its source is the 11 externally destined flows left outside the selector.
  • In the remote-access configuration, the tunnel must assign the flow a zone; even at full tunnel, the result falls to 25 correct, 11 false admit, 4 false deny.
  • The assigned zone alone pulls the two directions in opposite ways: 0 / 18 with ext, 18 / 0 with mgmt. Collecting flows from four zones into a single zone erases the distinction irrecoverably.

Next Step

The remote-access tunnel’s whole trick is making an outside endpoint look as if it came from the inside to the rule chain. The reason this trick is necessary sits in a single line: the rule chain still reads the source criterion and expects a zone to read. The next lesson measures what happens when this expectation is removed — how rule count and the two-way error change when the source criterion is stripped from the rule chain, and what has to be put in the location criterion’s place.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close