diff --git a/go.mod b/go.mod index 84f592b..c840f7e 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/miekg/pcap go 1.14 + +require golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..238c062 --- /dev/null +++ b/go.sum @@ -0,0 +1,8 @@ +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/pcap.go b/pcap.go index fe53a22..651a572 100644 --- a/pcap.go +++ b/pcap.go @@ -27,6 +27,8 @@ import ( "syscall" "time" "unsafe" + + "golang.org/x/net/bpf" ) type Pcap struct { @@ -173,6 +175,25 @@ func OpenOffline(file string) (handle *Pcap, err error) { return } +// Creates a fake Pcap that is not bound to any interface. This is useful for +// compiling filter expressions. +func OpenDead(linktype int32, snaplen int32) (handle *Pcap, err error) { + var buf *C.char + buf = (*C.char)(C.calloc(ERRBUF_SIZE, 1)) + h := new(Pcap) + + h.cptr = C.pcap_open_dead(C.int(linktype), C.int(snaplen)) + if h.cptr == nil { + handle = nil + err = &pcapError{C.GoString(buf)} + } else { + handle = h + } + + C.free(unsafe.Pointer(buf)) + return +} + // Pcap closes a handler. func (p *Pcap) Close() { C.pcap_close(p.cptr) @@ -234,6 +255,37 @@ func (p *Pcap) SetFilter(expr string) (err error) { return nil } +// Compile a pcap-filter(7) expression to BPF instructions. +// +// For filters that are not intended for a particular interface of the host, +// OpenDead can be used to create a fake Pcap to compile the expression. +func (p *Pcap) Compile(expr string) (insns []bpf.Instruction, err error) { + var bpf_prog C.struct_bpf_program + cexpr := C.CString(expr) + defer C.free(unsafe.Pointer(cexpr)) + + if C.pcap_compile(p.cptr, &bpf_prog, cexpr, 1, 0) == -1 { + return nil, p.Geterror() + } + + r_insns := make([]bpf.RawInstruction, bpf_prog.bf_len) + bf_len := int(bpf_prog.bf_len) + bf_insns := unsafe.Pointer(bpf_prog.bf_insns) + bf_size := unsafe.Sizeof(*bpf_prog.bf_insns) + for i := 0; i < bf_len; i++ { + bf_insn := (*C.struct_bpf_insn)(unsafe.Pointer(uintptr(bf_insns) + uintptr(i)*bf_size)) + r_insns[i] = bpf.RawInstruction{ + Op: uint16(bf_insn.code), + Jt: uint8(bf_insn.jt), + Jf: uint8(bf_insn.jf), + K: uint32(bf_insn.k), + } + } + C.pcap_freecode(&bpf_prog) + insns, _ = bpf.Disassemble(r_insns) + return +} + func (p *Pcap) SetDirection(direction string) (err error) { var pcap_direction C.pcap_direction_t if direction == "in" { diff --git a/pcap_test.go b/pcap_test.go index 16abb76..0348117 100644 --- a/pcap_test.go +++ b/pcap_test.go @@ -60,6 +60,27 @@ func TestPcapOpenLive(t *testing.T) { testPcapHandle(t, pcapOpenLive) } +func TestPcapFilterCompile(t *testing.T) { + h, err := OpenDead(LINKTYPE_ETHERNET, 65535) + if h == nil || err != nil { + if h != nil { + h.Close() + } + t.Fatalf("Failed to create/init pcap handle err: %s", err) + } + + filter := "udp port 53" + insns, err := h.Compile(filter) + if err != nil { + t.Errorf("Failed to compile filter expression: %s", err) + } + if len(insns) == 0 { + t.Error("Compiled filter has no instructions") + } + + h.Close() +} + func TestPcapDump(t *testing.T) { port := 54321 h, err := pcapOpenLive("lo", fmt.Sprintf("udp dst port %d", port), 2000)