diff --git a/felix/bpf-gpl/skb.h b/felix/bpf-gpl/skb.h index 3009f0d6a4c..2aaeb88c5a3 100644 --- a/felix/bpf-gpl/skb.h +++ b/felix/bpf-gpl/skb.h @@ -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__ */ diff --git a/felix/bpf-gpl/tc.c b/felix/bpf-gpl/tc.c index 0dc955935ec..734577ace03 100644 --- a/felix/bpf-gpl/tc.c +++ b/felix/bpf-gpl/tc.c @@ -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); @@ -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); diff --git a/felix/bpf-gpl/types.h b/felix/bpf-gpl/types.h index d4225c0c7ac..70ea91a2ee8 100644 --- a/felix/bpf-gpl/types.h +++ b/felix/bpf-gpl/types.h @@ -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 { diff --git a/felix/bpf/asm/asm.go b/felix/bpf/asm/asm.go index 0b96bf718e7..f471e4d6073 100644 --- a/felix/bpf/asm/asm.go +++ b/felix/bpf/asm/asm.go @@ -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, "") } diff --git a/felix/bpf/polprog/pol_prog_builder.go b/felix/bpf/polprog/pol_prog_builder.go index 3ff91bde13f..c0f0c8be641 100644 --- a/felix/bpf/polprog/pol_prog_builder.go +++ b/felix/bpf/polprog/pol_prog_builder.go @@ -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 { @@ -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) @@ -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) @@ -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()) } diff --git a/felix/bpf/ut/log_test.go b/felix/bpf/ut/log_test.go new file mode 100644 index 00000000000..d625d46a160 --- /dev/null +++ b/felix/bpf/ut/log_test.go @@ -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)) + }) +} diff --git a/felix/fv/bpf_test.go b/felix/fv/bpf_test.go index 03a67f97694..31f3f65db8b 100644 --- a/felix/fv/bpf_test.go +++ b/felix/fv/bpf_test.go @@ -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)