Skip to content

Commit

Permalink
[BPF] support for policy rules Log action
Browse files Browse the repository at this point in the history
It is sometimes nice to see whether packets are dropped and which
packets are dropped (or accepted) and where. Whenever a packet matches
ANY rule with a Log action, the verdict with packet details will be
logged to the trace pipe regardless of the bpfLogLevel setting or
bpfLogFilters filtering.

cali8d1e69e5f89-E: policy ALLOWED proto 17 src 10.65.0.3:46519 dest 172.18.0.6:8055
cali8d1e69e5f89-E: policy ALLOWED proto 6 src 10.65.0.3:36185 dest 10.65.0.2:8055
cali866cd63afec-I: policy ALLOWED proto 6 src 10.65.0.3:36185 dest 10.65.0.2:8055
cali866cd63afec-E: policy ALLOWED proto 6 src 10.65.0.2:43553 dest 10.65.0.3:8055
cali8d1e69e5f89-I: policy DENIED  proto 6 src 10.65.0.2:43553 dest 10.65.0.3:8055
cali8d1e69e5f89-E: policy ALLOWED proto 6 src 10.65.0.3:46519 dest 172.18.0.6:8055
  • Loading branch information
tomastigera committed Nov 7, 2024
1 parent c531e18 commit 46d50a1
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 13 deletions.
40 changes: 40 additions & 0 deletions felix/bpf-gpl/skb.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,44 @@ static CALI_BPF_INLINE void skb_set_mark(struct __sk_buff *skb, __u32 mark)

#define skb_mark_equals(skb, mask, val) (((skb)->mark & (mask)) == (val))

static CALI_BPF_INLINE void skb_log(struct cali_tc_ctx *ctx, bool accepted)
{
if (ctx->state->flags & CALI_ST_LOG_PACKET) {
#ifdef BPF_CORE_SUPPORTED
if (bpf_core_enum_value_exists(enum bpf_func_id, BPF_FUNC_trace_vprintk)) {
#if CALI_F_XDP
#define DIR_STR "X"
#elif ((CALI_COMPILE_FLAGS) & CALI_TC_INGRESS)
#define DIR_STR "I"
#else
#define DIR_STR "E"
#endif
char *ok = "ALLOWED";
char *drop = "DENIED ";
char fmt[] = "%s-" DIR_STR ": policy %s proto %d src " IP_FMT ":%d dest " IP_FMT ":%d";
__u64 args[] = {
#if !CALI_F_XDP
(__u64)(&ctx->globals->data.iface_name),
#else
(__u64)(&ctx->xdp_globals->iface_name),
#endif
accepted ? (__u64) ok : (__u64) drop,
ctx->state->ip_proto,
(__u64) &ctx->state->ip_src,
ctx->state->sport,
(__u64) &ctx->state->ip_dst,
ctx->state->dport,
};
bpf_trace_vprintk(fmt, sizeof(fmt), args, sizeof(args));
return;
}
#endif
if (accepted) {
bpf_log("ALLOWED");
} else {
bpf_log("DENIED");
}
}
}

#endif /* __SKB_H__ */
2 changes: 2 additions & 0 deletions felix/bpf-gpl/tc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1244,6 +1244,7 @@ int calico_tc_skb_accepted_entrypoint(struct __sk_buff *skb)
}

update_rule_counters(ctx);
skb_log(ctx, true);

ctx->fwd = calico_tc_skb_accepted(ctx);
return forward_or_drop(ctx);
Expand Down Expand Up @@ -1956,6 +1957,7 @@ int calico_tc_skb_drop(struct __sk_buff *skb)
CALI_DEBUG("Entering calico_tc_skb_drop");

update_rule_counters(ctx);
skb_log(ctx, false);
counter_inc(ctx, CALI_REASON_DROPPED_BY_POLICY);

CALI_DEBUG("proto=%d", ctx->state->ip_proto);
Expand Down
2 changes: 2 additions & 0 deletions felix/bpf-gpl/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ enum cali_state_flags {
CALI_ST_CT_NP_REMOTE = 0x100,
/* CALI_ST_NAT_EXCLUDE is set when there is a NAT hit, but we don't want to resolve (such as node local DNS). */
CALI_ST_NAT_EXCLUDE = 0x200,
/* CALI_ST_LOG_PACKET is set by policy program if a log rule was hit. */
CALI_ST_LOG_PACKET = 0x400,
};

struct fwd {
Expand Down
4 changes: 4 additions & 0 deletions felix/bpf/asm/asm.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,10 @@ func (b *Block) AndImm64(dst Reg, imm int32) {
b.add(AndImm64, dst, 0, 0, imm, "")
}

func (b *Block) OrImm64(dst Reg, imm int32) {
b.add(OrImm64, dst, 0, 0, imm, "")
}

func (b *Block) ShiftRImm64(dst Reg, imm int32) {
b.add(ShiftRImm64, dst, 0, 0, imm, "")
}
Expand Down
26 changes: 15 additions & 11 deletions felix/bpf/polprog/pol_prog_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ var (
// Bits in the state flags field.
FlagDestIsHost uint64 = 1 << 2
FlagSrcIsHost uint64 = 1 << 3
FlagLogPacket uint64 = 1 << 10
)

type Rule struct {
Expand Down Expand Up @@ -483,6 +484,7 @@ func (p *Builder) writeTiers(tiers []Tier, destLeg matchLeg, allowLabel string)
actionLabels := map[string]string{
"allow": allowLabel,
"deny": "deny",
"log": "log",
}
for _, tier := range tiers {
endOfTierLabel := fmt.Sprint("end_of_tier_", p.tierID)
Expand Down Expand Up @@ -532,10 +534,6 @@ func (p *Builder) writePolicyRules(policy Policy, actionLabels map[string]string
}
p.b.AddCommentF("Rule MatchID: %d", rule.MatchID)
action := strings.ToLower(rule.Action)
if action == "log" {
log.Debug("Skipping log rule. Not supported in BPF mode.")
continue
}
p.writeRule(rule, actionLabels[action], destLeg)
log.Debugf("End of rule %d", ruleIdx)
p.b.AddCommentF("End of rule %s", rule.RuleId)
Expand Down Expand Up @@ -740,14 +738,20 @@ func (p *Builder) writeStartOfRule() {
}

func (p *Builder) writeEndOfRule(rule Rule, actionLabel string) {
// If all the match criteria are met, we fall through to the end of the rule
// so all that's left to do is to jump to the relevant action.
// TODO log and log-and-xxx actions
if p.policyDebugEnabled {
p.writeRecordRuleHit(rule, actionLabel)
}
if actionLabel == "log" {
p.b.Load64(R1, R9, stateOffFlags)
p.b.OrImm64(R1, int32(FlagLogPacket))
p.b.Store64(R9, R1, stateOffFlags)
} else {
// If all the match criteria are met, we fall through to the end of the rule
// so all that's left to do is to jump to the relevant action.
// TODO log and log-and-xxx actions
if p.policyDebugEnabled {
p.writeRecordRuleHit(rule, actionLabel)
}

p.b.Jump(actionLabel)
p.b.Jump(actionLabel)
}

p.b.LabelNextInsn(p.endOfRuleLabel())
}
Expand Down
156 changes: 156 additions & 0 deletions felix/bpf/ut/log_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// Copyright (c) 2019-2021 Tigera, Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ut_test

import (
"net"
"testing"

"github.com/google/gopacket/layers"
. "github.com/onsi/gomega"

"github.com/projectcalico/calico/felix/bpf/polprog"
"github.com/projectcalico/calico/felix/bpf/routes"
"github.com/projectcalico/calico/felix/ip"
"github.com/projectcalico/calico/felix/proto"
)

func TestLog(t *testing.T) {
RegisterTestingT(t)

defer resetBPFMaps()
bpfIfaceName = "LOG"
defer func() { bpfIfaceName = "" }()

hostIP = node1ip

_, iphdr, _, _, pktBytes, _ := testPacketUDPDefault()

resetRTMap(rtMap)
beV4CIDR := ip.CIDRFromNetIP(iphdr.SrcIP).(ip.V4CIDR)
bertKey := routes.NewKey(beV4CIDR).AsBytes()
bertVal := routes.NewValueWithIfIndex(routes.FlagsLocalWorkload|routes.FlagInIPAMPool, 1).AsBytes()
err := rtMap.Update(bertKey, bertVal)
Expect(err).NotTo(HaveOccurred())

rules := &polprog.Rules{
Tiers: []polprog.Tier{{
Name: "base tier",
Policies: []polprog.Policy{
{
Name: "log icmp",
Rules: []polprog.Rule{
{
Rule: &proto.Rule{
Protocol: &proto.Protocol{NumberOrName: &proto.Protocol_Number{Number: 1}},
DstNet: []string{"8.8.8.8/32"},
Action: "Allow",
},
},
{
Rule: &proto.Rule{
Protocol: &proto.Protocol{NumberOrName: &proto.Protocol_Number{Number: 1}},
Action: "Log", // Denied by default deny when not matching any rule
},
},
},
},
{
Name: "log tcp allow",
Rules: []polprog.Rule{
{
Rule: &proto.Rule{
Protocol: &proto.Protocol{NumberOrName: &proto.Protocol_Number{Number: 6}},
Action: "Log",
},
},
{
Rule: &proto.Rule{
Protocol: &proto.Protocol{NumberOrName: &proto.Protocol_Number{Number: 6}},
Action: "Allow",
},
},
},
},
{
Name: "log udp deny",
Rules: []polprog.Rule{
{
Rule: &proto.Rule{
Protocol: &proto.Protocol{NumberOrName: &proto.Protocol_Number{Number: 17}},
Action: "Log",
},
},
{
Rule: &proto.Rule{
Protocol: &proto.Protocol{NumberOrName: &proto.Protocol_Number{Number: 17}},
Action: "Deny",
},
},
},
},
},
}},
}

runBpfTest(t, "calico_from_workload_ep", rules, func(bpfrun bpfProgRunFn) {
res, err := bpfrun(pktBytes)
Expect(err).NotTo(HaveOccurred())
Expect(res.Retval).To(Equal(resTC_ACT_SHOT))
})

pktIPHdr := *ipv4Default
pktIPHdr.Protocol = layers.IPProtocolTCP

pktTCPHdr := &layers.TCP{
SrcPort: layers.TCPPort(12345),
DstPort: layers.TCPPort(321),
SYN: true,
DataOffset: 5,
}

_, _, _, _, pktBytes, _ = testPacketV4(nil, &pktIPHdr, pktTCPHdr,
[]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 11, 22, 33, 44, 55, 66, 77, 88, 99, 0})

skbMark = 0

runBpfTest(t, "calico_from_workload_ep", rules, func(bpfrun bpfProgRunFn) {
res, err := bpfrun(pktBytes)
Expect(err).NotTo(HaveOccurred())
Expect(res.Retval).To(Equal(resTC_ACT_UNSPEC))
})

pktBytes = makeICMPErrorFrom(ipv4Default.SrcIP, &pktIPHdr, pktTCPHdr, 0, 0)

skbMark = 0

runBpfTest(t, "calico_from_workload_ep", rules, func(bpfrun bpfProgRunFn) {
res, err := bpfrun(pktBytes)
Expect(err).NotTo(HaveOccurred())
Expect(res.Retval).To(Equal(resTC_ACT_SHOT))
})

pktIPHdr2 := pktIPHdr
pktIPHdr2.SrcIP = net.ParseIP("8.8.8.8")
pktBytes = makeICMPErrorFrom(ipv4Default.SrcIP, &pktIPHdr2, pktTCPHdr, 0, 0)

skbMark = 0

runBpfTest(t, "calico_from_workload_ep", rules, func(bpfrun bpfProgRunFn) {
res, err := bpfrun(pktBytes)
Expect(err).NotTo(HaveOccurred())
Expect(res.Retval).To(Equal(resTC_ACT_UNSPEC))
})
}
9 changes: 7 additions & 2 deletions felix/fv/bpf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -609,8 +609,13 @@ func describeBPFTests(opts ...bpfTestOpt) bool {
pol := api.NewGlobalNetworkPolicy()
pol.Namespace = "fv"
pol.Name = "policy-1"
pol.Spec.Ingress = []api.Rule{{Action: "Allow"}}
pol.Spec.Egress = []api.Rule{{Action: "Allow"}}
if true || testOpts.bpfLogLevel == "info" {
pol.Spec.Ingress = []api.Rule{{Action: "Log"}, {Action: "Allow"}}
pol.Spec.Egress = []api.Rule{{Action: "Log"}, {Action: "Allow"}}
} else {
pol.Spec.Ingress = []api.Rule{{Action: "Allow"}}
pol.Spec.Egress = []api.Rule{{Action: "Allow"}}
}
pol.Spec.Selector = "all()"

pol = createPolicy(pol)
Expand Down

0 comments on commit 46d50a1

Please sign in to comment.