254 lines
6.6 KiB
Go
254 lines
6.6 KiB
Go
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
|
|
}
|
|
|
|
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
|
|
}
|