PacketForge is a programmable packet crafting and injection tool based on two DSLs:
PDL(Protocol Definition Language): define protocol headers and defaults.PSL(Packet Stream Language): describe packet streams and flow control.
go install github.com/yanjiulab/packetforge/cmd/pf@latestor:
git clone https://github.com/yanjiulab/packetforge.git
cd packetforge
go build -o pf ./cmd/pf| Command | Primary goal | Reads @fuzz |
Sends real packets | Typical output |
|---|---|---|---|---|
pf |
normal parse/build/send | No (errors if present) | Yes (--dry-run disables) |
send result or dry-run hexdump |
pf fuzz |
run fuzz rules from PSL | Yes | Yes (--dry-run disables) |
per-case values + send/dry-run output |
pf explain |
visualize packet layout | No | No | layer offsets/length/hex (text or JSON) |
pf builtin |
list built-in protocols | N/A | No | protocol name list |
pf gen |
generate Go/C/C++ struct definitions from PDL | N/A | No | generated source/header files |
pf -s examples/test.psl -i eth0
pf -s examples/test.psl -dCommon flags:
-s, --streamPSL file (required)-p, --protoPDL directory (defaultproto)-i, --ifaceinterface (defaultlo)-d, --dry-runparse/build only-r, --recvstart receiving before sending and print received packet hex--recv-waitdrain wait after all sends when--recv-countis0(default1s). If--recv-countis set to a positive N, the default1sis not used as a cap: wait until N drain-phase packets, unless you explicitly set--recv-waitorPF_RECV_WAITto limit total wait time--recv-countin the drain phase only, stop after N received packets (default0, unlimited; send phase does not count toward the limit)--recv-bpftcpdump-style BPF filter for received packets (e.g.icmp,tcp port 80); on Linux, a filter requires a CGO build with libpcap (CGO_ENABLED=1and e.g.libpcap-dev); without CGO, omit--recv-bpfor rebuild with CGO-b, --builtin-protoload builtin protocols--seedrandom seed for$rand*builtins
pf fuzz -s examples/fuzz-basic.psl -i eth0
pf fuzz -s examples/fuzz-basic.psl -d --seed 42 --max-cases 20Notes:
@fuzzrules are parsed only inpf fuzz.- In normal
pfmode, scripts containing@fuzzreturn an error.
Supported rules:
@fuzz layer.field boundary@fuzz layer.field pick(v1,v2,...)@fuzz layer.field range(min,max[,step])@fuzz count N
pf explain -s examples/basic.psl
pf explain -s examples/random-builtins.psl --seed 42 --format jsonOutput contains, per packet:
- layer name
- offset
- byte length
- layer hex bytes
pf builtinpf gen -p proto -o generated --lang go
pf gen -p proto -o generated --lang all
pf gen -p proto -o generated --lang cpp --expand-builtin-headsNotes:
- Supported
--lang:go,c,cpp,all. - Dynamic arrays are supported:
- Go:
[]T - C:
T*(for[]Tan extra<name>_lenfield is generated) - C++:
std::vector<T>
- Go:
- C++ output is split into two files:
pdl_gen.hpp: type definitionspdl_gen_codec.hpp: serialize/deserialize codec functions and usage comments
- Field-length arrays (
[field]Type) are generated as pointer + comment (length is referenced field). - By default builtin head fields (
mac,ipv4,ipv6) are kept as named types (Mac/IPv4/IPv6); use--expand-builtin-headsto expand to raw byte arrays. - C/C++ generated struct names no longer use
pf_/Pfprefixes.
- Built-in types:
u8,u16,u32,u64,mac,ipv4,ipv6 - Auto values:
$len,$payload_len,$cksum - Struct arrays:
[]Type,[N]Type,[field]Type - Nested structures via
struct+ field references
- Layer fields:
proto(field=value,...) - Packet wrappers:
[...](single-line packet may omit brackets) - Payload: backticks with optional prefixes:
- string (default):
`hello` - hex:
`x 48656c6c6f` - binary:
`b 01000001` - base64:
`64 SGVsbG8=`
- string (default):
- Flow control:
@repeat N/@repeat forever@interval 100ms|1s|...async { ... }
- Constants:
const NAME = ... - Builtins:
- sequence:
$inc(step),$seq(start[,step]) - random:
$rand(),$randn(max),$randrange(min,max),$randport(),$randmac(),$randipv4(),$randhex(n)
- sequence:
examples/test.psl:
const SOURCE = 192.168.1.1
const TARGET = 192.168.1.100
async {
[eth() ip(src=SOURCE, dst=TARGET) icmp() `ping`]
@repeat forever
@interval 1s
}
[
eth()
ip(src=SOURCE, dst=TARGET, id=$seq(1, 1))
tcp(sport=1234, dport=80)
`GET / HTTP/1.1\r\n\r\n`
]
@repeat 10
@interval 100ms
Run:
pf -s examples/test.psl -i eth0More examples are in examples/, and protocol definitions are in proto/.