Skip to content

Commit

Permalink
NETOBSERV-1617: reuse flow filter capability with pcap feature (#359)
Browse files Browse the repository at this point in the history
* NETOBSERV-1617: reuse flow filter capability with pcap feature

rename all flow filters to just filter to allow reuse the same configs
modify pca to use filter config
update userspace, examples and doc

Signed-off-by: Mohamed Mahmoud <mmahmoud@redhat.com>

* Allow sampling configs to be applied for PCA

Signed-off-by: Mohamed Mahmoud <mmahmoud@redhat.com>

---------

Signed-off-by: Mohamed Mahmoud <mmahmoud@redhat.com>
  • Loading branch information
msherif1234 authored Jun 28, 2024
1 parent 58e5d37 commit 8a6d8ce
Show file tree
Hide file tree
Showing 21 changed files with 265 additions and 283 deletions.
3 changes: 1 addition & 2 deletions bpf/configs.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
volatile const u32 sampling = 0;
volatile const u8 trace_messages = 0;
volatile const u8 enable_rtt = 0;
volatile const u16 pca_port = 0;
volatile const u8 pca_proto = 0;
volatile const u8 enable_pca = 0;
volatile const u8 enable_dns_tracking = 0;
volatile const u8 enable_flows_filtering = 0;
#endif //__CONFIGS_H__
95 changes: 37 additions & 58 deletions bpf/pca.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,93 +36,72 @@ static int attach_packet_payload(void *data, void *data_end, struct __sk_buff *s
return TC_ACT_UNSPEC;
}

static inline bool validate_pca_filter(u8 ipproto, void *ipheaderend, void *data_end) {
// If filters: pca_proto and pca_port are not specified, export packet
if (pca_proto == 0 && pca_port == 0)
return true;

//Only export packets with protocol set by ENV var PCA_FILTER
u16 sourcePort, destPort;
if (ipproto != pca_proto) {
return false;
}
static inline bool validate_pca_filter(struct __sk_buff *skb, direction dir) {
pkt_info pkt;
__builtin_memset(&pkt, 0, sizeof(pkt));
flow_id id;
__builtin_memset(&id, 0, sizeof(id));

if (ipproto == IPPROTO_TCP) {
struct tcphdr *tcp_header = ipheaderend;
if ((void *)tcp_header + sizeof(*tcp_header) > data_end) {
return false;
}
sourcePort = tcp_header->source;
destPort = tcp_header->dest;
} else if (ipproto == IPPROTO_UDP) {
struct udphdr *udp_header = ipheaderend;
if ((void *)udp_header + sizeof(*udp_header) > data_end) {
return false;
}
sourcePort = udp_header->source;
destPort = udp_header->dest;
} else if (ipproto == IPPROTO_SCTP) {
struct sctphdr *sctp_header = ipheaderend;
if ((void *)sctp_header + sizeof(*sctp_header) > data_end) {
return false;
}
sourcePort = sctp_header->source;
destPort = sctp_header->dest;
} else {
return false;
}
u16 pca_port_end = bpf_htons(pca_port);
if (sourcePort == pca_port_end || destPort == pca_port_end) {
return true;
}
return false;
}
pkt.id = &id;

static inline int export_packet_payload(struct __sk_buff *skb) {
void *data_end = (void *)(long)skb->data_end;
void *data = (void *)(long)skb->data;
struct ethhdr *eth = data;
struct iphdr *ip;
struct ethhdr *eth = (struct ethhdr *)data;

if ((void *)eth + sizeof(*eth) > data_end) {
return TC_ACT_UNSPEC;
if (fill_ethhdr(eth, data_end, &pkt) == DISCARD) {
return false;
}

// Only IPv4 and IPv6 packets captured
u16 ethType = bpf_ntohs(eth->h_proto);
if (ethType != ETH_P_IP && ethType != ETH_P_IPV6) {
return TC_ACT_UNSPEC;
//Set extra fields
id.if_index = skb->ifindex;
id.direction = dir;

// check if this packet need to be filtered if filtering feature is enabled
bool skip = check_and_do_flow_filtering(&id);
if (skip) {
return false;
}

ip = data + sizeof(*eth);
if ((void *)ip + sizeof(*ip) > data_end) {
return TC_ACT_UNSPEC;
return true;
}

static inline int export_packet_payload(struct __sk_buff *skb, direction dir) {
// If sampling is defined, will only parse 1 out of "sampling" flows
if (sampling > 1 && (bpf_get_prandom_u32() % sampling) != 0) {
return 0;
}

if (validate_pca_filter(ip->protocol, (void *)ip + sizeof(*ip), data_end)) {
void *data_end = (void *)(long)skb->data_end;
void *data = (void *)(long)skb->data;

if (validate_pca_filter(skb, dir)) {
return attach_packet_payload(data, data_end, skb);
}
return TC_ACT_UNSPEC;
return 0;
}

SEC("tc_pca_ingress")
int tc_ingress_pca_parse(struct __sk_buff *skb) {
return export_packet_payload(skb);
export_packet_payload(skb, INGRESS);
return TC_ACT_OK;
}

SEC("tc_pca_egress")
int tc_egress_pca_parse(struct __sk_buff *skb) {
return export_packet_payload(skb);
export_packet_payload(skb, EGRESS);
return TC_ACT_OK;
}

SEC("tcx_pca_ingress")
int tcx_ingress_pca_parse(struct __sk_buff *skb) {
return export_packet_payload(skb);
export_packet_payload(skb, INGRESS);
return TCX_NEXT;
}

SEC("tcx_pca_egress")
int tcx_egress_pca_parse(struct __sk_buff *skb) {
return export_packet_payload(skb);
export_packet_payload(skb, EGRESS);
return TCX_NEXT;
}

#endif /* __PCA_H__ */
6 changes: 3 additions & 3 deletions bpf/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,9 @@ typedef struct dns_flow_id_t {
// Enum to define global counters keys and share it with userspace
typedef enum global_counters_key_t {
HASHMAP_FLOWS_DROPPED_KEY = 0,
FILTER_FLOWS_REJECT_KEY = 1,
FILTER_FLOWS_ACCEPT_KEY = 2,
FILTER_FLOWS_NOMATCH_KEY = 3,
FILTER_REJECT_KEY = 1,
FILTER_ACCEPT_KEY = 2,
FILTER_NOMATCH_KEY = 3,
MAX_DROPPED_FLOWS_KEY = 4,
} global_counters_key;

Expand Down
6 changes: 3 additions & 3 deletions bpf/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -284,14 +284,14 @@ static inline long pkt_drop_lookup_and_update_flow(struct sk_buff *skb, flow_id
*/
static inline bool check_and_do_flow_filtering(flow_id *id) {
// check if this packet need to be filtered if filtering feature is enabled
if (enable_flows_filtering) {
if (enable_flows_filtering || enable_pca) {
filter_action action = ACCEPT;
u32 *filter_counter_p = NULL;
u32 initVal = 1, key = 0;
if (is_flow_filtered(id, &action) != 0 && action != MAX_FILTER_ACTIONS) {
// we have matching rules follow through the actions to decide if we should accept or reject the flow
// and update global counter for both cases
u32 reject_key = FILTER_FLOWS_REJECT_KEY, accept_key = FILTER_FLOWS_ACCEPT_KEY;
u32 reject_key = FILTER_REJECT_KEY, accept_key = FILTER_ACCEPT_KEY;
bool skip = false;

switch (action) {
Expand Down Expand Up @@ -319,7 +319,7 @@ static inline bool check_and_do_flow_filtering(flow_id *id) {
}
} else {
// we have no matching rules so we update global counter for flows that are not matched by any rule
key = FILTER_FLOWS_NOMATCH_KEY;
key = FILTER_NOMATCH_KEY;
filter_counter_p = bpf_map_lookup_elem(&global_counters, &key);
if (!filter_counter_p) {
bpf_map_update_elem(&global_counters, &key, &initVal, BPF_ANY);
Expand Down
3 changes: 0 additions & 3 deletions cmd/netobserv-ebpf-agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,6 @@ func main() {
logrus.WithField("configuration", fmt.Sprintf("%#v", config)).Debugf("configuration loaded")

if config.EnablePCA {
if config.PCAFilters == "" {
logrus.Info("[PCA] NetObserv eBPF Agent instantiated without filters to identify packets. All packets will be captured. This might cause reduced performance.")
}
packetsAgent, err := agent.PacketsAgent(&config)
if err != nil {
logrus.WithError(err).Fatal("[PCA] can't instantiate NetObserv eBPF Agent")
Expand Down
120 changes: 60 additions & 60 deletions docs/flow_filtering.md
Original file line number Diff line number Diff line change
@@ -1,80 +1,80 @@
# eBPF Flow Rule Based Filtering
# eBPF Rule Based Filtering

## Introduction

Flow rule-base filtering is a method to control the flow of packets cached in the eBPF flows table based on certain configuration
Rule-base filtering is a method to control the flow of packets cached in the eBPF flows table based on certain configuration

## Flow filter rule configuration
## Filter rule configuration

The Flow filter rule consists of two parts mandatory and optional parameters.
The filter rule consists of two parts mandatory and optional parameters.

### Mandatory parameters

- `FLOW_FILTER_IP_CIDR` - IP address and CIDR mask for the flow filter rule, supports IPv4 and IPv6 address format.
- `FILTER_IP_CIDR` - IP address and CIDR mask for the flow filter rule, supports IPv4 and IPv6 address format.
If wanted to match against any IP, user can use `0.0.0.0/0` or `::/0` for IPv4 and IPv6 respectively.
- `FLOW_FILTER_ACTION` - Action to be taken for the flow filter rule. Possible values are `Accept` and `Reject`.
- For the matching rule with `Accept` action this flow will be allowed to be cached in eBPF table, with updated global metric `FlowFilterAcceptCounter`.
- For the matching rule with `Reject` action this flow will not be cached in eBPF table, with updated global metric `FlowFilterRejectCounter`.
- `FILTER_ACTION` - Action to be taken for the flow filter rule. Possible values are `Accept` and `Reject`.
- For the matching rule with `Accept` action this flow will be allowed to be cached in eBPF table, with updated global metric `FilterAcceptCounter`.
- For the matching rule with `Reject` action this flow will not be cached in eBPF table, with updated global metric `FilterRejectCounter`.
- If the rule is not matched, based on the configured action if its `Accept` the flow will not be cached in eBPF table,
if the action is `Reject` then the flow will be cached in the eBPF table and a global metric `FlowFilterNoMatchCounter` will be updated.
if the action is `Reject` then the flow will be cached in the eBPF table and a global metric `FilterNoMatchCounter` will be updated.

### Optional parameters

- `FLOW_FILTER_DIRECTION` - Direction of the flow filter rule. Possible values are `Ingress` and `Egress`.
- `FLOW_FILTER_PROTOCOL` - Protocol of the flow filter rule. Possible values are `TCP`, `UDP`, `SCTP`, `ICMP`, `ICMPv6`.
- `FLOW_FILTER_SOURCE_PORT` - Single Source port of the flow filter rule.
- `FLOW_FILTER_SOURCE_PORT_RANGE` - Source port range of the flow filter rule. using "80-100" format.
- `FLOW_FILTER_DESTINATION_PORT` - Single Destination port of the flow filter rule.
- `FLOW_FILTER_DESTINATION_PORT_RANGE` - Destination port range of the flow filter rule. using "80-100" format.
- `FLOW_FILTER_PORT` - Single L4 port of the flow filter rule, can be either source or destination port.
- `FLOW_FILTER_PORT_RANGE` - L4 port range of the flow filter rule. using "80-100" format can be either source or destination ports range.
- `FLOW_FILTER_ICMP_TYPE` - ICMP type of the flow filter rule.
- `FLOW_FILTER_ICMP_CODE` - ICMP code of the flow filter rule.
- `FLOW_FILTER_PEER_IP` - Specific Peer IP address of the flow filter rule.
- `FILTER_DIRECTION` - Direction of the flow filter rule. Possible values are `Ingress` and `Egress`.
- `FILTER_PROTOCOL` - Protocol of the flow filter rule. Possible values are `TCP`, `UDP`, `SCTP`, `ICMP`, `ICMPv6`.
- `FILTER_SOURCE_PORT` - Single Source port of the flow filter rule.
- `FILTER_SOURCE_PORT_RANGE` - Source port range of the flow filter rule. using "80-100" format.
- `FILTER_DESTINATION_PORT` - Single Destination port of the flow filter rule.
- `FILTER_DESTINATION_PORT_RANGE` - Destination port range of the flow filter rule. using "80-100" format.
- `FILTER_PORT` - Single L4 port of the flow filter rule, can be either source or destination port.
- `FILTER_PORT_RANGE` - L4 port range of the flow filter rule. using "80-100" format can be either source or destination ports range.
- `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.

Note:
- for L4 ports configuration you can use either single port config options or the range but not both.
- use either specific src and/or dst ports or the generic port config that works for both direction.
- for L4 ports configuration, you can use either single port config options or the range but not both.
- use either specific src and/or dst ports or the generic port config that works for both directions.

## How does Flow Filtering work

### Flow Filter and CIDR Matching
### Filter and CIDR Matching

The flow filter examines incoming or outgoing packets and attempts to match the source IP address or the destination IP address
of each packet against a CIDR range specified in the `FLOW_FILTER_IP_CIDR` parameter.
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 `FLOW_FILTER_PEER_IP`
### Matching Specific Endpoints with `FILTER_PEER_IP`

The `FLOW_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.
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 `FLOW_FILTER_PEER_IP` is used to match against the destination IP address of the packet.
- In ingress traffic filtering, the `FILTER_PEER_IP` is used to match against the destination IP 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 `FLOW_FILTER_PEER_IP` is used to match against the source IP address of the packet.
- 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 `FLOW_FILTER_PEER_IP`.
specified by `FILTER_PEER_IP`.

### How to fine tune the flow filter rule configuration?
### How to fine-tune the flow filter rule configuration?

We have many configuration options available for the flow filter rule configuration, but we can use them in combination to achieve the desired
flow filter rule configuration. Let's use some examples to understand how to fine tune the flow filter rule configuration.
flow filter rule configuration. Let's use some examples to understand how to fine-tune the flow filter rule configuration.

#### Use-case 1:

Filter k8s service traffic to specific POD IP endpoint.
For example if we wanted to filter in incoming k8s service traffic coming from source `172.210.150.100` for `SCTP` protocol,
on specific dport range 80-100, and targeting specific POD IP endpoint at `10.10.10.10` we can use the following configuration:
For example, if we wanted to filter in incoming k8s service traffic coming from source `172.210.150.100` for `SCTP` protocol,
on specific dport range 80100, and targeting specific POD IP endpoint at `10.10.10.10` we can use the following configuration:

```shell
FLOW_FILTER_IP_CIDR=172.210.150.1/24
FLOW_FILTER_ACTION=Accept
FLOW_FILTER_PROTOCOL=SCTP
FLOW_FILTER_DIRECTION=Ingress
FLOW_FILTER_DESTINATION_PORT_RANGE=80-100
FLOW_FILTER_PEER_IP=10.10.10.10
FILTER_IP_CIDR=172.210.150.1/24
FILTER_ACTION=Accept
FILTER_PROTOCOL=SCTP
FILTER_DIRECTION=Ingress
FILTER_DESTINATION_PORT_RANGE=80-100
FILTER_PEER_IP=10.10.10.10
```

#### Use-case 2:
Expand All @@ -83,12 +83,12 @@ Users wanted to see flows after EgressIP feature is configured with EgressIP `19
to any cluster's outside addresses (destinations is unknown or don't care), so they can use the following configuration:

```shell
FLOW_FILTER_IP_CIDR=0.0.0.0/0
FLOW_FILTER_ACTION=Accept
FLOW_FILTER_PROTOCOL=TCP
FLOW_FILTER_DIRECTION=Egress
FLOW_FILTER_SOURCE_PORT=100
FLOW_FILTER_PEER_IP=192.168.127.12
FILTER_IP_CIDR=0.0.0.0/0
FILTER_ACTION=Accept
FILTER_PROTOCOL=TCP
FILTER_DIRECTION=Egress
FILTER_SOURCE_PORT=100
FILTER_PEER_IP=192.168.127.12
```

#### Use-case 3:
Expand All @@ -97,22 +97,22 @@ OpenShift ovn kubernetes CNI uses `169.254.169.1-4` as masquerade addresses when
I am not interested in capturing any those packets, so I can use the following configuration:

```shell
FLOW_FILTER_IP_CIDR=169.254.169.1/24
FLOW_FILTER_ACTION=Reject
FLOW_FILTER_DIRECTION=Ingress
FILTER_IP_CIDR=169.254.169.1/24
FILTER_ACTION=Reject
FILTER_DIRECTION=Ingress
```

#### Use-case 4:

We have case where ping traffic is going between PODA `1.1.1.10` to PODB in different node `1.2.1.10` for that we can use the following configuration:
We have a case where ping traffic is going between PODA `1.1.1.10` to PODB in different node `1.2.1.10` for that we can use the following configuration:

```shell
FLOW_FILTER_IP_CIDR=1.1.1.10/32
FLOW_FILTER_ACTION=Accept
FLOW_FILTER_DIRECTION=Ingress
FLOW_FILTER_PROTOCOL=ICMP
FLOW_FILTER_PEER_IP=1.2.1.10
FLOW_FILTER_ICMP_TYPE=8
FILTER_IP_CIDR=1.1.1.10/32
FILTER_ACTION=Accept
FILTER_DIRECTION=Ingress
FILTER_PROTOCOL=ICMP
FILTER_PEER_IP=1.2.1.10
FILTER_ICMP_TYPE=8
```

#### Use-case 5:
Expand All @@ -121,9 +121,9 @@ We wanted to filter in `curl` request and response for TCP flow going from PODA
for that we can use the following configuration:

```shell
FLOW_FILTER_IP_CIDR=1.1.1.10/32
FLOW_FILTER_ACTION=Accept
FLOW_FILTER_PROTOCOL=TCP
FLOW_FILTER_PORT=80
FLOW_FILTER_PEER_IP=1.2.1.10
FILTER_IP_CIDR=1.1.1.10/32
FILTER_ACTION=Accept
FILTER_PROTOCOL=TCP
FILTER_PORT=80
FILTER_PEER_IP=1.2.1.10
```
2 changes: 1 addition & 1 deletion examples/packetcapture-dump/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Start the packetcapture-client using: (in a secondary shell)

Start the agent using:
```bash
sudo TARGET_HOST=localhost TARGET_PORT=9990 ENABLE_PCA=true PCA_FILTER=tcp,22 ./bin/netobserv-ebpf-agent
sudo TARGET_HOST=localhost TARGET_PORT=9990 ENABLE_PCA="true" FILTER_IP_CIDR="0.0.0.0/0" FILTER_PROTOCOL="TCP" FILTER_PORT=22 FILTER_ACTION="Accept" ./bin/netobserv-ebpf-agent
```

You should see output such as:
Expand Down
Loading

0 comments on commit 8a6d8ce

Please sign in to comment.