Add missing files, update style

This commit is contained in:
2026-10-05 20:48:08 +02:00
parent e70549337e
commit 9d092ed9df
20 changed files with 1161 additions and 202 deletions
+63 -50
View File
@@ -1,35 +1,44 @@
{{define "title"}}Hex → pcap{{end}}
{{define "toolbar-info"}}<span class="toolbar-info">Build a pcap file from hex · <a href="/hex2pcap?example" class="toolbar-link">Load example</a></span>{{end}}
{{define "content"}}
{{with .Data}}
<h1>Hex → pcap</h1>
<p class="lead">
Paste hex for each layer. The fields are decoded and concatenated into one packet.
Any field may be empty, so you can also paste a whole packet into a single field.
Spaces, newlines, <code>:</code>, <code>-</code>, <code>0x</code> and <code>\x</code> are ignored.
<a href="/hex2pcap?example">Load example</a>.
</p>
{{if .Error}}<div class="error" role="alert">{{.Error}}</div>{{end}}
<form method="post" action="/hex2pcap">
<label for="l2">Layer 2 <span class="hint">e.g. Ethernet header</span></label>
<textarea id="l2" name="l2" rows="2" spellcheck="false">{{.Form.L2}}</textarea>
<section class="widget">
<div class="widget-title"><h2>Packet</h2></div>
<div class="widget-content">
<p class="note">
Paste hex for each layer. The fields are decoded and concatenated into one packet.
Any field may be empty, so you can also paste a whole packet into a single field.
Spaces, newlines, <code>:</code>, <code>-</code>, <code>0x</code> and <code>\x</code> are ignored.
</p>
<label for="l3">Layer 3 <span class="hint">IPv4/IPv6 header with options, optionally followed by the TCP/UDP/ICMP header, or the SCTP header and chunk header(s)</span></label>
<textarea id="l3" name="l3" rows="4" spellcheck="false">{{.Form.L3}}</textarea>
<label for="l2">Layer 2 <span class="hint">e.g. Ethernet header</span></label>
<textarea id="l2" name="l2" rows="2" spellcheck="false">{{.Form.L2}}</textarea>
<label for="l7">Payload <span class="hint">the rest of the packet, not interpreted</span></label>
<textarea id="l7" name="l7" rows="6" spellcheck="false">{{.Form.L7}}</textarea>
<label for="l3">Layer 3 <span class="hint">IPv4/IPv6 header with options, optionally followed by the TCP/UDP/ICMP header, or the SCTP header and chunk header(s)</span></label>
<textarea id="l3" name="l3" rows="4" spellcheck="false">{{.Form.L3}}</textarea>
<fieldset>
<label for="l7">Payload <span class="hint">the rest of the packet, not interpreted</span></label>
<textarea id="l7" name="l7" rows="6" spellcheck="false">{{.Form.L7}}</textarea>
</div>
</section>
<section class="widget">
<div class="widget-title"><h2>Options</h2></div>
<div class="widget-content">
<label class="check">
<input type="checkbox" name="fix"{{if .Form.Fix}} checked{{end}}>
Recalculate IP lengths and checksums
<span class="hint">IPv4/IPv6 length, IPv4 header checksum, TCP/UDP/ICMP/ICMPv6/SCTP checksum, UDP length.
The end of the layer 3 field also sets the TCP data offset and the length of the SCTP chunk the payload belongs to
(paste chunk data without padding).</span>
</label>
<p class="hint field-hint">
IPv4/IPv6 length, IPv4 header checksum, TCP/UDP/ICMP/ICMPv6/SCTP checksum, UDP length.
The end of the layer 3 field also sets the TCP data offset and the length of the SCTP chunk the payload belongs to
(paste chunk data without padding).
</p>
<div class="row">
<div>
@@ -39,55 +48,59 @@
</select>
</div>
<div>
<label for="customlinktype">Custom link type <span class="hint">when “Custom…” is selected</span></label>
<input id="customlinktype" name="customlinktype" inputmode="numeric" value="{{.Form.CustomLinkType}}" placeholder="e.g. 228">
<label for="customlinktype">Custom link type</label>
<input id="customlinktype" name="customlinktype" inputmode="numeric" value="{{.Form.CustomLinkType}}" placeholder="e.g. 228" aria-describedby="customlinktype-hint">
<span id="customlinktype-hint" class="hint field-hint">Used when “Custom…” is selected</span>
</div>
<div>
<label for="filename">File name</label>
<input id="filename" name="filename" value="{{.Form.Filename}}" placeholder="packet.pcap">
</div>
</div>
</fieldset>
<div class="actions">
<button type="submit" name="action" value="preview">Preview</button>
<button type="submit" name="action" value="download" class="primary">Download pcap</button>
<div class="actions">
<button type="submit" name="action" value="preview" class="btn">Preview</button>
<button type="submit" name="action" value="download" class="btn primary">Download pcap</button>
</div>
</div>
</section>
</form>
{{with .Result}}
<section class="result">
<h2>Preview</h2>
<dl class="summary">
<dt>Packet length</dt><dd>{{len .Packet}} bytes</dd>
<dt>Link type</dt><dd>{{.LinkTypeName}} ({{.LinkType}})</dd>
<dt>Layers</dt>
<dd>
<span class="l2">layer 2: {{.L2Len}}</span> ·
<span class="l3">layer 3: {{.L3Len}}</span> ·
<span class="l7">payload: {{.L7Len}}</span>
</dd>
</dl>
<section class="widget">
<div class="widget-title"><h2>Preview</h2></div>
<div class="widget-content">
<dl class="summary">
<dt>Packet length</dt><dd>{{len .Packet}} bytes</dd>
<dt>Link type</dt><dd>{{.LinkTypeName}} ({{.LinkType}})</dd>
<dt>Layers</dt>
<dd>
<span class="l2">layer 2: {{.L2Len}}</span> ·
<span class="l3">layer 3: {{.L3Len}}</span> ·
<span class="l7">payload: {{.L7Len}}</span>
</dd>
</dl>
{{if .Fixed}}
<h3>Fixups <span class="hint">IP header at offset {{.IPOffset}}</span></h3>
<table>
<thead><tr><th>Field</th><th>Old</th><th>New</th></tr></thead>
<tbody>
{{range .Fix.Changes}}
<tr{{if ne .Old .New}} class="changed"{{end}}><td>{{.Field}}</td><td><code>{{.Old}}</code></td><td><code>{{.New}}</code></td></tr>
{{end}}
</tbody>
</table>
{{range .Fix.Notes}}<p class="note">{{.}}</p>{{end}}
{{end}}
{{if .Fixed}}
<h3>Fixups <span class="hint">IP header at offset {{.IPOffset}}</span></h3>
<table>
<thead><tr><th>Field</th><th>Old</th><th>New</th></tr></thead>
<tbody>
{{range .Fix.Changes}}
<tr{{if ne .Old .New}} class="changed"{{end}}><td>{{.Field}}</td><td><code>{{.Old}}</code></td><td><code>{{.New}}</code></td></tr>
{{end}}
</tbody>
</table>
{{range .Fix.Notes}}<p class="note">{{.}}</p>{{end}}
{{end}}
<h3>Hex dump <span class="hint">changed bytes are underlined</span></h3>
<pre class="dump">
<h3>Hex dump <span class="hint">changed bytes are underlined</span></h3>
<pre class="dump">
{{- range .Dump -}}
<span class="off">{{.Offset}}</span> {{range .Bytes}}<span class="{{.Class}}{{if .Changed}} chg{{end}}">{{.Hex}}</span>{{.Sep}}{{end}} {{.ASCII}}
{{end -}}
</pre>
</div>
</section>
{{end}}
{{end}}
+9 -9
View File
@@ -1,14 +1,14 @@
{{define "title"}}Home{{end}}
{{define "title"}}Tools{{end}}
{{define "toolbar-info"}}<span class="toolbar-info">Small utilities for working with pcap packet captures</span>{{end}}
{{define "content"}}
<h1>pcap tools</h1>
<p class="lead">Small utilities for working with pcap packet captures.</p>
<ul class="tools">
<div class="widget-grid">
{{range .Tools}}
<li>
<a href="{{.Path}}">{{.Name}}</a>
<p>{{.Description}}</p>
</li>
<a href="{{.Path}}" class="widget tool-card">
<div class="widget-title"><h2>{{.Name}}</h2></div>
<div class="widget-content">{{.Description}}</div>
</a>
{{end}}
</ul>
</div>
{{end}}
+25 -6
View File
@@ -3,17 +3,36 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark">
<title>{{template "title" .}} · pcap tools</title>
<link rel="icon" type="image/png" sizes="32x32" href="/static/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/images/favicon-16x16.png">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<header>
<nav>
<a href="/" class="brand">pcap tools</a>
{{range .Tools}}<a href="{{.Path}}"{{if eq .Path $.Active}} class="active"{{end}}>{{.Name}}</a>{{end}}
</nav>
<input type="checkbox" id="nav-toggle" class="nav-toggle visually-hidden" aria-label="Toggle navigation">
<header class="topbar">
<label for="nav-toggle" class="burger" title="Menu">
<svg viewBox="0 0 60 50" aria-hidden="true"><rect y="41.8" width="60" height="8.2"/><rect y="20.9" width="60" height="8.2"/><rect width="60" height="8.2"/></svg>
</label>
<a href="/" class="app-title">pcap tools</a>
<a href="/" class="logo-symbol" aria-label="Home">
<svg viewBox="0 0 25 25" aria-hidden="true"><path d="m15.3 18.8 1.9 4.2h2.1l-2.5-6.4zM9.9 19.8 7.5 19l.7 4h2z"/><path d="m18.7 8.1-1.2.8L15 7.1l-1.2 1.3L11 7.3l-.5 2.9 2.1 2.3-6.8 1.1.1-2.6-2.1.9.7 1.8-1 9.3h2.1l1.2-5.2 4.8 1.6 2.5 3.6h2.1l-2-4.3 4.4-6.7-.7-1.6.8-.4 1.4 3 1.4-2.8-2.8-2.1zm-3.8 2.2-.8-.5.9-1.4.8.5-.9 1.4zM9.195 2.368l1.042-.354 1.286 3.788-1.042.353zM5.072 3.737l.747-.808 3.892 3.597-.746.808z"/><path d="m4.483 8.179.274-1.065 3.777.97L8.26 9.15z"/></svg>
</a>
</header>
<main>
<nav class="sidebar" aria-label="Tools">
<div class="nav-section">Tools</div>
{{range .Tools}}<a href="{{.Path}}" class="nav-item{{if eq .Path $.Active}} active{{end}}"{{if eq .Path $.Active}} aria-current="page"{{end}}>{{.Name}}</a>
{{end}}
</nav>
<main class="content">
<div class="toolbar">
<div class="breadcrumb">
{{- if ne .Active "/"}}<a href="/" class="toolbar-link">pcap tools</a><span class="title-separator">/</span>{{end -}}
<span class="toolbar-title">{{template "title" .}}</span>
</div>
{{block "toolbar-info" .}}{{end}}
</div>
{{template "content" .}}
</main>
</body>
+29
View File
@@ -0,0 +1,29 @@
{{define "title"}}Reorder pcap{{end}}
{{define "toolbar-info"}}<span class="toolbar-info">Sort packets by timestamp, like <code>reordercap</code></span>{{end}}
{{define "content"}}
{{with .Data}}
{{if .Error}}<div class="error" role="alert">{{.Error}}</div>{{end}}
<form method="post" action="/reorder" enctype="multipart/form-data">
<section class="widget">
<div class="widget-title"><h2>Reorder</h2></div>
<div class="widget-content">
<p class="note">
Packets with the same timestamp keep their original order. Packet data and the file header are not changed.
</p>
<label for="file">pcap file <span class="hint">classic pcap, up to {{.MaxUpload}}. Convert pcapng with <code>editcap -F pcap</code></span></label>
<input id="file" name="file" type="file" accept=".pcap,.cap,.dmp,application/vnd.tcpdump.pcap" required>
<p class="note">If the capture ends with a truncated packet, that packet is dropped.</p>
<div class="actions">
<button type="submit" class="btn primary">Reorder and download</button>
</div>
</div>
</section>
</form>
{{end}}
{{end}}
+37 -33
View File
@@ -1,47 +1,51 @@
{{define "title"}}Split pcap{{end}}
{{define "toolbar-info"}}<span class="toolbar-info">Split a pcap file by packet count or file size</span>{{end}}
{{define "content"}}
{{with .Data}}
<h1>Split pcap</h1>
<p class="lead">
Upload a pcap file and split it into several files by packet count or by file size.
The parts are downloaded together as a zip.
Packets are copied unchanged, and each part keeps the original file header.
</p>
{{if .Error}}<div class="error" role="alert">{{.Error}}</div>{{end}}
<form method="post" action="/split" enctype="multipart/form-data">
<label for="file">pcap file <span class="hint">classic pcap, up to {{.MaxUpload}}. Convert pcapng with <code>editcap -F pcap</code></span></label>
<input id="file" name="file" type="file" accept=".pcap,.cap,.dmp,application/vnd.tcpdump.pcap" required>
<section class="widget">
<div class="widget-title"><h2>Split</h2></div>
<div class="widget-content">
<p class="note">
The parts are downloaded together as a zip. Packets are copied unchanged, and each part keeps the original file header.
</p>
<fieldset class="modes">
<legend>Split by</legend>
<label class="check">
<input type="radio" name="mode" value="packets"{{if eq .Form.Mode "packets"}} checked{{end}}>
At most
<input name="packets" class="num" inputmode="numeric" value="{{.Form.Packets}}" aria-label="Packets per file">
packets per file
</label>
<label class="check">
<input type="radio" name="mode" value="size"{{if eq .Form.Mode "size"}} checked{{end}}>
At most
<input name="size" class="num" inputmode="decimal" value="{{.Form.Size}}" aria-label="Size per file">
<select name="unit" class="unit" aria-label="Size unit">
{{range .Units}}<option{{if eq . $.Data.Form.Unit}} selected{{end}}>{{.}}</option>{{end}}
</select>
per file
<span class="hint">1 MB = 1,000,000 bytes. A packet bigger than this gets a file to itself.</span>
</label>
</fieldset>
<label for="file">pcap file <span class="hint">classic pcap, up to {{.MaxUpload}}. Convert pcapng with <code>editcap -F pcap</code></span></label>
<input id="file" name="file" type="file" accept=".pcap,.cap,.dmp,application/vnd.tcpdump.pcap" required>
<p class="note">
At most {{.MaxParts}} output files. If the capture ends with a truncated packet, that packet is dropped.
</p>
<fieldset class="modes">
<legend>Split by</legend>
<label class="check">
<input type="radio" name="mode" value="packets"{{if eq .Form.Mode "packets"}} checked{{end}}>
At most
<input name="packets" class="num" inputmode="numeric" value="{{.Form.Packets}}" aria-label="Packets per file">
packets per file
</label>
<label class="check">
<input type="radio" name="mode" value="size"{{if eq .Form.Mode "size"}} checked{{end}}>
At most
<input name="size" class="num" inputmode="decimal" value="{{.Form.Size}}" aria-label="Size per file">
<select name="unit" class="unit" aria-label="Size unit">
{{range .Units}}<option{{if eq . $.Data.Form.Unit}} selected{{end}}>{{.}}</option>{{end}}
</select>
per file
</label>
</fieldset>
<div class="actions">
<button type="submit" class="primary">Split and download zip</button>
<p class="note">
1 MB = 1,000,000 bytes. A packet bigger than the size limit gets a file to itself.
At most {{.MaxParts}} output files. If the capture ends with a truncated packet, that packet is dropped.
</p>
<div class="actions">
<button type="submit" class="btn primary">Split and download zip</button>
</div>
</div>
</section>
</form>
{{end}}
{{end}}