Add hex2pcap
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"pcap-web-tools/internal/hexutil"
|
||||
"pcap-web-tools/internal/inet"
|
||||
"pcap-web-tools/internal/pcap"
|
||||
)
|
||||
|
||||
const maxFormBytes = 2 << 20
|
||||
|
||||
type linkTypeOption struct {
|
||||
Value string
|
||||
Label string
|
||||
}
|
||||
|
||||
var linkTypeOptions = []linkTypeOption{
|
||||
{"auto", "Auto (Ethernet, or Raw IP when layer 2 is empty and layer 3 is set)"},
|
||||
{"1", "Ethernet (1)"},
|
||||
{"101", "Raw IP (101)"},
|
||||
{"113", "Linux cooked SLL (113)"},
|
||||
{"276", "Linux cooked SLL2 (276)"},
|
||||
{"105", "IEEE 802.11 (105)"},
|
||||
{"custom", "Custom…"},
|
||||
}
|
||||
|
||||
type hex2pcapForm struct {
|
||||
L2, L3, L7 string
|
||||
Fix bool
|
||||
LinkType string
|
||||
CustomLinkType string
|
||||
Filename string
|
||||
}
|
||||
|
||||
var hex2pcapExample = hex2pcapForm{
|
||||
L2: "00 11 22 33 44 55 66 77 88 99 aa bb 08 00",
|
||||
L3: "45 00 00 00 00 01 40 00 40 11 00 00 c0 a8 00 01 c0 a8 00 02\n30 39 04 d2 00 00 00 00",
|
||||
L7: "68 65 6c 6c 6f 2c 20 70 63 61 70 21",
|
||||
Fix: true,
|
||||
LinkType: "auto",
|
||||
}
|
||||
|
||||
type hex2pcapResult struct {
|
||||
Packet []byte
|
||||
LinkType uint32
|
||||
LinkTypeName string
|
||||
L2Len int
|
||||
L3Len int
|
||||
L7Len int
|
||||
Fixed bool
|
||||
IPOffset int
|
||||
Fix inet.Result
|
||||
Dump []dumpRow
|
||||
}
|
||||
|
||||
type hex2pcapPage struct {
|
||||
Form hex2pcapForm
|
||||
Options []linkTypeOption
|
||||
Error string
|
||||
Result *hex2pcapResult
|
||||
Filename string
|
||||
}
|
||||
|
||||
func (s *Server) handleHex2PcapForm(w http.ResponseWriter, r *http.Request) {
|
||||
form := hex2pcapForm{Fix: true, LinkType: "auto"}
|
||||
if r.URL.Query().Has("example") {
|
||||
form = hex2pcapExample
|
||||
}
|
||||
s.render(w, http.StatusOK, "hex2pcap.html", "/hex2pcap", hex2pcapPage{Form: form, Options: linkTypeOptions})
|
||||
}
|
||||
|
||||
func (s *Server) handleHex2Pcap(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxFormBytes)
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "bad form: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
form := hex2pcapForm{
|
||||
L2: r.PostFormValue("l2"),
|
||||
L3: r.PostFormValue("l3"),
|
||||
L7: r.PostFormValue("l7"),
|
||||
Fix: r.PostFormValue("fix") != "",
|
||||
LinkType: r.PostFormValue("linktype"),
|
||||
CustomLinkType: r.PostFormValue("customlinktype"),
|
||||
Filename: r.PostFormValue("filename"),
|
||||
}
|
||||
page := hex2pcapPage{Form: form, Options: linkTypeOptions, Filename: pcapFilename(form.Filename)}
|
||||
|
||||
res, err := buildPacket(form)
|
||||
if err != nil {
|
||||
page.Error = err.Error()
|
||||
s.render(w, http.StatusBadRequest, "hex2pcap.html", "/hex2pcap", page)
|
||||
return
|
||||
}
|
||||
|
||||
if r.PostFormValue("action") != "download" {
|
||||
page.Result = res
|
||||
s.render(w, http.StatusOK, "hex2pcap.html", "/hex2pcap", page)
|
||||
return
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
pw, err := pcap.NewWriter(&buf, res.LinkType)
|
||||
if err == nil {
|
||||
err = pw.WritePacket(time.Now(), res.Packet)
|
||||
}
|
||||
if err != nil {
|
||||
page.Error = err.Error()
|
||||
s.render(w, http.StatusBadRequest, "hex2pcap.html", "/hex2pcap", page)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/vnd.tcpdump.pcap")
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", page.Filename))
|
||||
w.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
|
||||
w.Write(buf.Bytes())
|
||||
}
|
||||
|
||||
// buildPacket decodes and concatenates the hex fields, and optionally fixes
|
||||
// up the IP lengths and checksums.
|
||||
func buildPacket(f hex2pcapForm) (*hex2pcapResult, error) {
|
||||
var layers [3][]byte
|
||||
for i, in := range []struct{ name, hex string }{
|
||||
{"Layer 2", f.L2}, {"Layer 3", f.L3}, {"Payload", f.L7},
|
||||
} {
|
||||
b, err := hexutil.Decode(in.hex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", in.name, err)
|
||||
}
|
||||
layers[i] = b
|
||||
}
|
||||
l2, l3, l7 := layers[0], layers[1], layers[2]
|
||||
|
||||
pkt := bytes.Join(layers[:], nil)
|
||||
if len(pkt) == 0 {
|
||||
return nil, errors.New("all fields are empty; paste some hex")
|
||||
}
|
||||
if len(pkt) > pcap.DefaultSnapLen {
|
||||
return nil, fmt.Errorf("packet is %d bytes; maximum is %d", len(pkt), pcap.DefaultSnapLen)
|
||||
}
|
||||
|
||||
lt, err := resolveLinkType(f, len(l2) > 0, len(l3) > 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := &hex2pcapResult{
|
||||
LinkType: lt,
|
||||
LinkTypeName: pcap.LinkTypeName(lt),
|
||||
L2Len: len(l2),
|
||||
L3Len: len(l3),
|
||||
L7Len: len(l7),
|
||||
}
|
||||
|
||||
orig := bytes.Clone(pkt)
|
||||
if f.Fix {
|
||||
// The layer 3 field marks where the headers end, which tells Fix the
|
||||
// TCP header length and which SCTP chunk the payload belongs to.
|
||||
ipOff, hdrLen := len(l2), len(l3)
|
||||
if len(l3) == 0 {
|
||||
// No explicit layer 3: find the IP header by parsing the link layer.
|
||||
if ipOff, err = inet.FindIP(pkt, lt); err != nil {
|
||||
return nil, fmt.Errorf("fix lengths/checksums: %w (or uncheck the fix option)", err)
|
||||
}
|
||||
}
|
||||
if res.Fix, err = inet.Fix(pkt[ipOff:], hdrLen); err != nil {
|
||||
return nil, fmt.Errorf("fix lengths/checksums: %w (or uncheck the fix option)", err)
|
||||
}
|
||||
res.Fixed = true
|
||||
res.IPOffset = ipOff
|
||||
}
|
||||
|
||||
res.Packet = pkt
|
||||
res.Dump = hexDump(pkt, orig, len(l2), len(l2)+len(l3))
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func resolveLinkType(f hex2pcapForm, haveL2, haveL3 bool) (uint32, error) {
|
||||
switch f.LinkType {
|
||||
case "", "auto":
|
||||
if !haveL2 && haveL3 {
|
||||
return pcap.LinkTypeRaw, nil
|
||||
}
|
||||
return pcap.LinkTypeEthernet, nil
|
||||
case "custom":
|
||||
v, err := strconv.ParseUint(strings.TrimSpace(f.CustomLinkType), 0, 32)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid custom link type %q", f.CustomLinkType)
|
||||
}
|
||||
return uint32(v), nil
|
||||
}
|
||||
v, err := strconv.ParseUint(f.LinkType, 10, 32)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid link type %q", f.LinkType)
|
||||
}
|
||||
return uint32(v), nil
|
||||
}
|
||||
|
||||
// pcapFilename sanitizes a user supplied download name.
|
||||
func pcapFilename(name string) string {
|
||||
name = strings.TrimSuffix(strings.TrimSpace(name), ".pcap")
|
||||
name = strings.Map(func(r rune) rune {
|
||||
if r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9' || r == '-' || r == '_' || r == '.' {
|
||||
return r
|
||||
}
|
||||
return '_'
|
||||
}, name)
|
||||
if strings.Trim(name, "._") == "" {
|
||||
name = "packet"
|
||||
}
|
||||
return name + ".pcap"
|
||||
}
|
||||
|
||||
type dumpByte struct {
|
||||
Hex string
|
||||
Class string // l2, l3, l7 or empty for padding
|
||||
Changed bool
|
||||
Sep string // whitespace following the byte
|
||||
}
|
||||
|
||||
type dumpRow struct {
|
||||
Offset string
|
||||
Bytes []dumpByte
|
||||
ASCII string
|
||||
}
|
||||
|
||||
// hexDump renders pkt as hexdump rows, tagging each byte with the layer it
|
||||
// came from and whether the fixup changed it relative to orig.
|
||||
func hexDump(pkt, orig []byte, l3Start, l7Start int) []dumpRow {
|
||||
var rows []dumpRow
|
||||
for off := 0; off < len(pkt); off += 16 {
|
||||
row := dumpRow{Offset: fmt.Sprintf("%04x", off)}
|
||||
var ascii strings.Builder
|
||||
for i := off; i < off+16; i++ {
|
||||
sep := " "
|
||||
if i-off == 7 {
|
||||
sep = " "
|
||||
}
|
||||
if i >= len(pkt) {
|
||||
row.Bytes = append(row.Bytes, dumpByte{Hex: " ", Sep: sep})
|
||||
continue
|
||||
}
|
||||
class := "l7"
|
||||
switch {
|
||||
case i < l3Start:
|
||||
class = "l2"
|
||||
case i < l7Start:
|
||||
class = "l3"
|
||||
}
|
||||
b := pkt[i]
|
||||
row.Bytes = append(row.Bytes, dumpByte{Hex: fmt.Sprintf("%02x", b), Class: class, Changed: b != orig[i], Sep: sep})
|
||||
if b >= 0x20 && b < 0x7f {
|
||||
ascii.WriteByte(b)
|
||||
} else {
|
||||
ascii.WriteByte('.')
|
||||
}
|
||||
}
|
||||
row.ASCII = ascii.String()
|
||||
rows = append(rows, row)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
Reference in New Issue
Block a user