basically works
This commit is contained in:
parent
2a0461566d
commit
8e3a0dc41d
145
main.py
145
main.py
@ -0,0 +1,145 @@
|
|||||||
|
import pprint
|
||||||
|
|
||||||
|
from typing import List, Text, Optional
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from flask import Flask, render_template, request, jsonify
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ChainRule:
|
||||||
|
packets: str
|
||||||
|
byte_s: str
|
||||||
|
target: str
|
||||||
|
protocol: str
|
||||||
|
options: str
|
||||||
|
inp: str
|
||||||
|
out: str
|
||||||
|
source: str
|
||||||
|
destination: str
|
||||||
|
extra: str
|
||||||
|
raw: str
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
return f"{self.protocol}\n{self.inp}->{self.out}>{self.target}{'(' + self.extra + ')' if self.extra else ''}"
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Chain:
|
||||||
|
name: str
|
||||||
|
policy: Optional[str]
|
||||||
|
rules: List[ChainRule]
|
||||||
|
referenced: bool = False
|
||||||
|
|
||||||
|
def add_rule(self, rule: ChainRule):
|
||||||
|
self.rules.append(rule)
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
def find_id_position(list_of_dicts, target_id):
|
||||||
|
for i, d in enumerate(list_of_dicts):
|
||||||
|
if d.get("id") == target_id:
|
||||||
|
return i
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def build_chain_tree(chain: Chain):
|
||||||
|
# Initialize the tree shape
|
||||||
|
ret = [[{"id": chain.name}], [], []]
|
||||||
|
# Add each rule
|
||||||
|
for rule in chain.rules:
|
||||||
|
# Every rule is a new one, so just append
|
||||||
|
ret[1].append({"id": rule.name(), "parents": [chain.name]})
|
||||||
|
# The destination is trickier
|
||||||
|
# For each rule, check to see if the output exists in the third slot of the return
|
||||||
|
# If the output does exist, then append the rules name to the list,
|
||||||
|
# If the output does not exist, add a new output with the rules name
|
||||||
|
pos = find_id_position(ret[2], rule.target)
|
||||||
|
if pos is None:
|
||||||
|
ret[2].append({"id": rule.target, "parents": [rule.name()]})
|
||||||
|
else:
|
||||||
|
ret[2][pos]["parents"].append(rule.name())
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def build_full_chain_tree(chains: List[Chain]):
|
||||||
|
ret = [[], [], []]
|
||||||
|
for chain in chains:
|
||||||
|
if not chain.referenced:
|
||||||
|
tree = build_chain_tree(chain)
|
||||||
|
ret[0] = ret[0] + tree[0]
|
||||||
|
ret[1] = ret[1] + tree[1]
|
||||||
|
ret[2] = ret[2] + tree[2]
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/parse', methods=['POST'])
|
||||||
|
def manual_parse_chain():
|
||||||
|
raw_data = request.form.get('data')
|
||||||
|
chains = raw_data.split("Chain")
|
||||||
|
chains: List[Text] = list(filter(len, chains))
|
||||||
|
trees = []
|
||||||
|
parsed_chains = []
|
||||||
|
for chain in chains:
|
||||||
|
rules_raw = chain.splitlines()
|
||||||
|
if len(rules_raw) < 2:
|
||||||
|
raise ValueError("bad chain")
|
||||||
|
|
||||||
|
# First line of a chain is the chain metadata, such as name
|
||||||
|
chain_meta_raw = rules_raw[0].strip().split(" ")
|
||||||
|
policy = None
|
||||||
|
pprint.pp(chain_meta_raw)
|
||||||
|
if not ('references' in rules_raw[0]):
|
||||||
|
policy = chain_meta_raw[2]
|
||||||
|
|
||||||
|
chain = Chain(chain_meta_raw[0], policy, [])
|
||||||
|
if not ('policy' in rules_raw[0]):
|
||||||
|
chain.referenced = True
|
||||||
|
pprint.pp(chain)
|
||||||
|
|
||||||
|
|
||||||
|
# Second line is headers for the table, so drop
|
||||||
|
|
||||||
|
# Lines past this point may not exist, so care in parsing
|
||||||
|
# Third line and onwards is the rule itself
|
||||||
|
for rule in rules_raw[2:]:
|
||||||
|
r =[part for part in rule.split(" ") if part.strip()]
|
||||||
|
if len(r) == 0:
|
||||||
|
continue
|
||||||
|
if len(r) > 8:
|
||||||
|
r = r[:9] + [' '.join(r[9:])]
|
||||||
|
cr = ChainRule(r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], "", rule)
|
||||||
|
if len(r) > 9:
|
||||||
|
cr = ChainRule(r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], rule)
|
||||||
|
chain.add_rule(cr)
|
||||||
|
|
||||||
|
# If the chain has no rules, read the policy and add default rule of any
|
||||||
|
if len(chain.rules) == 0:
|
||||||
|
chain.add_rule(ChainRule(
|
||||||
|
0, 0, chain.policy, "all", "--", "any", "any", "anywhere", "anywhere", "", ""
|
||||||
|
))
|
||||||
|
|
||||||
|
# Build the tree for each chain
|
||||||
|
tree = build_chain_tree(chain)
|
||||||
|
trees.append(tree)
|
||||||
|
parsed_chains.append(chain)
|
||||||
|
|
||||||
|
full_tree = build_full_chain_tree(parsed_chains)
|
||||||
|
return jsonify(full_tree)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/', methods=["POST"])
|
||||||
|
def capture():
|
||||||
|
# Why is counter strike pinging on port 5000???
|
||||||
|
pprint.pp(request.json)
|
||||||
|
pprint.pp(request.data)
|
||||||
|
pprint.pp(request.values)
|
||||||
|
return jsonify({})
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def hello_world():
|
||||||
|
return render_template('index.html')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run()
|
327
templates/index.html
Normal file
327
templates/index.html
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>DWS IPFlow</title>
|
||||||
|
<!-- Google Fonts -->
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
|
||||||
|
|
||||||
|
<!-- CSS Reset -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/purecss@3.0.0/build/pure-min.css"
|
||||||
|
integrity="sha384-X38yfunGUhNzHpBaEBsWLO+A0HDYOQi8ufWDkZ0k9e0eXz/tH3II7uKZ9msv++Ls" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/purecss@3.0.0/build/grids-responsive-min.css">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.pure-g>div {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- You should properly set the path from the main file. -->
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body style="height:100vh; width: 100%;">
|
||||||
|
<div class="container" style="height: 100%;">
|
||||||
|
<div class="pure-g" style="height: 100%;">
|
||||||
|
<div class="pure-u-1-4"
|
||||||
|
style="height: 100%; padding: .5em; z-index: 10; box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;">
|
||||||
|
<form id="parse-form" method="post" class="pure-form pure-form-stacke" style="height: 100%;">
|
||||||
|
<div style="display: flex; flex-direction: column; height: 100%; gap: .25em;">
|
||||||
|
<div>
|
||||||
|
<label for="parsedata">Input</label>
|
||||||
|
</div>
|
||||||
|
<div style="flex-grow: 2;">
|
||||||
|
<textarea placeholder="Paste the output of 'sudo iptables -L -v' here" id="parsedata" name="parsedata"
|
||||||
|
class=" pure-input" style="width: 100%; resize: none; height: 100%;"></textarea>
|
||||||
|
</div>
|
||||||
|
<div style="width 100%">
|
||||||
|
<input class="pure-button pure-button-primary" type="submit" value="Render" style="width: 100%;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="pure-u-3-4" style="height: 100%;">
|
||||||
|
<div id="svg-container" style="height: 100%;">
|
||||||
|
<svg width="1" height="1"></svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script src="https://code.jquery.com/jquery-3.7.1.min.js"
|
||||||
|
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
color = d3.scaleOrdinal(d3.schemeDark2);
|
||||||
|
background_color = 'white';
|
||||||
|
constructTangleLayout = (levels, options = {}) => {
|
||||||
|
// precompute level depth
|
||||||
|
levels.forEach((l, i) => l.forEach(n => (n.level = i)));
|
||||||
|
|
||||||
|
var nodes = levels.reduce((a, x) => a.concat(x), []);
|
||||||
|
var nodes_index = {};
|
||||||
|
nodes.forEach(d => (nodes_index[d.id] = d));
|
||||||
|
|
||||||
|
// objectification
|
||||||
|
nodes.forEach(d => {
|
||||||
|
d.parents = (d.parents === undefined ? [] : d.parents).map(
|
||||||
|
p => nodes_index[p]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// precompute bundles
|
||||||
|
levels.forEach((l, i) => {
|
||||||
|
var index = {};
|
||||||
|
l.forEach(n => {
|
||||||
|
if (n.parents.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = n.parents
|
||||||
|
.map(d => d.id)
|
||||||
|
.sort()
|
||||||
|
.join('-X-');
|
||||||
|
if (id in index) {
|
||||||
|
index[id].parents = index[id].parents.concat(n.parents);
|
||||||
|
} else {
|
||||||
|
index[id] = {id: id, parents: n.parents.slice(), level: i, span: i - d3.min(n.parents, p => p.level)};
|
||||||
|
}
|
||||||
|
n.bundle = index[id];
|
||||||
|
});
|
||||||
|
l.bundles = Object.keys(index).map(k => index[k]);
|
||||||
|
l.bundles.forEach((b, i) => (b.i = i));
|
||||||
|
});
|
||||||
|
|
||||||
|
var links = [];
|
||||||
|
nodes.forEach(d => {
|
||||||
|
d.parents.forEach(p =>
|
||||||
|
links.push({source: d, bundle: d.bundle, target: p})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
var bundles = levels.reduce((a, x) => a.concat(x.bundles), []);
|
||||||
|
|
||||||
|
// reverse pointer from parent to bundles
|
||||||
|
bundles.forEach(b =>
|
||||||
|
b.parents.forEach(p => {
|
||||||
|
if (p.bundles_index === undefined) {
|
||||||
|
p.bundles_index = {};
|
||||||
|
}
|
||||||
|
if (!(b.id in p.bundles_index)) {
|
||||||
|
p.bundles_index[b.id] = [];
|
||||||
|
}
|
||||||
|
p.bundles_index[b.id].push(b);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
nodes.forEach(n => {
|
||||||
|
if (n.bundles_index !== undefined) {
|
||||||
|
n.bundles = Object.keys(n.bundles_index).map(k => n.bundles_index[k]);
|
||||||
|
} else {
|
||||||
|
n.bundles_index = {};
|
||||||
|
n.bundles = [];
|
||||||
|
}
|
||||||
|
n.bundles.sort((a, b) => d3.descending(d3.max(a, d => d.span), d3.max(b, d => d.span)))
|
||||||
|
n.bundles.forEach((b, i) => (b.i = i));
|
||||||
|
});
|
||||||
|
|
||||||
|
links.forEach(l => {
|
||||||
|
if (l.bundle.links === undefined) {
|
||||||
|
l.bundle.links = [];
|
||||||
|
}
|
||||||
|
l.bundle.links.push(l);
|
||||||
|
});
|
||||||
|
|
||||||
|
// layout
|
||||||
|
const padding = 8;
|
||||||
|
const node_height = 48;
|
||||||
|
const node_width = 150;
|
||||||
|
const bundle_width = 14;
|
||||||
|
const level_y_padding = 16;
|
||||||
|
const metro_d = 4;
|
||||||
|
const min_family_height = 22;
|
||||||
|
|
||||||
|
options.c ||= 16;
|
||||||
|
const c = options.c;
|
||||||
|
options.bigc ||= node_width + c;
|
||||||
|
|
||||||
|
nodes.forEach(
|
||||||
|
n => (n.height = (Math.max(1, n.bundles.length) - 1) * metro_d)
|
||||||
|
);
|
||||||
|
|
||||||
|
var x_offset = padding;
|
||||||
|
var y_offset = padding;
|
||||||
|
levels.forEach(l => {
|
||||||
|
x_offset += l.bundles.length * bundle_width;
|
||||||
|
y_offset += level_y_padding;
|
||||||
|
l.forEach((n, i) => {
|
||||||
|
n.x = n.level * node_width + x_offset;
|
||||||
|
n.y = node_height + y_offset + n.height / 2;
|
||||||
|
|
||||||
|
y_offset += node_height + n.height;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
levels.forEach(l => {
|
||||||
|
l.bundles.forEach(b => {
|
||||||
|
b.x =
|
||||||
|
d3.max(b.parents, d => d.x) +
|
||||||
|
node_width +
|
||||||
|
(l.bundles.length - 1 - b.i) * bundle_width;
|
||||||
|
b.y = i * node_height;
|
||||||
|
});
|
||||||
|
i += l.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
links.forEach(l => {
|
||||||
|
l.xt = l.target.x;
|
||||||
|
l.yt =
|
||||||
|
l.target.y +
|
||||||
|
l.target.bundles_index[l.bundle.id].i * metro_d -
|
||||||
|
(l.target.bundles.length * metro_d) / 2 +
|
||||||
|
metro_d / 2;
|
||||||
|
l.xb = l.bundle.x;
|
||||||
|
l.yb = l.bundle.y;
|
||||||
|
l.xs = l.source.x;
|
||||||
|
l.ys = l.source.y;
|
||||||
|
});
|
||||||
|
|
||||||
|
// compress vertical space
|
||||||
|
var y_negative_offset = 0;
|
||||||
|
levels.forEach(l => {
|
||||||
|
y_negative_offset +=
|
||||||
|
-min_family_height +
|
||||||
|
d3.min(l.bundles, b =>
|
||||||
|
d3.min(b.links, link => link.ys - 2 * c - (link.yt + c))
|
||||||
|
) || 0;
|
||||||
|
l.forEach(n => (n.y -= y_negative_offset));
|
||||||
|
});
|
||||||
|
|
||||||
|
// very ugly, I know
|
||||||
|
links.forEach(l => {
|
||||||
|
l.yt =
|
||||||
|
l.target.y +
|
||||||
|
l.target.bundles_index[l.bundle.id].i * metro_d -
|
||||||
|
(l.target.bundles.length * metro_d) / 2 +
|
||||||
|
metro_d / 2;
|
||||||
|
l.ys = l.source.y;
|
||||||
|
l.c1 = l.source.level - l.target.level > 1 ? Math.min(options.bigc, l.xb - l.xt, l.yb - l.yt) - c : c;
|
||||||
|
l.c2 = c;
|
||||||
|
});
|
||||||
|
|
||||||
|
var layout = {
|
||||||
|
width: d3.max(nodes, n => n.x) + node_width + 2 * padding,
|
||||||
|
height: d3.max(nodes, n => n.y) + node_height / 2 + 2 * padding,
|
||||||
|
node_height,
|
||||||
|
node_width,
|
||||||
|
bundle_width,
|
||||||
|
level_y_padding,
|
||||||
|
metro_d
|
||||||
|
};
|
||||||
|
|
||||||
|
return {levels, nodes, nodes_index, links, bundles, layout};
|
||||||
|
};
|
||||||
|
|
||||||
|
renderChart = (tangleLayout, options = {}) => {
|
||||||
|
options.color ||= (d, i) => color(i)
|
||||||
|
|
||||||
|
|
||||||
|
return `<svg width="${tangleLayout.layout.width}" height="${tangleLayout.layout.height
|
||||||
|
}" style="background-color: ${background_color}">
|
||||||
|
<style>
|
||||||
|
text {
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
.node {
|
||||||
|
stroke-linecap: round;
|
||||||
|
}
|
||||||
|
.link {
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<g>
|
||||||
|
${tangleLayout.bundles.map((b, i) => {
|
||||||
|
let d = b.links
|
||||||
|
.map(
|
||||||
|
l => `
|
||||||
|
M${l.xt} ${l.yt}
|
||||||
|
L${l.xb - l.c1} ${l.yt}
|
||||||
|
A${l.c1} ${l.c1} 90 0 1 ${l.xb} ${l.yt + l.c1}
|
||||||
|
L${l.xb} ${l.ys - l.c2}
|
||||||
|
A${l.c2} ${l.c2} 90 0 0 ${l.xb + l.c2} ${l.ys}
|
||||||
|
L${l.xs} ${l.ys}`
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
return `
|
||||||
|
<path class="link" d="${d}" stroke="${background_color}" stroke-width="5"/>
|
||||||
|
<path class="link" d="${d}" stroke="${options.color(b, i)}" stroke-width="2"/>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
|
||||||
|
${tangleLayout.nodes.map(
|
||||||
|
n => `
|
||||||
|
<path class="selectable node" data-id="${n.id
|
||||||
|
}" stroke="black" stroke-width="8" d="M${n.x} ${n.y - n.height / 2} L${n.x
|
||||||
|
} ${n.y + n.height / 2}"/>
|
||||||
|
<path class="node" stroke="white" stroke-width="4" d="M${n.x} ${n.y -
|
||||||
|
n.height / 2} L${n.x} ${n.y + n.height / 2}"/>
|
||||||
|
|
||||||
|
<text class="selectable" data-id="${n.id}" x="${n.x + 4}" y="${n.y -
|
||||||
|
n.height / 2 -
|
||||||
|
4}" stroke="${background_color}" stroke-width="2">${n.id}</text>
|
||||||
|
<text x="${n.x + 4}" y="${n.y -
|
||||||
|
n.height / 2 -
|
||||||
|
4}" style="pointer-events: none;">${n.id}</text>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</g>
|
||||||
|
</svg>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).on('submit', '#parse-form', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '/parse',
|
||||||
|
data: {
|
||||||
|
data: $("#parsedata").val()
|
||||||
|
},
|
||||||
|
success: function (result) {
|
||||||
|
|
||||||
|
console.log(result);
|
||||||
|
const tangleLayout = constructTangleLayout(result);
|
||||||
|
chart = renderChart(tangleLayout)
|
||||||
|
|
||||||
|
$("#svg-container").html(chart);
|
||||||
|
svg = d3.select('svg');
|
||||||
|
var zoom = d3.zoom()
|
||||||
|
.on("zoom", function (event) {
|
||||||
|
svg.select("g").attr("transform", event.transform);
|
||||||
|
});
|
||||||
|
svg.call(zoom);
|
||||||
|
svg.attr("width", "100%");
|
||||||
|
svg.attr("height", "100%");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
window.addEventListener("load", (event) => {
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user