---
title: 'Firewall Generations'
source: 'https://academia.sh/en/courses/network-security/firewall-generations'
course: 'Network Security'
language: en
updated: '2026-09-26T03:02:54+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Counts the claims a flow set carries and separates which generation each claim becomes visible from'
    - 'Runs three generations over the same set: measures separately the claim it sees, the claim it proves, and the claim it accepts unverified'
    - 'Names the item each generation must watch and the claim type no generation can prove'
---

# Firewall Generations

The forty-eight claims carried by eight flow classes are run across three firewall generations: how many the packet-filtering box sees, how many the stateful one sees, how many the application-aware one proves, how many new items each generation must watch in return, and how much of what is proven encrypted flow takes back.

The previous topic counted the claims carried by protocols, and all of them traveled the same
flat network: a message's source address, the session it belonged to, and the subject behind it
were tested at no point along the path. The first form of drawing a boundary through the middle
of the network is the firewall. Drawing the boundary alone proves no claim; the box standing at
the boundary can decide only on the field it **sees**.

This lesson's question is not a comparison of boxes. Over the same flow set, how many claims does
each of the packet-filtering, stateful, and application-aware firewalls see, how many can it
prove, how many does it accept unverified, and how many new items must it watch in order to prove
what it sees. Rule writing, default-deny, and rule breadth were measured in another course; the
axis here is not the rule itself but whether the field the rule rests on is provable.

- **PD1.** The eight flow classes, claim types, and session counts belong to a fictional regional
  measurement network. No real packets flow; what is measured is the model built with `node`.
- **PD2.** The claim types were reduced to eight. In a real exchange the field count is far
  larger; it grows the item count, it does not change the direction of the difference between
  generations.
- **PD3.** "Proves" is used here in a narrow sense: it means the box holds a counterpart that can
  test the claim. Cryptographic proof and certificate verification lie outside this course.
- **PD4.** The state-record count was taken as the period's concurrent-session total. In reality
  the table empties with timeouts; that lowers the record count, it does not change the fact that
  the table is an item.
- **PD5.** The spoofed-claim classes were written only as a model; none was given in executable
  form. What is counted is which generation stops it.
- **PD6.** In end-to-end encrypted flow, the only application claim visible from outside was
  counted as the server name.

## Claim and Generation Model

The model holds two things: from which generation onward each claim type is seen, and from which
generation onward it is provable. These two are not the same thing; the distinction itself is the
lesson's entire claim. The packet-filtering box sees the destination port number too, but only the
box that parses the flow can test whether the service behind that number is truly that service.

```js
// generations.mjs — the flow set and what each of three firewall generations sees/can prove; a model, not real traffic
export const GENERATION = ['packet', 'state', 'application'];
const D = (sees, proves) => ({ sees, proves });
export const CLAIM = {                        // claim: from which generation onward it is seen / provable
  'source-address': D('packet', 'packet'),     // proof: the expected address block of the direction it arrived from
  'destination-service': D('packet', 'application'), // the port number does not prove the service itself
  'flow-origin': D('packet', 'state'),          // proof: whether a record exists in the state table
  'session-membership': D('state', 'state'),
  'server-name': D('application', 'application'),
  'request-path': D('application', 'application'),
  'content-type': D('application', 'application'),
  'subject-identity': D('application', null),  // carried identity; the network layer cannot prove it
};
const sr = (n) => GENERATION.indexOf(n);
export const sees = (n, i) => sr(n) >= sr(CLAIM[i].sees);
export const proves = (n, i) => CLAIM[i].proves !== null && sr(n) >= sr(CLAIM[i].proves);
const T = Object.keys(CLAIM);
const k = (...a) => T.filter((_, i) => a[i]);
// [name, claims carried, concurrent sessions in period] — fictional regional measurement network
export const FLOW = [
  ['meter -> collector', k(1, 1, 1, 1, 0, 0, 0, 0), 41200],
  ['collector -> validator', k(1, 1, 1, 1, 1, 1, 0, 0), 8600],
  ['branch -> billing interface', k(1, 1, 1, 1, 1, 1, 0, 1), 12400],
  ['field terminal -> api', k(1, 1, 1, 1, 1, 1, 1, 1), 5100],
  ['management -> shell', k(1, 1, 1, 1, 0, 0, 0, 0), 310],
  ['outside -> public interface', k(1, 1, 1, 1, 1, 1, 1, 1), 26400],
  ['application -> package repository', k(1, 1, 1, 1, 1, 1, 0, 0), 90],
  ['backup -> offsite storage', k(1, 1, 1, 1, 1, 0, 0, 0), 12],
];
export const TOTAL = FLOW.reduce((a, [, i]) => a + i.length, 0);
export const SESSION = FLOW.reduce((a, x) => a + x[2], 0);
// End-to-end encrypted flows: the only application claim visible from outside is the server name
export const ENCRYPTED = ['branch -> billing interface', 'field terminal -> api', 'outside -> public interface',
  'application -> package repository', 'backup -> offsite storage'];
const closed = (name, i) => ENCRYPTED.includes(name) && CLAIM[i].sees === 'application' && i !== 'server-name';
export function count(n, encrypted = false) {  // for one generation: seen, proven, accepted unverified
  let see = 0, prove = 0;
  for (const [name, claims] of FLOW) for (const i of claims) {
    if (encrypted && closed(name, i)) continue;
    if (sees(n, i)) see++;
    if (proves(n, i)) prove++;
  }
  return { see, prove, unverified: TOTAL - prove, unseen: TOTAL - see };
}
export const ENCRYPTED_SESSION = FLOW.filter((a) => ENCRYPTED.includes(a[0])).reduce((a, x) => a + x[2], 0);
export const ITEM = {                          // the item each generation must watch
  packet: { 'rule line': 34, 'state record': 0, 'flow buffer': 0, 'protocol parser': 0 },
  state: { 'rule line': 34, 'state record': SESSION, 'flow buffer': 0, 'protocol parser': 0 },
  application: { 'rule line': 34, 'state record': SESSION, 'flow buffer': SESSION, 'protocol parser': 6 },
};
// Claim-forgery classes were written as a model only; none was given in executable form.
export const CLASS = [                         // [name, spoofed claim, lowest generation that proves that claim]
  ['source address from another zone', 'source-address', 'packet'],
  ['packet appearing as an established session', 'flow-origin', 'state'],
  ['different protocol on an allowed port', 'destination-service', 'application'],
  ['different server name in an allowed flow', 'server-name', 'application'],
  ['subject header carried in the request', 'subject-identity', null],
];
export const stopper = (s) => s[2] === null ? '-' : s[2];
```

Each row in the flow list holds one class, the claim set it carries, and the concurrent session
count for the period. The claim set is written as an eight-digit mask; zero means that field is
never present in that flow. The flow to the meter collector carries only four fields, because
neither a server name nor a request path exists at the application layer — it is a binary flow.
The request from the branch network to the billing interface, on the other hand, carries seven
fields, and one of them is the header stating who the subject behind the request is.

## Proven and Accepted Unverified

```js
// run1.mjs — the claims carried by eight flow classes are counted; three generations run over the same set
import { GENERATION, FLOW, TOTAL, CLAIM, sees, proves, count } from './generations.mjs';

console.log(`${'flow class'.padEnd(36)} ${'claims'.padStart(6)}` + GENERATION.map((n) => `${n} see/prove`.padStart(23)).join(''));
for (const [name, claims] of FLOW)
  console.log(`${name.padEnd(36)} ${String(claims.length).padStart(6)}` +
    GENERATION.map((n) => `${claims.filter((i) => sees(n, i)).length}/${claims.filter((i) => proves(n, i)).length}`.padStart(23)).join(''));
console.log(`\neight flow classes carry ${TOTAL} claims\n`);
console.log(`${'generation'.padEnd(13)} ${'unseen'.padStart(9)} ${'seen'.padStart(7)} ${'proven'.padStart(10)} ${'accepted unverified'.padStart(20)}`);
for (const n of GENERATION) {
  const s = count(n);
  console.log(`${n.padEnd(13)} ${String(s.unseen).padStart(9)} ${String(s.see).padStart(7)} ${String(s.prove).padStart(10)} ${String(s.unverified).padStart(20)}`);
}
const never = Object.keys(CLAIM).filter((i) => !GENERATION.some((n) => proves(n, i)));
console.log(`\ntype no generation can prove: ${never.join(', ')} — in ${FLOW.filter(([, i]) => i.includes(never[0])).length} flow classes`);
```

```
flow class                           claims       packet see/prove        state see/prove  application see/prove
meter -> collector                        4                    3/1                    4/3                    4/4
collector -> validator                    6                    3/1                    4/3                    6/6
branch -> billing interface               7                    3/1                    4/3                    7/6
field terminal -> api                     8                    3/1                    4/3                    8/7
management -> shell                       4                    3/1                    4/3                    4/4
outside -> public interface               8                    3/1                    4/3                    8/7
application -> package repository         6                    3/1                    4/3                    6/6
backup -> offsite storage                 5                    3/1                    4/3                    5/5

eight flow classes carry 48 claims

generation       unseen    seen     proven  accepted unverified
packet               24      24          8                   40
state                16      32         24                   24
application           0      48         45                    3

type no generation can prove: subject-identity — in 3 flow classes
```

The most telling column of the first table is the packet-filtering box's: no matter what the flow
class carries, the number does not change — three are seen and one is proven in every row. A flow
carrying eight claims and a flow carrying four claims are the same thing to that box; the four
fields in between lie outside its field of view. The only claim it proves is the source address,
and it can do this only because it knows the expected address block of the direction the packet
arrived from. The remaining two fields, the destination port and the flow origin, it sees but
cannot test.

Of the forty-eight claims in total, forty are accepted unverified at the packet-filtering box. The
stateful generation brings this number down to twenty-four, and the whole of its gain comes from
one place: the session table. The flow-origin claim means "this packet already belongs to an
established connection," and a field inside the packet itself states this. The box that keeps the
table tests that field not by taking its word, but by checking its own record. Sixteen claims
still lie outside the field of view.

The application-aware generation leaves no field unseen; it proves forty-five of the forty-eight
claims. The remaining three claims are of the same type, and the row below names it. The subject
identity carried in the request appears in three flow classes, and no generation can prove it —
because no box at the network layer holds a counterpart that can test that identity. The box only
sees that the header states it there. This claim's proof lies outside the network; whom it belongs
to and what it rests on is not this course's subject.

## Spoofed Claim, Item Watched, and Encrypted Flow

A claim not being proven does not mean that claim is false; it means it was not tested. To see
where the difference starts to matter, five classes in which the same claim types are spoofed were
placed into the model. These are not method recipes but single-line labels each: the point where
what a field states diverges from the truth.

```js
// run2.mjs — which generation stops each of five claim-forgery classes, how many items each generation must watch
import { GENERATION, CLASS, ITEM, SESSION, ENCRYPTED, ENCRYPTED_SESSION, count, stopper, proves } from './generations.mjs';

console.log(`${'spoofed claim class'.padEnd(46)} ${'claim type'.padEnd(22)}` + GENERATION.map((n) => n.padStart(13)).join(''));
for (const s of CLASS)
  console.log(`${s[0].padEnd(46)} ${s[1].padEnd(22)}` +
    GENERATION.map((n) => (proves(n, s[1]) ? 'stops' : 'passes').padStart(13)).join(''));
const stops = (n) => CLASS.filter((s) => proves(n, s[1])).length;
console.log('\n' + `${'generation'.padEnd(13)} ${'stopping class'.padStart(14)}` +
  Object.keys(ITEM.application).map((k) => k.padStart(22)).join(''));
for (const n of GENERATION)
  console.log(`${n.padEnd(13)} ${`${stops(n)}/${CLASS.length}`.padStart(14)}` +
    Object.values(ITEM[n]).map((v) => String(v).padStart(22)).join(''));
console.log(`\nstate record = concurrent sessions in period (${SESSION}); once the table fills, a new session is either refused or passed unrecorded`);
console.log(`no generation stops ${CLASS.filter((s) => stopper(s) === '-').length} class: ${CLASS.filter((s) => stopper(s) === '-').map((s) => s[0]).join(', ')}`);
const [open, encrypted] = [count('application'), count('application', true)];
console.log(`\nend-to-end encrypted flow: ${ENCRYPTED.length}/8 classes, ${ENCRYPTED_SESSION} sessions`);
console.log(`claim proven by the application-aware generation  ${open.prove} -> ${encrypted.prove}  (in encrypted flow only the server name is visible)`);
console.log(`claim accepted unverified                          ${open.unverified} -> ${encrypted.unverified}`);
console.log(`closing the gap: terminate and re-establish ${ENCRYPTED_SESSION} sessions, 1 extra round trip and 2 extra key items per session`);
```

```
spoofed claim class                            claim type                   packet        state  application
source address from another zone               source-address                stops        stops        stops
packet appearing as an established session     flow-origin                  passes        stops        stops
different protocol on an allowed port          destination-service          passes       passes        stops
different server name in an allowed flow       server-name                  passes       passes        stops
subject header carried in the request          subject-identity             passes       passes       passes

generation    stopping class             rule line          state record           flow buffer       protocol parser
packet                   1/5                    34                     0                     0                     0
state                    2/5                    34                 94112                     0                     0
application              4/5                    34                 94112                 94112                     6

state record = concurrent sessions in period (94112); once the table fills, a new session is either refused or passed unrecorded
no generation stops 1 class: subject header carried in the request

end-to-end encrypted flow: 5/8 classes, 44002 sessions
claim proven by the application-aware generation  45 -> 39  (in encrypted flow only the server name is visible)
claim accepted unverified                          3 -> 9
closing the gap: terminate and re-establish 44002 sessions, 1 extra round trip and 2 extra key items per session
```

The upper table runs the five spoofed-claim classes through the same three boxes. The rule-line
count is thirty-four in all three generations; the number of stopped classes rises from one to
four. The difference lies not in the rule written, but in the counterpart the box holds. The
second table names that counterpart: the stateful generation must keep a table of ninety-four
thousand records, and without keeping that table it cannot test the flow-origin claim. The
application-aware generation carries, in addition to the same table, a flow buffer per session and
six protocol parsers. The cost of stopping one more class is the item watched climbing from zero
to ninety-four thousand, and from there doubling.

There is one more direction to the ranking. The first of the five classes is stopped in all three
generations, and what stops it is the cheapest check of the cheapest generation: whether the
direction the packet arrived from matches the source address it carries. The item watched is zero,
because the counterpart already lies inside the routing decision. Upgrading the generation does
not strengthen this check; it only adds more behind it. The expensive generation lets the same
class through in an arrangement that forgets to run the cheap check.

The state table itself is a new item, and it can fill up. At the moment it fills, there are two
options: refuse the new session — cutting off legitimate traffic — or pass it unrecorded, meaning
the flow-origin claim for that session is once again accepted unverified. The capacity of a
state-keeping box is therefore not a performance item but a security item.

The bottom block gives the place where defense falls short. Five of the eight flow classes are
end-to-end encrypted, and in these the only application claim the box can see from outside is the
server name. The claim proven goes from forty-five to thirty-nine, and the claim accepted
unverified goes from three to nine. Application awareness works only where the flow is open. The
only way to take the loss back is to terminate the connection at the box and re-establish it
behind: one extra round trip, two extra key items, and a new point where the flow is seen in the
open, for each of the forty-four thousand sessions. The move that raises the number of claims
proven adds to the network a concentration point that was not there before.

## Summary

- Eight flow classes carry 48 claims. The packet-filtering box sees the same 3 fields in every
  class and proves only 1 of them; in total 40 claims are accepted unverified.
- The stateful generation brings the claims accepted unverified down to 24, and the whole of its
  gain comes from the session table: instead of the flow origin the packet states itself, it
  checks the box's own record.
- The application-aware generation proves 45 of the 48 claims. The remaining 3 are of the same
  type: no generation can prove the subject identity carried in the request, because there is no
  counterpart at the network layer to test it against.
- The cost is the item watched: the rule line stays at 34 across all three generations while the
  state record climbs from 0 to 94,112, the flow buffer from 0 to 94,112, and 6 protocol parsers
  are added. Once the table fills, the option is to refuse the legitimate session or pass it
  unverified.
- 5 of the 8 flow classes are end-to-end encrypted; across these 44,002 sessions the claim proven
  falls from 45 to 39. The way to take it back is to terminate the connection: 1 extra round trip,
  2 extra key items, and a new point where the flow appears open, per session.

## Next Step

This lesson's entire measurement assumed a single box standing at a single boundary, and every
claim that box could not prove entered the whole of the network behind it. All eight flow classes
stood on the same plane: a message reaching the meter collector could reach the billing interface
with the same move. The next lesson's question is not what the box sees, but how many pieces the
network is divided into: how many zones are reachable when one zone falls once boundaries are
drawn between zones, how many paths close, which side the demilitarized zone leaves the
outward-facing items on, and how many rule lines legitimate traffic between zones needs to keep
flowing.
