Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,16 @@ To display only selected chains (supports regexp):

To also render empty chains:

`awk -f iptables-vis.awk -v 'include_empty_chains=1 < iptables.txt > iptables.dia'`
`awk -f iptables-vis.awk -v 'include_empty_chains=1' < iptables.txt > iptables.dia`

Mermaid Output
===============
Same as above, but use `iptables-vis-mermaid.awk`, e.g.

`awk -f iptables-vis-mermaid.awk -v 'include_empty_chains=1' < iptables.txt > iptables.mmd`

To `mermaid.html` file can be used to view mermaid files in the browser.
It needs to be servied via HTTPS so it can load the mermaid failes.

Legend
======
Expand Down
253 changes: 253 additions & 0 deletions iptables-vis-mermaid.awk
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
#!/usr/bin/awk -f
#
BEGIN {
print "flowchart TD"
counter = 0
edge_counter = 0
}

function make_id(s) {
gsub(/-/, "_", s)
return s
}

function sanitize_label(s) {
gsub(/"/, "'", s)
gsub(/ +/, " ", s)
gsub(/^ | $/, "", s)
return s
}

# Begin of a chain
/^Chain/ {
chainname = $2
in_chain=1
is_a_chain[chainname] = 1
if ( chainname !~ chain_selector && chain_selector != "")
next
in_relevant_chain=1
if ( $3 == "(policy" )
policy=$4
else
policy=0
last=chainname
delete chain_buffer; chain_buf_size = 0
delete pending_edge_styles; chain_edge_counter = 0
delete filter_nodes; delete targets; delete target_node_names; delete target_labels
num_targets = 0
chain_buffer[chain_buf_size++] = " subgraph sg_" make_id(chainname) " [\"" chainname "\"]"
chain_buffer[chain_buf_size++] = " direction TB"
}

# Filter in chain
in_chain && /^ *[0-9]/ {
filters_in_chain++
if ( !in_relevant_chain )
next
name="Node" counter++
reject_with=""
label=""
if ( $4 != "all" )
label=label $4 " "
if ( $5 != "--" )
label=label $5 " "
if ( $6 != "any" )
label=label "in:" $6 " "
if ( $7 != "any" )
label=label "out:" $7 " "
if ( $8 != "anywhere" )
label=label "src:" $8 " "
if ( $9 != "anywhere" )
label=label "dst:" $9 " "
comment=0
for (i=10; i<=NF; i++) {
if ( $i == "/*" )
comment=1
else if ( $i == "*/" )
comment=0
else if ( $i == "reject-with" ) {
i++
reject_with = $i
i++
}
else if ( ! comment )
label=label " " $i
}
if ( label == "" )
label = "*"
label = sanitize_label(label)
chain_buffer[chain_buf_size++] = " " name "{\"" label "\"}:::rule"
chain_buffer[chain_buf_size++] = " " make_id(last) " --> " name
chain_edge_counter++
last=name
filter_nodes[num_targets++] = name
target_node_name = chainname "_" $3
if ( reject_with )
target_node_name = target_node_name "_" reject_with
target_label = $3
if ( reject_with )
target_label = target_label "<br/>" reject_with

targets[name] = $3
all_targets[name] = $3
target_node_names[name] = target_node_name
all_target_node_names[name] = target_node_name
target_labels[target_node_name] = target_label
}

# End of chain
/^$/ {
in_chain=0
filter_number[chainname] = filters_in_chain
if (in_relevant_chain)
finalize_chain()
filters_in_chain=0
in_relevant_chain=0
}

function finalize_chain( i, head_class, pol_target_node, pol_target_id, idx, name, target, target_node_name, target_label, target_id, rhs, safe_lbl) {
if ( !filters_in_chain && !include_empty_chains ) {
delete chain_buffer; chain_buf_size = 0
delete pending_edge_styles; chain_edge_counter = 0
delete filter_nodes; delete targets
delete target_node_names; delete target_labels
num_targets = 0
return
}

# Determine chain head class
if ( !filters_in_chain )
head_class = ":::chain_empty"
else if ( chainname ~ "^(INPUT|OUTPUT|FORWARD|PREROUTING|POSTROUTING)$" )
head_class = ":::chain_head_builtin"
else
head_class = ":::chain_head"

# Track chain order for top-alignment links emitted in END
chain_order[num_chains++] = make_id(chainname)

# Chain head node definition (inside subgraph)
chain_buffer[chain_buf_size++] = " " make_id(chainname) "[\"" chainname "\"]" head_class

# Policy edge + target node (inside subgraph)
if ( policy ) {
pol_target_node = chainname "_" policy
pol_target_id = make_id(pol_target_node)
chain_buffer[chain_buf_size++] = " " make_id(last) " --> " pol_target_id
if ( policy ~ "^ACCEPT$" )
pending_edge_styles[chain_edge_counter] = "stroke:green"
else if ( policy ~ "^(DROP|REJECT)" )
pending_edge_styles[chain_edge_counter] = "stroke:red"
chain_edge_counter++
if ( !already_rendered[pol_target_node] ) {
already_rendered[pol_target_node] = 1
if ( policy ~ "^ACCEPT$" )
chain_buffer[chain_buf_size++] = " " pol_target_id "[\"ACCEPT\"]:::accept"
else if ( policy ~ "^DROP$" )
chain_buffer[chain_buf_size++] = " " pol_target_id "[\"DROP\"]:::drop"
else if ( policy ~ "^REJECT($|_)" )
chain_buffer[chain_buf_size++] = " " pol_target_id "[\"REJECT\"]:::reject"
else if ( policy ~ "^RETURN$" )
chain_buffer[chain_buf_size++] = " " pol_target_id "[\"RETURN\"]:::return"
}
}

# Rule-to-target edges + target node definitions (all inside subgraph)
for ( idx = 0; idx < num_targets; idx++ ) {
name = filter_nodes[idx]
target = targets[name]
target_node_name = target_node_names[name]
target_label = target_labels[target_node_name]
target_id = make_id(target_node_name)

if ( target ~ "^ACCEPT$" )
pending_edge_styles[chain_edge_counter] = "stroke:green"
else if ( target ~ "^REJECT($|_)|DROP$" )
pending_edge_styles[chain_edge_counter] = "stroke:red"
else if ( target ~ "^RETURN$" )
pending_edge_styles[chain_edge_counter] = "stroke:blue"

if ( !already_rendered[target_node_name] ) {
already_rendered[target_node_name] = 1
if ( target ~ "^ACCEPT$" )
rhs = target_id "[\"ACCEPT\"]:::accept"
else if ( target ~ "^DROP$" )
rhs = target_id "[\"DROP\"]:::drop"
else if ( target ~ "^REJECT($|_)" ) {
safe_lbl = sanitize_label(target_label)
rhs = target_id "[\"" safe_lbl "\"]:::reject"
}
else if ( target ~ "^RETURN$" )
rhs = target_id "[\"RETURN\"]:::return"
else {
safe_lbl = sanitize_label(target_label)
rhs = target_id "[\"" safe_lbl "\"]:::chain_target"
}
} else {
rhs = target_id
}

chain_buffer[chain_buf_size++] = " " name " --> " rhs
chain_edge_counter++
}

# Close subgraph
chain_buffer[chain_buf_size++] = " end"

# Merge pending edge styles into global edge_styles at current offset
for ( i = 0; i < chain_edge_counter; i++ ) {
if ( i in pending_edge_styles )
edge_styles[edge_counter + i] = pending_edge_styles[i]
}

# Flush chain buffer
for ( i = 0; i < chain_buf_size; i++ )
print chain_buffer[i]
edge_counter += chain_edge_counter

delete chain_buffer; chain_buf_size = 0
delete pending_edge_styles; chain_edge_counter = 0
delete filter_nodes; delete targets
delete target_node_names; delete target_labels
num_targets = 0
# Note: do NOT delete already_rendered — it is global across chains
}

END {
filter_number[chainname] = filters_in_chain
if (in_relevant_chain)
finalize_chain()

# Override style for custom chain targets that turned out to be empty chains
for ( name in all_targets ) {
target = all_targets[name]
if ( !is_a_chain[target] )
continue
target_node_name = all_target_node_names[name]
if ( filter_number[target] == 0 && !style_overridden[target_node_name] ) {
style_overridden[target_node_name] = 1
print " style " make_id(target_node_name) " fill:#eeeeee,stroke:#aaaaaa,stroke-dasharray:5 5"
}
}

# linkStyle directives for colored edges
for ( i = 0; i < edge_counter; i++ ) {
if ( i in edge_styles )
print " linkStyle " i " " edge_styles[i]
}

# Invisible links from the first chain head to all others to force top-alignment
for ( i = 1; i < num_chains; i++ )
print " " chain_order[0] " ~~~ " chain_order[i]

# classDef declarations
print " classDef accept fill:lightgreen,stroke:green"
print " classDef drop fill:#ff6666,stroke:red"
print " classDef reject fill:#ff9999,stroke:red"
print " classDef return fill:#1ab3ff,stroke:blue"
print " classDef rule fill:white,stroke:black"
print " classDef chain_head fill:white,stroke:black"
print " classDef chain_head_builtin fill:#ffffcc,stroke:#999900"
print " classDef chain_target fill:white,stroke:#666"
print " classDef chain_empty fill:#eeeeee,stroke:#aaaaaa,stroke-dasharray:5 5"
}
Loading