diff --git a/filter.go b/filter.go index fe418133..a1a07ace 100644 --- a/filter.go +++ b/filter.go @@ -394,6 +394,7 @@ type BpfFilter struct { DirectAction bool Id int Tag string + Ops []SockFilter } func (filter *BpfFilter) Type() string { @@ -418,3 +419,10 @@ func (filter *GenericFilter) Attrs() *FilterAttrs { func (filter *GenericFilter) Type() string { return filter.FilterType } + +type SockFilter struct { + Code uint16 + Jt uint8 + Jf uint8 + K uint32 +} diff --git a/filter_linux.go b/filter_linux.go index fbd434aa..54e94a04 100644 --- a/filter_linux.go +++ b/filter_linux.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "errors" "fmt" + "math" "net" "syscall" @@ -371,6 +372,18 @@ func (h *Handle) filterModify(filter Filter, proto, flags int) error { if filter.Fd >= 0 { options.AddRtAttr(nl.TCA_BPF_FD, nl.Uint32Attr((uint32(filter.Fd)))) } + if len(filter.Ops) > 0 { + if filter.Fd >= 0 { + return fmt.Errorf("only Ops or Fd can be specified on a BpfFilter") + } + + opsLen, ops, err := serializeSockFilter(filter.Ops) + if err != nil { + return err + } + options.AddRtAttr(nl.TCA_BPF_OPS_LEN, nl.Uint16Attr(opsLen)) + options.AddRtAttr(nl.TCA_BPF_OPS, ops) + } if filter.Name != "" { options.AddRtAttr(nl.TCA_BPF_NAME, nl.ZeroTerminated(filter.Name)) } @@ -934,7 +947,10 @@ func parseFwData(filter Filter, data []syscall.NetlinkRouteAttr) (bool, error) { func parseBpfData(filter Filter, data []syscall.NetlinkRouteAttr) (bool, error) { bpf := filter.(*BpfFilter) + bpf.Fd = -1 detailed := true + var opsLen uint16 + var ops []byte for _, datum := range data { switch datum.Attr.Type { case nl.TCA_BPF_FD: @@ -952,6 +968,17 @@ func parseBpfData(filter Filter, data []syscall.NetlinkRouteAttr) (bool, error) bpf.Id = int(native.Uint32(datum.Value[0:4])) case nl.TCA_BPF_TAG: bpf.Tag = hex.EncodeToString(datum.Value) + case nl.TCA_BPF_OPS_LEN: + opsLen = native.Uint16(datum.Value[0:2]) + case nl.TCA_BPF_OPS: + ops = datum.Value + } + } + if opsLen > 0 { + var err error + bpf.Ops, err = deserializeSockFilter(opsLen, ops) + if err != nil { + return detailed, err } } return detailed, nil @@ -1039,3 +1066,42 @@ func SerializeRtab(rtab [256]uint32) []byte { _ = binary.Write(&w, native, rtab) return w.Bytes() } + +func deserializeSockFilter(opsLen uint16, ops []byte) ([]SockFilter, error) { + if excp := int(opsLen) * 8; len(ops) != excp { + return nil, fmt.Errorf("unexpected ops length %d, expected %d", len(ops), excp) + } + + ins := make([]SockFilter, opsLen) + for i := 0; i < len(ins); i++ { + ins[i] = SockFilter{ + Code: native.Uint16(ops[0:]), + Jt: ops[2], + Jf: ops[3], + K: native.Uint32(ops[4:]), + } + + ops = ops[8:] + } + + return ins, nil +} + +func serializeSockFilter(rawIns []SockFilter) (uint16, []byte, error) { + opsLen := len(rawIns) + if opsLen > math.MaxUint16 { + return 0, nil, fmt.Errorf("too many bpf instructions, max %d", math.MaxUint16) + } + + ops := make([]byte, 8*opsLen) + b := ops + for _, ins := range rawIns { + native.PutUint16(b[0:], ins.Code) + b[2] = ins.Jt + b[3] = ins.Jf + native.PutUint32(b[4:], ins.K) + + b = b[8:] + } + return uint16(opsLen), ops, nil +} diff --git a/filter_test.go b/filter_test.go index 76ce8668..88b53148 100644 --- a/filter_test.go +++ b/filter_test.go @@ -1156,6 +1156,87 @@ func TestFilterClsActBpfAddDel(t *testing.T) { } } +func TestFilterClsActCBpfAddDel(t *testing.T) { + // This feature was added in kernel 4.5 + minKernelRequired(t, 4, 5) + + tearDown := setUpNetlinkTest(t) + defer tearDown() + + qdisc, link := setupLinkForTestWithQdisc(t, "foo") + filterattrs := FilterAttrs{ + LinkIndex: link.Attrs().Index, + Parent: HANDLE_MIN_EGRESS, + Handle: MakeHandle(0, 1), + Protocol: unix.ETH_P_ALL, + Priority: 1, + } + + filter := &BpfFilter{ + FilterAttrs: filterattrs, + Fd: -1, + Name: "simple", + DirectAction: true, + Ops: []SockFilter{ + SockFilter{ + Code: 0x0006, + Jt: 0x00, + Jf: 0x00, + K: 0x00000002, + }, + }, + } + + if err := FilterAdd(filter); err != nil { + t.Fatal(err) + } + + filters, err := FilterList(link, HANDLE_MIN_EGRESS) + if err != nil { + t.Fatal(err) + } + if len(filters) != 1 { + t.Fatal("Failed to add filter") + } + bpf, ok := filters[0].(*BpfFilter) + if !ok { + t.Fatal("Filter is the wrong type") + } + if len(filter.Ops) != 1 || + bpf.Ops[0].Code != filter.Ops[0].Code || + bpf.Ops[0].Jt != filter.Ops[0].Jt || + bpf.Ops[0].Jf != filter.Ops[0].Jf || + bpf.Ops[0].K != filter.Ops[0].K { + + t.Fatal("Filter Ops does not match") + } + if bpf.DirectAction != filter.DirectAction { + t.Fatal("Filter DirectAction does not match") + } + + if err := FilterDel(filter); err != nil { + t.Fatal(err) + } + filters, err = FilterList(link, HANDLE_MIN_EGRESS) + if err != nil { + t.Fatal(err) + } + if len(filters) != 0 { + t.Fatal("Failed to remove filter") + } + + if err := QdiscDel(qdisc); err != nil { + t.Fatal(err) + } + qdiscs, err := SafeQdiscList(link) + if err != nil { + t.Fatal(err) + } + if len(qdiscs) != 0 { + t.Fatal("Failed to remove qdisc") + } +} + func TestFilterMatchAllAddDel(t *testing.T) { // This classifier was added in kernel 4.7 minKernelRequired(t, 4, 7)