diff --git a/.mk/bc.mk b/.mk/bc.mk index 74923f064..1d147af58 100644 --- a/.mk/bc.mk +++ b/.mk/bc.mk @@ -31,7 +31,8 @@ define MAPS "packets_record":"perf_event_array", "dns_flows":"hash", "global_counters":"per_cpu_array", - "filter_map":"lpm_trie" + "filter_map":"lpm_trie", + "peer_filter_map":"lpm_trie" } endef diff --git a/bpf/flows_filter.h b/bpf/flows_filter.h index 43b211bfb..22055c774 100644 --- a/bpf/flows_filter.h +++ b/bpf/flows_filter.h @@ -11,42 +11,75 @@ if (trace_messages) \ bpf_printk(fmt, ##args) -static __always_inline int is_zero_ip(u8 *ip, u8 len) { - for (int i = 0; i < len; i++) { - if (ip[i] != 0) { - BPF_PRINTK("ip not zero ip[%d]:%d\n", i, ip[i]); - return 0; - } - } - return 1; -} +static __always_inline int flow_filter_setup_lookup_key(flow_id *id, struct filter_key_t *key, + u8 *len, u8 *offset, bool use_src_ip, + u16 eth_protocol) { -static __always_inline int is_equal_ip(u8 *ip1, u8 *ip2, u8 len) { - for (int i = 0; i < len; i++) { - if (ip1[i] != ip2[i]) { - BPF_PRINTK("ip mismatched ip1[%d]:%d not equal to ip2[%d]:%d\n", i, ip1[i], i, ip2[i]); - return 0; + if (eth_protocol == ETH_P_IP) { + *len = sizeof(u32); + *offset = sizeof(ip4in6); + if (use_src_ip) { + __builtin_memcpy(key->ip_data, id->src_ip + *offset, *len); + } else { + __builtin_memcpy(key->ip_data, id->dst_ip + *offset, *len); + } + key->prefix_len = 32; + } else if (eth_protocol == ETH_P_IPV6) { + *len = IP_MAX_LEN; + *offset = 0; + if (use_src_ip) { + __builtin_memcpy(key->ip_data, id->src_ip + *offset, *len); + } else { + __builtin_memcpy(key->ip_data, id->dst_ip + *offset, *len); } + key->prefix_len = 128; + } else { + return -1; } - return 1; + return 0; } static __always_inline int do_flow_filter_lookup(flow_id *id, struct filter_key_t *key, filter_action *action, u8 len, u8 offset, u16 flags, u32 drop_reason, u32 *sampling, - u8 direction) { + u8 direction, bool use_src_ip, u16 eth_protocol) { int result = 0; struct filter_value_t *rule = (struct filter_value_t *)bpf_map_lookup_elem(&filter_map, key); if (rule) { - BPF_PRINTK("rule found drop_reason %d flags %d\n", drop_reason, flags); + BPF_PRINTK("rule found drop_reason %d flags %d do_peerCIDR_lookup %d\n", drop_reason, flags, + rule->do_peerCIDR_lookup); result++; if (rule->action != MAX_FILTER_ACTIONS) { BPF_PRINTK("action matched: %d\n", rule->action); *action = rule->action; result++; } + + if (rule->do_peerCIDR_lookup) { + struct filter_key_t peerKey; + __builtin_memset(&peerKey, 0, sizeof(peerKey)); + // PeerCIDR lookup will will target the opposite IP compared to original CIDR lookup + // In other words if cidr is using srcIP then peerCIDR will be the dstIP + if (flow_filter_setup_lookup_key(id, &peerKey, &len, &offset, use_src_ip, + eth_protocol) < 0) { + BPF_PRINTK("peerCIDR failed to setup lookup key\n"); + result = 0; + goto end; + } + + u8 *peer_result = (u8 *)bpf_map_lookup_elem(&peer_filter_map, &peerKey); + if (peer_result) { + BPF_PRINTK("peerCIDR matched\n"); + result++; + } else { + BPF_PRINTK("peerCIDR couldn't find a matching key\n"); + result = 0; + goto end; + } + } + if (rule->sample && sampling != NULL) { BPF_PRINTK("sampling action is set to %d\n", rule->sample); *sampling = rule->sample; @@ -160,27 +193,6 @@ static __always_inline int do_flow_filter_lookup(flow_id *id, struct filter_key_ goto end; } - if (!is_zero_ip(rule->ip, len)) { - // for Ingress side we can filter using dstIP and for Egress side we can filter using srcIP - if (direction == INGRESS) { - if (is_equal_ip(rule->ip, id->dst_ip + offset, len)) { - BPF_PRINTK("dstIP matched\n"); - result++; - } else { - result = 0; - goto end; - } - } else { - if (is_equal_ip(rule->ip, id->src_ip + offset, len)) { - BPF_PRINTK("srcIP matched\n"); - result++; - } else { - result = 0; - goto end; - } - } - } - if (rule->direction != MAX_DIRECTION) { if (rule->direction == direction) { BPF_PRINTK("direction matched\n"); @@ -206,34 +218,6 @@ static __always_inline int do_flow_filter_lookup(flow_id *id, struct filter_key_ return result; } -static __always_inline int flow_filter_setup_lookup_key(flow_id *id, struct filter_key_t *key, - u8 *len, u8 *offset, bool use_src_ip, - u16 eth_protocol) { - - if (eth_protocol == ETH_P_IP) { - *len = sizeof(u32); - *offset = sizeof(ip4in6); - if (use_src_ip) { - __builtin_memcpy(key->ip_data, id->src_ip + *offset, *len); - } else { - __builtin_memcpy(key->ip_data, id->dst_ip + *offset, *len); - } - key->prefix_len = 32; - } else if (eth_protocol == ETH_P_IPV6) { - *len = IP_MAX_LEN; - *offset = 0; - if (use_src_ip) { - __builtin_memcpy(key->ip_data, id->src_ip + *offset, *len); - } else { - __builtin_memcpy(key->ip_data, id->dst_ip + *offset, *len); - } - key->prefix_len = 128; - } else { - return -1; - } - return 0; -} - /* * check if the flow match filter rule and return >= 1 if the flow is to be dropped */ @@ -254,7 +238,7 @@ static __always_inline int is_flow_filtered(flow_id *id, filter_action *action, } result = do_flow_filter_lookup(id, &key, action, len, offset, flags, drop_reason, sampling, - direction); + direction, false, eth_protocol); // we have a match so return if (result > 0) { return result; @@ -267,7 +251,7 @@ static __always_inline int is_flow_filtered(flow_id *id, filter_action *action, } return do_flow_filter_lookup(id, &key, action, len, offset, flags, drop_reason, sampling, - direction); + direction, true, eth_protocol); } #endif //__FLOWS_FILTER_H__ diff --git a/bpf/maps_definition.h b/bpf/maps_definition.h index 1cb466990..b6da15e3e 100644 --- a/bpf/maps_definition.h +++ b/bpf/maps_definition.h @@ -59,7 +59,7 @@ struct { __uint(pinning, LIBBPF_PIN_BY_NAME); } global_counters SEC(".maps"); -// LPM trie map used to filter traffic by IP address CIDR and direction +// LPM trie map used to filter traffic by IP address CIDR struct { __uint(type, BPF_MAP_TYPE_LPM_TRIE); __type(key, struct filter_key_t); @@ -69,4 +69,14 @@ struct { __uint(pinning, LIBBPF_PIN_BY_NAME); } filter_map SEC(".maps"); +// LPM trie map used to filter traffic by peer IP address CIDR +struct { + __uint(type, BPF_MAP_TYPE_LPM_TRIE); + __type(key, struct filter_key_t); + __type(value, u8); + __uint(max_entries, MAX_FILTER_ENTRIES); + __uint(map_flags, BPF_F_NO_PREALLOC); + __uint(pinning, LIBBPF_PIN_BY_NAME); +} peer_filter_map SEC(".maps"); + #endif //__MAPS_DEFINITION_H__ diff --git a/bpf/types.h b/bpf/types.h index cbae5af09..90f91d79f 100644 --- a/bpf/types.h +++ b/bpf/types.h @@ -268,8 +268,8 @@ struct filter_value_t { tcp_flags tcpFlags; u8 filter_drops; u32 sample; - u8 ip[IP_MAX_LEN]; -} __attribute__((packed)); + u8 do_peerCIDR_lookup; +} filter_value; // Force emitting enums/structs into the ELF const struct filter_value_t *unused12 __attribute__((unused)); diff --git a/docs/flow_filtering.md b/docs/flow_filtering.md index aceca7f95..ed5c5143a 100644 --- a/docs/flow_filtering.md +++ b/docs/flow_filtering.md @@ -34,6 +34,7 @@ Rule-base filtering is a method to control the flow of packets cached in the eBP - `FILTER_ICMP_TYPE` - ICMP type of the flow filter rule. - `FILTER_ICMP_CODE` - ICMP code of the flow filter rule. - `FILTER_PEER_IP` - Specific Peer IP address of the flow filter rule. +- `FILTER_PEER_CIDR` - Specific Peer IP CIDR of the flow filter rule. - `FILTER_TCP_FLAGS` - Filter based on TCP flags Possible values are SYN, SYN-ACK, ACK, FIN, RST, PSH, URG, ECE, CWR, FIN-ACK, RST_ACK - `FILTER_DROPS` - Filter flows when packets drop feature is enabled to filter only flows with drop cause not 0. @@ -50,17 +51,18 @@ of each packet against a CIDR range specified in the `FILTER_IP_CIDR` parameter. If the packet's source or destination IP address falls within the specified CIDR range, the filter takes action based on the configured rules. This action could involve allowing the packet to be cached in an eBPF flow table or blocking it. -### Matching Specific Endpoints with `FILTER_PEER_IP` +### Matching Specific Endpoints with `FILTER_PEER_IP` or `FILTER_PEER_CIDR` -The `FILTER_PEER_IP` parameter specifies the IP address of a specific endpoint. +The `FILTER_PEER_IP` parameter specifies the IP address of a specific endpoint, while +`FILTER_PEER_CIDR` specifies subnet for range of endpoints. Depending on whether the traffic is ingress (incoming) or egress (outgoing), this IP address is used to further refine the filtering process: -- In ingress traffic filtering, the `FILTER_PEER_IP` is used to match against the destination IP address of the packet. +- In ingress traffic filtering, the `FILTER_PEER_IP`/`FILTER_PEER_CIDR` is used to match against the destination IP(s) address of the packet. After the initial CIDR matching, the filter then narrows down the scope to packets destined for a specific endpoint specified by `FLOW_FILTER_PEER_IP`. -- In egress traffic filtering, the `FILTER_PEER_IP` is used to match against the source IP address of the packet. -After the initial CIDR matching, the filter narrows down the scope to packets originating from a specific endpoint -specified by `FILTER_PEER_IP`. +- In egress traffic filtering, the `FILTER_PEER_IP`/`FILTER_PEER_CIDR` is used to match against the source IP(s) address of the packet. +After the initial CIDR matching, the filter narrows down the scope to packets originating from a specific endpoint(s) +specified by `FILTER_PEER_IP` or `FILTER_PEER_CIDR`. ### How to fine-tune the flow filter rule configuration? @@ -130,5 +132,5 @@ for that we can use the following configuration: FILTER_ACTION=Accept FILTER_PROTOCOL=TCP FILTER_PORT=80 - FILTER_PEER_IP=1.2.1.10 + FILTER_PEER_CIDR=1.2.1.10/32 ``` diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index fd9397904..0c6452874 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -209,6 +209,7 @@ func FlowsAgent(cfg *Config) (*Flows, error) { FilterIPCIDR: r.FilterIPCIDR, FilterProtocol: r.FilterProtocol, FilterPeerIP: r.FilterPeerIP, + FilterPeerCIDR: r.FilterPeerCIDR, FilterDestinationPort: tracer.ConvertFilterPortsToInstr(r.FilterDestinationPort, r.FilterDestinationPortRange, r.FilterDestinationPorts), FilterSourcePort: tracer.ConvertFilterPortsToInstr(r.FilterSourcePort, r.FilterSourcePortRange, r.FilterSourcePorts), FilterPort: tracer.ConvertFilterPortsToInstr(r.FilterPort, r.FilterPortRange, r.FilterPorts), diff --git a/pkg/agent/config.go b/pkg/agent/config.go index 571e4701a..8afaeaa6b 100644 --- a/pkg/agent/config.go +++ b/pkg/agent/config.go @@ -75,6 +75,9 @@ type FlowFilter struct { FilterDrops bool `json:"drops,omitempty"` // FilterSample is the sample rate this matching flow will use FilterSample uint32 `json:"sample,omitempty"` + // FilterPeerCIDR is the PeerIP CIDR to filter flows. + // Example: 10.10.10.0/24 or 100:100:100:100::/64, default is 0.0.0.0/0 + FilterPeerCIDR string `json:"peer_cidr,omitempty"` } type Config struct { diff --git a/pkg/agent/packets_agent.go b/pkg/agent/packets_agent.go index 3b714d150..21cba9b44 100644 --- a/pkg/agent/packets_agent.go +++ b/pkg/agent/packets_agent.go @@ -90,6 +90,7 @@ func PacketsAgent(cfg *Config) (*Packets, error) { FilterIPCIDR: r.FilterIPCIDR, FilterProtocol: r.FilterProtocol, FilterPeerIP: r.FilterPeerIP, + FilterPeerCIDR: r.FilterPeerCIDR, FilterDestinationPort: tracer.ConvertFilterPortsToInstr(r.FilterDestinationPort, r.FilterDestinationPortRange, r.FilterDestinationPorts), FilterSourcePort: tracer.ConvertFilterPortsToInstr(r.FilterSourcePort, r.FilterSourcePortRange, r.FilterSourcePorts), FilterPort: tracer.ConvertFilterPortsToInstr(r.FilterPort, r.FilterPortRange, r.FilterPorts), diff --git a/pkg/ebpf/bpf_arm64_bpfel.go b/pkg/ebpf/bpf_arm64_bpfel.go index 31f4ca87e..019c122b4 100644 --- a/pkg/ebpf/bpf_arm64_bpfel.go +++ b/pkg/ebpf/bpf_arm64_bpfel.go @@ -68,27 +68,30 @@ type BpfFilterKeyT struct { } type BpfFilterValueT struct { - Protocol uint8 - DstPortStart uint16 - DstPortEnd uint16 - DstPort1 uint16 - DstPort2 uint16 - SrcPortStart uint16 - SrcPortEnd uint16 - SrcPort1 uint16 - SrcPort2 uint16 - PortStart uint16 - PortEnd uint16 - Port1 uint16 - Port2 uint16 - IcmpType uint8 - IcmpCode uint8 - Direction BpfDirectionT - Action BpfFilterActionT - TcpFlags BpfTcpFlagsT - FilterDrops uint8 - Sample uint32 - Ip [16]uint8 + Protocol uint8 + _ [1]byte + DstPortStart uint16 + DstPortEnd uint16 + DstPort1 uint16 + DstPort2 uint16 + SrcPortStart uint16 + SrcPortEnd uint16 + SrcPort1 uint16 + SrcPort2 uint16 + PortStart uint16 + PortEnd uint16 + Port1 uint16 + Port2 uint16 + IcmpType uint8 + IcmpCode uint8 + Direction BpfDirectionT + Action BpfFilterActionT + TcpFlags BpfTcpFlagsT + FilterDrops uint8 + _ [3]byte + Sample uint32 + DoPeerCIDR_lookup uint8 + _ [3]byte } type BpfFlowId BpfFlowIdT @@ -251,6 +254,7 @@ type BpfMapSpecs struct { FilterMap *ebpf.MapSpec `ebpf:"filter_map"` GlobalCounters *ebpf.MapSpec `ebpf:"global_counters"` PacketRecord *ebpf.MapSpec `ebpf:"packet_record"` + PeerFilterMap *ebpf.MapSpec `ebpf:"peer_filter_map"` } // BpfObjects contains all objects after they have been loaded into the kernel. @@ -279,6 +283,7 @@ type BpfMaps struct { FilterMap *ebpf.Map `ebpf:"filter_map"` GlobalCounters *ebpf.Map `ebpf:"global_counters"` PacketRecord *ebpf.Map `ebpf:"packet_record"` + PeerFilterMap *ebpf.Map `ebpf:"peer_filter_map"` } func (m *BpfMaps) Close() error { @@ -290,6 +295,7 @@ func (m *BpfMaps) Close() error { m.FilterMap, m.GlobalCounters, m.PacketRecord, + m.PeerFilterMap, ) } diff --git a/pkg/ebpf/bpf_arm64_bpfel.o b/pkg/ebpf/bpf_arm64_bpfel.o index e40a5643f..ea9d91d42 100644 Binary files a/pkg/ebpf/bpf_arm64_bpfel.o and b/pkg/ebpf/bpf_arm64_bpfel.o differ diff --git a/pkg/ebpf/bpf_powerpc_bpfel.go b/pkg/ebpf/bpf_powerpc_bpfel.go index eefda5fad..e989ff4d0 100644 --- a/pkg/ebpf/bpf_powerpc_bpfel.go +++ b/pkg/ebpf/bpf_powerpc_bpfel.go @@ -68,27 +68,30 @@ type BpfFilterKeyT struct { } type BpfFilterValueT struct { - Protocol uint8 - DstPortStart uint16 - DstPortEnd uint16 - DstPort1 uint16 - DstPort2 uint16 - SrcPortStart uint16 - SrcPortEnd uint16 - SrcPort1 uint16 - SrcPort2 uint16 - PortStart uint16 - PortEnd uint16 - Port1 uint16 - Port2 uint16 - IcmpType uint8 - IcmpCode uint8 - Direction BpfDirectionT - Action BpfFilterActionT - TcpFlags BpfTcpFlagsT - FilterDrops uint8 - Sample uint32 - Ip [16]uint8 + Protocol uint8 + _ [1]byte + DstPortStart uint16 + DstPortEnd uint16 + DstPort1 uint16 + DstPort2 uint16 + SrcPortStart uint16 + SrcPortEnd uint16 + SrcPort1 uint16 + SrcPort2 uint16 + PortStart uint16 + PortEnd uint16 + Port1 uint16 + Port2 uint16 + IcmpType uint8 + IcmpCode uint8 + Direction BpfDirectionT + Action BpfFilterActionT + TcpFlags BpfTcpFlagsT + FilterDrops uint8 + _ [3]byte + Sample uint32 + DoPeerCIDR_lookup uint8 + _ [3]byte } type BpfFlowId BpfFlowIdT @@ -251,6 +254,7 @@ type BpfMapSpecs struct { FilterMap *ebpf.MapSpec `ebpf:"filter_map"` GlobalCounters *ebpf.MapSpec `ebpf:"global_counters"` PacketRecord *ebpf.MapSpec `ebpf:"packet_record"` + PeerFilterMap *ebpf.MapSpec `ebpf:"peer_filter_map"` } // BpfObjects contains all objects after they have been loaded into the kernel. @@ -279,6 +283,7 @@ type BpfMaps struct { FilterMap *ebpf.Map `ebpf:"filter_map"` GlobalCounters *ebpf.Map `ebpf:"global_counters"` PacketRecord *ebpf.Map `ebpf:"packet_record"` + PeerFilterMap *ebpf.Map `ebpf:"peer_filter_map"` } func (m *BpfMaps) Close() error { @@ -290,6 +295,7 @@ func (m *BpfMaps) Close() error { m.FilterMap, m.GlobalCounters, m.PacketRecord, + m.PeerFilterMap, ) } diff --git a/pkg/ebpf/bpf_powerpc_bpfel.o b/pkg/ebpf/bpf_powerpc_bpfel.o index 161863307..77cb37b00 100644 Binary files a/pkg/ebpf/bpf_powerpc_bpfel.o and b/pkg/ebpf/bpf_powerpc_bpfel.o differ diff --git a/pkg/ebpf/bpf_s390_bpfeb.go b/pkg/ebpf/bpf_s390_bpfeb.go index 10fd2d4dd..1b0b501f6 100644 --- a/pkg/ebpf/bpf_s390_bpfeb.go +++ b/pkg/ebpf/bpf_s390_bpfeb.go @@ -68,27 +68,30 @@ type BpfFilterKeyT struct { } type BpfFilterValueT struct { - Protocol uint8 - DstPortStart uint16 - DstPortEnd uint16 - DstPort1 uint16 - DstPort2 uint16 - SrcPortStart uint16 - SrcPortEnd uint16 - SrcPort1 uint16 - SrcPort2 uint16 - PortStart uint16 - PortEnd uint16 - Port1 uint16 - Port2 uint16 - IcmpType uint8 - IcmpCode uint8 - Direction BpfDirectionT - Action BpfFilterActionT - TcpFlags BpfTcpFlagsT - FilterDrops uint8 - Sample uint32 - Ip [16]uint8 + Protocol uint8 + _ [1]byte + DstPortStart uint16 + DstPortEnd uint16 + DstPort1 uint16 + DstPort2 uint16 + SrcPortStart uint16 + SrcPortEnd uint16 + SrcPort1 uint16 + SrcPort2 uint16 + PortStart uint16 + PortEnd uint16 + Port1 uint16 + Port2 uint16 + IcmpType uint8 + IcmpCode uint8 + Direction BpfDirectionT + Action BpfFilterActionT + TcpFlags BpfTcpFlagsT + FilterDrops uint8 + _ [3]byte + Sample uint32 + DoPeerCIDR_lookup uint8 + _ [3]byte } type BpfFlowId BpfFlowIdT @@ -251,6 +254,7 @@ type BpfMapSpecs struct { FilterMap *ebpf.MapSpec `ebpf:"filter_map"` GlobalCounters *ebpf.MapSpec `ebpf:"global_counters"` PacketRecord *ebpf.MapSpec `ebpf:"packet_record"` + PeerFilterMap *ebpf.MapSpec `ebpf:"peer_filter_map"` } // BpfObjects contains all objects after they have been loaded into the kernel. @@ -279,6 +283,7 @@ type BpfMaps struct { FilterMap *ebpf.Map `ebpf:"filter_map"` GlobalCounters *ebpf.Map `ebpf:"global_counters"` PacketRecord *ebpf.Map `ebpf:"packet_record"` + PeerFilterMap *ebpf.Map `ebpf:"peer_filter_map"` } func (m *BpfMaps) Close() error { @@ -290,6 +295,7 @@ func (m *BpfMaps) Close() error { m.FilterMap, m.GlobalCounters, m.PacketRecord, + m.PeerFilterMap, ) } diff --git a/pkg/ebpf/bpf_s390_bpfeb.o b/pkg/ebpf/bpf_s390_bpfeb.o index e8a670984..315530539 100644 Binary files a/pkg/ebpf/bpf_s390_bpfeb.o and b/pkg/ebpf/bpf_s390_bpfeb.o differ diff --git a/pkg/ebpf/bpf_x86_bpfel.go b/pkg/ebpf/bpf_x86_bpfel.go index 1044fb98c..3fe711208 100644 --- a/pkg/ebpf/bpf_x86_bpfel.go +++ b/pkg/ebpf/bpf_x86_bpfel.go @@ -68,27 +68,30 @@ type BpfFilterKeyT struct { } type BpfFilterValueT struct { - Protocol uint8 - DstPortStart uint16 - DstPortEnd uint16 - DstPort1 uint16 - DstPort2 uint16 - SrcPortStart uint16 - SrcPortEnd uint16 - SrcPort1 uint16 - SrcPort2 uint16 - PortStart uint16 - PortEnd uint16 - Port1 uint16 - Port2 uint16 - IcmpType uint8 - IcmpCode uint8 - Direction BpfDirectionT - Action BpfFilterActionT - TcpFlags BpfTcpFlagsT - FilterDrops uint8 - Sample uint32 - Ip [16]uint8 + Protocol uint8 + _ [1]byte + DstPortStart uint16 + DstPortEnd uint16 + DstPort1 uint16 + DstPort2 uint16 + SrcPortStart uint16 + SrcPortEnd uint16 + SrcPort1 uint16 + SrcPort2 uint16 + PortStart uint16 + PortEnd uint16 + Port1 uint16 + Port2 uint16 + IcmpType uint8 + IcmpCode uint8 + Direction BpfDirectionT + Action BpfFilterActionT + TcpFlags BpfTcpFlagsT + FilterDrops uint8 + _ [3]byte + Sample uint32 + DoPeerCIDR_lookup uint8 + _ [3]byte } type BpfFlowId BpfFlowIdT @@ -251,6 +254,7 @@ type BpfMapSpecs struct { FilterMap *ebpf.MapSpec `ebpf:"filter_map"` GlobalCounters *ebpf.MapSpec `ebpf:"global_counters"` PacketRecord *ebpf.MapSpec `ebpf:"packet_record"` + PeerFilterMap *ebpf.MapSpec `ebpf:"peer_filter_map"` } // BpfObjects contains all objects after they have been loaded into the kernel. @@ -279,6 +283,7 @@ type BpfMaps struct { FilterMap *ebpf.Map `ebpf:"filter_map"` GlobalCounters *ebpf.Map `ebpf:"global_counters"` PacketRecord *ebpf.Map `ebpf:"packet_record"` + PeerFilterMap *ebpf.Map `ebpf:"peer_filter_map"` } func (m *BpfMaps) Close() error { @@ -290,6 +295,7 @@ func (m *BpfMaps) Close() error { m.FilterMap, m.GlobalCounters, m.PacketRecord, + m.PeerFilterMap, ) } diff --git a/pkg/ebpf/bpf_x86_bpfel.o b/pkg/ebpf/bpf_x86_bpfel.o index 3b446ffc1..aa3039e88 100644 Binary files a/pkg/ebpf/bpf_x86_bpfel.o and b/pkg/ebpf/bpf_x86_bpfel.o differ diff --git a/pkg/tracer/flow_filter.go b/pkg/tracer/flow_filter.go index 141f86e24..0fb15e14f 100644 --- a/pkg/tracer/flow_filter.go +++ b/pkg/tracer/flow_filter.go @@ -22,6 +22,7 @@ type FilterConfig struct { FilterIcmpType int FilterIcmpCode int FilterPeerIP string + FilterPeerCIDR string FilterAction string FilterTCPFlags string FilterDrops bool @@ -49,6 +50,18 @@ func (f *Filter) ProgramFilter(objects *ebpf.BpfObjects) error { return fmt.Errorf("failed to get filter value: %w", err) } + if val.DoPeerCIDR_lookup == 1 { + peerVal := uint8(1) + peerKey, err := f.getPeerFilterKey(config) + if err != nil { + return fmt.Errorf("failed to get peer filter key: %w", err) + } + err = objects.PeerFilterMap.Update(peerKey, peerVal, cilium.UpdateAny) + if err != nil { + return fmt.Errorf("failed to update peer filter map: %w", err) + } + log.Infof("Programmed filter with PeerCIDR: %v", peerKey) + } err = objects.FilterMap.Update(key, val, cilium.UpdateAny) if err != nil { return fmt.Errorf("failed to update filter map: %w", err) @@ -59,24 +72,42 @@ func (f *Filter) ProgramFilter(objects *ebpf.BpfObjects) error { return nil } -func (f *Filter) getFilterKey(config *FilterConfig) (ebpf.BpfFilterKeyT, error) { +func (f *Filter) buildFilterKey(cidr, ipStr string) (ebpf.BpfFilterKeyT, error) { key := ebpf.BpfFilterKeyT{} + if cidr != "" { + ip, ipNet, err := net.ParseCIDR(cidr) + if err != nil { + return key, fmt.Errorf("failed to parse CIDR: %w", err) + } + if ip.To4() != nil { + copy(key.IpData[:], ip.To4()) + } else { + copy(key.IpData[:], ip.To16()) + } + pfLen, _ := ipNet.Mask.Size() + key.PrefixLen = uint32(pfLen) + } else if ipStr != "" { + ip := net.ParseIP(ipStr) + if ip.To4() != nil { + copy(key.IpData[:], ip.To4()) + key.PrefixLen = 32 + } else { + copy(key.IpData[:], ip.To16()) + key.PrefixLen = 128 + } + } + return key, nil +} + +func (f *Filter) getFilterKey(config *FilterConfig) (ebpf.BpfFilterKeyT, error) { if config.FilterIPCIDR == "" { config.FilterIPCIDR = "0.0.0.0/0" } - ip, ipNet, err := net.ParseCIDR(config.FilterIPCIDR) - if err != nil { - return key, fmt.Errorf("failed to parse FlowFilterIPCIDR: %w", err) - } - if ip.To4() != nil { - copy(key.IpData[:], ip.To4()) - } else { - copy(key.IpData[:], ip.To16()) - } - pfLen, _ := ipNet.Mask.Size() - key.PrefixLen = uint32(pfLen) + return f.buildFilterKey(config.FilterIPCIDR, "") +} - return key, nil +func (f *Filter) getPeerFilterKey(config *FilterConfig) (ebpf.BpfFilterKeyT, error) { + return f.buildFilterKey(config.FilterPeerCIDR, config.FilterPeerIP) } // nolint:cyclop @@ -123,15 +154,6 @@ func (f *Filter) getFilterValue(config *FilterConfig) (ebpf.BpfFilterValueT, err val.IcmpType = uint8(config.FilterIcmpType) val.IcmpCode = uint8(config.FilterIcmpCode) - if config.FilterPeerIP != "" { - ip := net.ParseIP(config.FilterPeerIP) - if ip.To4() != nil { - copy(val.Ip[:], ip.To4()) - } else { - copy(val.Ip[:], ip.To16()) - } - } - switch config.FilterTCPFlags { case "SYN": val.TcpFlags = ebpf.BpfTcpFlagsTSYN_FLAG @@ -164,6 +186,9 @@ func (f *Filter) getFilterValue(config *FilterConfig) (ebpf.BpfFilterValueT, err if config.FilterSample != 0 { val.Sample = config.FilterSample } + if config.FilterPeerCIDR != "" || config.FilterPeerIP != "" { + val.DoPeerCIDR_lookup = 1 + } return val, nil } diff --git a/pkg/tracer/flow_filter_test.go b/pkg/tracer/flow_filter_test.go index 830f5b840..d288ccdf5 100644 --- a/pkg/tracer/flow_filter_test.go +++ b/pkg/tracer/flow_filter_test.go @@ -175,3 +175,102 @@ func TestConvertFilterPortsToInstr(t *testing.T) { require.Equal(t, intstr.FromString(portsStr), result) }) } + +func TestBuildFilterKey(t *testing.T) { + tests := []struct { + name string + cidr string + ipStr string + wantKey ebpf.BpfFilterKeyT + wantError bool + }{ + { + name: "Valid CIDR IPv4", + cidr: "192.168.1.0/24", + ipStr: "", + wantKey: ebpf.BpfFilterKeyT{ + IpData: [16]byte{192, 168, 1, 0}, + PrefixLen: 24, + }, + wantError: false, + }, + { + name: "Valid default IPv4 CIDR", + cidr: "0.0.0.0/0", + ipStr: "", + wantKey: ebpf.BpfFilterKeyT{ + IpData: [16]byte{0}, + PrefixLen: 0, + }, + wantError: false, + }, + { + name: "Valid CIDR IPv6", + cidr: "2001:db8::/48", + ipStr: "", + wantKey: ebpf.BpfFilterKeyT{ + IpData: [16]byte{0x20, 0x01, 0x0d, 0xb8}, + PrefixLen: 48, + }, + wantError: false, + }, + { + name: "Valid default IPv6 CIDR", + cidr: "0::0/0", + ipStr: "", + wantKey: ebpf.BpfFilterKeyT{ + IpData: [16]byte{0}, + PrefixLen: 0, + }, + wantError: false, + }, + { + name: "Valid IP string IPv4", + cidr: "", + ipStr: "192.168.1.1", + wantKey: ebpf.BpfFilterKeyT{ + IpData: [16]byte{192, 168, 1, 1}, + PrefixLen: 32, + }, + wantError: false, + }, + { + name: "Valid IP string IPv6", + cidr: "", + ipStr: "2001:db8::1", + wantKey: ebpf.BpfFilterKeyT{ + IpData: [16]byte{0x20, 0x01, 0x0d, 0xb8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + PrefixLen: 128, + }, + wantError: false, + }, + { + name: "Invalid CIDR", + cidr: "invalidCIDR", + ipStr: "", + wantKey: ebpf.BpfFilterKeyT{}, + wantError: true, + }, + { + name: "Empty input", + cidr: "", + ipStr: "", + wantKey: ebpf.BpfFilterKeyT{}, + wantError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + filterObj := Filter{} + key, err := filterObj.buildFilterKey(tt.cidr, tt.ipStr) + + if tt.wantError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.wantKey, key) + } + }) + } +} diff --git a/pkg/tracer/tracer.go b/pkg/tracer/tracer.go index 75912c83a..da2cac8bc 100644 --- a/pkg/tracer/tracer.go +++ b/pkg/tracer/tracer.go @@ -36,6 +36,8 @@ const ( aggregatedFlowsMap = "aggregated_flows" additionalFlowMetrics = "additional_flow_metrics" dnsLatencyMap = "dns_flows" + flowFilterMap = "filter_map" + flowPeerFilterMap = "peer_filter_map" // constants defined in flows.c as "volatile const" constSampling = "sampling" constHasFilterSampling = "has_filter_sampling" @@ -135,7 +137,7 @@ func NewFlowFetcher(cfg *FlowFetcherConfig) (*FlowFetcher, error) { spec.Maps[additionalFlowMetrics].MaxEntries = uint32(cfg.CacheMaxSize) // remove pinning from all maps - maps2Name := []string{"aggregated_flows", "additional_flow_metrics", "direct_flows", "dns_flows", "filter_map", "global_counters", "packet_record"} + maps2Name := []string{"aggregated_flows", "additional_flow_metrics", "direct_flows", "dns_flows", "filter_map", "peer_filter_map", "global_counters", "packet_record"} for _, m := range maps2Name { spec.Maps[m].Pinning = 0 } @@ -168,6 +170,9 @@ func NewFlowFetcher(cfg *FlowFetcherConfig) (*FlowFetcher, error) { if filter != nil { enableFlowFiltering = 1 hasFilterSampling = filter.hasSampling() + } else { + spec.Maps[flowFilterMap].MaxEntries = 1 + spec.Maps[flowPeerFilterMap].MaxEntries = 1 } enableNetworkEventsMonitoring := 0 if cfg.EnableNetworkEventsMonitoring { @@ -318,6 +323,10 @@ func NewFlowFetcher(cfg *FlowFetcherConfig) (*FlowFetcher, error) { if err != nil { return nil, fmt.Errorf("failed to load %s: %w", mPath, err) } + objects.BpfMaps.PeerFilterMap, err = cilium.LoadPinnedMap(mPath, opts) + if err != nil { + return nil, fmt.Errorf("failed to load %s: %w", mPath, err) + } log.Infof("BPFManager mode: loading global counters pinned maps") mPath = path.Join(pinDir, "global_counters") objects.BpfMaps.GlobalCounters, err = cilium.LoadPinnedMap(mPath, opts) @@ -737,6 +746,9 @@ func (m *FlowFetcher) Close() error { if err := m.objects.FilterMap.Close(); err != nil { errs = append(errs, err) } + if err := m.objects.PeerFilterMap.Close(); err != nil { + errs = append(errs, err) + } if len(errs) == 0 { m.objects = nil } @@ -1072,6 +1084,7 @@ func kernelSpecificLoadAndAssign(oldKernel, rtKernel, supportNetworkEvents bool, AdditionalFlowMetrics: newObjects.AdditionalFlowMetrics, DnsFlows: newObjects.DnsFlows, FilterMap: newObjects.FilterMap, + PeerFilterMap: newObjects.PeerFilterMap, GlobalCounters: newObjects.GlobalCounters, }, } @@ -1123,6 +1136,7 @@ func kernelSpecificLoadAndAssign(oldKernel, rtKernel, supportNetworkEvents bool, AdditionalFlowMetrics: newObjects.AdditionalFlowMetrics, DnsFlows: newObjects.DnsFlows, FilterMap: newObjects.FilterMap, + PeerFilterMap: newObjects.PeerFilterMap, GlobalCounters: newObjects.GlobalCounters, }, } @@ -1174,6 +1188,7 @@ func kernelSpecificLoadAndAssign(oldKernel, rtKernel, supportNetworkEvents bool, AdditionalFlowMetrics: newObjects.AdditionalFlowMetrics, DnsFlows: newObjects.DnsFlows, FilterMap: newObjects.FilterMap, + PeerFilterMap: newObjects.PeerFilterMap, GlobalCounters: newObjects.GlobalCounters, }, } @@ -1226,6 +1241,7 @@ func kernelSpecificLoadAndAssign(oldKernel, rtKernel, supportNetworkEvents bool, AdditionalFlowMetrics: newObjects.AdditionalFlowMetrics, DnsFlows: newObjects.DnsFlows, FilterMap: newObjects.FilterMap, + PeerFilterMap: newObjects.PeerFilterMap, GlobalCounters: newObjects.GlobalCounters, }, } @@ -1339,8 +1355,9 @@ func NewPacketFetcher(cfg *FlowFetcherConfig) (*PacketFetcher, error) { RhNetworkEventsMonitoring: nil, }, BpfMaps: ebpf.BpfMaps{ - PacketRecord: newObjects.PacketRecord, - FilterMap: newObjects.FilterMap, + PacketRecord: newObjects.PacketRecord, + FilterMap: newObjects.FilterMap, + PeerFilterMap: newObjects.PeerFilterMap, }, }