From d17f5a4f54423539aaee87b1a18e10fcc352870e Mon Sep 17 00:00:00 2001 From: Christian Deacon Date: Thu, 7 May 2020 13:18:43 +0000 Subject: [PATCH] Added blocktime filter option and optimized code. --- README.md | 19 +- src/config.c | 12 + src/include/xdpfw.h | 2 + src/xdpfw_kern.c | 679 +++++++++++++++++++++++--------------------- src/xdpfw_loader.c | 1 + 5 files changed, 385 insertions(+), 328 deletions(-) diff --git a/README.md b/README.md index c35e8e8..0f93029 100644 --- a/README.md +++ b/README.md @@ -22,14 +22,17 @@ Config option `filters` is an array. Each filter includes the following options: * `enabled` => If true, this rule is enabled. * `action` => What action to perform against the packet if matched. 0 = Block. 1 = Allow. -* `srcip` => The source IP to match (e.g. 10.50.0.3). -* `dstip` => The destination IP to match (e.g. 10.50.0.4). -* `min_ttl` => The minimum TTL (time to live) the packet has to match. -* `max_ttl` => The maximum TTL (time to live) the packet has to match. -* `max_len` => The maximum packet length the packet has to match. This includes the entire frame (ethernet header, IP header, L4 header, and data). -* `min_len` => The minimum packet length the packet has to match. This includes the entire frame (ethernet header, IP header, L4 header, and data). -* `tos` => The TOS (type of service) the packet has to match. -* `payloadmatch` => The payload (L4 data) the packet has to match. The format is in hexadecimal and each byte is separated by a space. An example includes: `FF FF FF FF 59`. +* `srcip` => The source IP the packet must have to match (e.g. 10.50.0.3). +* `dstip` => The destination IP the packet must have to match (e.g. 10.50.0.4). +* `min_ttl` => The minimum TTL (time to live) the packet must have to match. +* `max_ttl` => The maximum TTL (time to live) the packet must have to match. +* `max_len` => The maximum packet length the packet must have to match. This includes the entire frame (ethernet header, IP header, L4 header, and data). +* `min_len` => The minimum packet length the packet must have to match. This includes the entire frame (ethernet header, IP header, L4 header, and data). +* `tos` => The TOS (type of service) the packet must have to match. +* `pps` => The maximum packets per second a source IP can send before matching. +* `bps` => The maximum amount of bytes per second a source IP can send before matching. +* `blocktime` => The maximum of time in seconds to block the source IP if the rule matches and the action is block (0). Default value is `1`. +* `payloadmatch` => The payload (L4 data) the packet must have to match. The format is in hexadecimal and each byte is separated by a space. An example includes: `FF FF FF FF 59`. #### TCP Options The config option `tcpopts` within a filter is an array including TCP options. This should only be one array per filter. Options include: diff --git a/src/config.c b/src/config.c index 1f0ff32..a9d0f3d 100644 --- a/src/config.c +++ b/src/config.c @@ -278,6 +278,18 @@ int ReadConfig(struct config_map *cfg) cfg->filters[i].do_bps = 1; } + // Block time (default 1). + int blocktime; + + if (config_setting_lookup_int(filter, "blocktime", &blocktime)) + { + cfg->filters[i].blockTime = blocktime; + } + else + { + cfg->filters[i].blockTime = 1; + } + // Payload match. const char *payload; diff --git a/src/include/xdpfw.h b/src/include/xdpfw.h index cb64390..74a9763 100644 --- a/src/include/xdpfw.h +++ b/src/include/xdpfw.h @@ -91,6 +91,8 @@ struct filter unsigned int do_bps : 1; uint64_t bps; + uint16_t blockTime; + uint8_t payloadMatch[MAX_PCKT_LENGTH]; uint16_t payloadLen; diff --git a/src/xdpfw_kern.c b/src/xdpfw_kern.c index 52e0d2c..d9151b7 100644 --- a/src/xdpfw_kern.c +++ b/src/xdpfw_kern.c @@ -66,6 +66,14 @@ struct bpf_map_def SEC("maps") ip_stats_map = .max_entries = MAX_TRACK_IPS }; +struct bpf_map_def SEC("maps") ip_blacklist_map = +{ + .type = BPF_MAP_TYPE_LRU_PERCPU_HASH, + .key_size = sizeof(uint32_t), + .value_size = sizeof(uint64_t), + .max_entries = MAX_TRACK_IPS +}; + SEC("xdp_prog") int xdp_prog_main(struct xdp_md *ctx) { @@ -82,6 +90,91 @@ int xdp_prog_main(struct xdp_md *ctx) return XDP_DROP; } + // Check Ethernet protocol. + if (unlikely(ethhdr->h_proto != htons(ETH_P_IP))) + { + return XDP_PASS; + } + + uint8_t matched = 0; + uint8_t action = 0; + uint64_t blocktime = 1; + + // Scan IP header. + struct iphdr *iph = data + sizeof(struct ethhdr); + + // Check if the IP header is valid. + if (unlikely(iph + 1 > (struct iphdr *)data_end)) + { + return XDP_DROP; + } + + // Check IP header protocols. + if (unlikely(iph->protocol != IPPROTO_UDP && iph->protocol != IPPROTO_TCP && iph->protocol != IPPROTO_ICMP)) + { + return XDP_DROP; + } + + uint64_t now = bpf_ktime_get_ns(); + + // Check blacklist map. + uint64_t *blocked = bpf_map_lookup_elem(&ip_blacklist_map, &iph->saddr); + + if (blocked != NULL && *blocked > 0) + { + #ifdef DEBUG + bpf_printk("Checking for blocked packet... Block time %" PRIu64 "\n", *blocked); + #endif + + if (now > *blocked) + { + // Remove element from map. + bpf_map_delete_elem(&ip_blacklist_map, &iph->saddr); + } + else + { + // They're still blocked. Drop the packet. + return XDP_DROP; + } + } + + // Update IP stats (PPS/BPS). + uint64_t pps = 0; + uint64_t bps = 0; + + struct xdpfw_ip_stats *ip_stats = bpf_map_lookup_elem(&ip_stats_map, &iph->saddr); + + if (ip_stats) + { + // Check for reset. + if ((now - ip_stats->tracking) > 1000000000) + { + ip_stats->pps = 0; + ip_stats->bps = 0; + ip_stats->tracking = now; + } + + ip_stats->pps++; + ip_stats->bps += ctx->data_end - ctx->data; + + pps = ip_stats->pps; + bps = ip_stats->bps; + } + else + { + // Create new entry. + struct xdpfw_ip_stats new; + + new.pps = 1; + new.bps = ctx->data_end - ctx->data; + new.tracking = now; + + pps = new.pps; + bps = new.bps; + + bpf_map_update_elem(&ip_stats_map, &iph->saddr, &new, BPF_ANY); + } + // Let's get the filters we need. struct filter *filter[MAX_FILTERS]; @@ -92,359 +185,305 @@ int xdp_prog_main(struct xdp_md *ctx) filter[i] = bpf_map_lookup_elem(&filters_map, &key); } - uint8_t matched = 0; - uint8_t action = 0; + struct tcphdr *tcph; + struct udphdr *udph; + struct icmphdr *icmph; + + uint16_t l4headerLen = 0; - // Check Ethernet protocol and ensure it's IP. - if (likely(ethhdr->h_proto == htons(ETH_P_IP))) + // Check protocol. + if (iph->protocol == IPPROTO_TCP) { - // Scan IP header. - struct iphdr *iph = data + sizeof(struct ethhdr); + // Scan TCP header. + tcph = (data + sizeof(struct ethhdr) + (iph->ihl * 4)); - // Check if the IP header is valid. - if (unlikely(iph + 1 > (struct iphdr *)data_end)) - { - return XDP_DROP; - } - - // Check IP header protocols. - if (unlikely(iph->protocol != IPPROTO_UDP && iph->protocol != IPPROTO_TCP && iph->protocol != IPPROTO_ICMP)) + // Check TCP header. + if (tcph + 1 > (struct tcphdr *)data_end) { return XDP_PASS; } - // Update IP stats (PPS/BPS). - uint64_t pps = 0; - uint64_t bps = 0; - uint64_t now = bpf_ktime_get_ns(); + // Set L4 Header length. + l4headerLen = sizeof(struct tcphdr); + } + else if (iph->protocol == IPPROTO_UDP) + { + // Scan UDP header. + udph = (data + sizeof(struct ethhdr) + (iph->ihl * 4)); - struct xdpfw_ip_stats *ip_stats = bpf_map_lookup_elem(&ip_stats_map, &iph->saddr); - - if (ip_stats) + // Check TCP header. + if (udph + 1 > (struct udphdr *)data_end) { - // Check for reset. - if ((now - ip_stats->tracking) > 1e9) - { - ip_stats->pps = 0; - ip_stats->bps = 0; - ip_stats->tracking = now; - } - - ip_stats->pps++; - ip_stats->bps += ctx->data_end - ctx->data; - - pps = ip_stats->pps; - bps = ip_stats->bps; - } - else - { - // Create new entry. - struct xdpfw_ip_stats new; - - new.pps = 1; - new.bps = ctx->data_end - ctx->data; - new.tracking = now; - - pps = new.pps; - bps = new.bps; - - bpf_map_update_elem(&ip_stats_map, &iph->saddr, &new, BPF_ANY); + return XDP_PASS; } - struct tcphdr *tcph; - struct udphdr *udph; - struct icmphdr *icmph; - - uint16_t l4headerLen = 0; + // Set L4 Header length. + l4headerLen = sizeof(struct udphdr); + } + else if (iph->protocol == IPPROTO_ICMP) + { + // Scan UDP header. + icmph = (data + sizeof(struct ethhdr) + (iph->ihl * 4)); - // Check protocol. - if (iph->protocol == IPPROTO_TCP) + // Check TCP header. + if (icmph + 1 > (struct icmphdr *)data_end) { - // Scan TCP header. - tcph = (data + sizeof(struct ethhdr) + (iph->ihl * 4)); - - // Check TCP header. - if (tcph + 1 > (struct tcphdr *)data_end) - { - return XDP_PASS; - } - - // Set L4 Header length. - l4headerLen = sizeof(struct tcphdr); + return XDP_PASS; } - else if (iph->protocol == IPPROTO_UDP) + + // Set L4 Header length. + l4headerLen = sizeof(struct icmphdr); + } + + for (uint8_t i = 0; i < MAX_FILTERS; i++) + { + // Check if ID is above 0 (if 0, it's an invalid rule). + if (!filter[i] || filter[i]->id < 1) { - // Scan UDP header. - udph = (data + sizeof(struct ethhdr) + (iph->ihl * 4)); - - // Check TCP header. - if (udph + 1 > (struct udphdr *)data_end) - { - return XDP_PASS; - } - - // Set L4 Header length. - l4headerLen = sizeof(struct udphdr); - } - else if (iph->protocol == IPPROTO_ICMP) - { - // Scan UDP header. - icmph = (data + sizeof(struct ethhdr) + (iph->ihl * 4)); - - // Check TCP header. - if (icmph + 1 > (struct icmphdr *)data_end) - { - return XDP_PASS; - } - - // Set L4 Header length. - l4headerLen = sizeof(struct icmphdr); - } - - for (uint8_t i = 0; i < MAX_FILTERS; i++) - { - // Check if ID is above 0 (if 0, it's an invalid rule). - if (!filter[i] || filter[i]->id < 1) - { - break; - } - - // Check if the rule is enabled. - if (!filter[i]->enabled) - { - continue; - } - - // Source address. - if (filter[i]->srcIP != 0 && iph->saddr != filter[i]->srcIP) - { - continue; - } - - // Destination address. - if (filter[i]->dstIP != 0 && iph->daddr != filter[i]->dstIP) - { - continue; - } - - // Max TTL length. - if (filter[i]->do_max_ttl && filter[i]->max_ttl > iph->ttl) - { - continue; - } - - // Min TTL length. - if (filter[i]->do_min_ttl && filter[i]->min_ttl < iph->ttl) - { - continue; - } - - // Max packet length. - if (filter[i]->do_max_len && filter[i]->max_len > (ntohs(iph->tot_len) + sizeof(struct ethhdr))) - { - continue; - } - - // Min packet length. - if (filter[i]->do_min_len && filter[i]->min_len < (ntohs(iph->tot_len) + sizeof(struct ethhdr))) - { - continue; - } - - // TOS. - if (filter[i]->do_tos && filter[i]->tos != iph->tos) - { - continue; - } - - // PPS. - if (filter[i]->do_pps && pps <= filter[i]->pps) - { - continue; - } - - // BPS. - if (filter[i]->do_bps && bps <= filter[i]->bps) - { - continue; - } - - // Payload match. - /* - if (filter[i]->payloadLen > 0) - { - uint8_t found = 1; - - // Initialize packet data. - for (uint16_t j = 0; j < MAX_PCKT_LENGTH; j++) - { - if ((j + 1) > filter[i]->payloadLen) - { - break; - } - - uint8_t *byte = (data + sizeof(struct ethhdr) + (iph->ihl * 4) + l4headerLen + j); - - if (byte + 1 > (uint8_t *)data_end) - { - break; - } - - if (*byte == filter[i]->payloadMatch[j]) - { - continue; - } - - found = 0; - - break; - } - - if (!found) - { - continue; - } - } - */ - - // Check layer 4 filters. - if (iph->protocol == IPPROTO_TCP && !filter[i]->tcpopts.enabled) - { - continue; - } - else if (iph->protocol == IPPROTO_UDP && !filter[i]->udpopts.enabled) - { - continue; - } - else if (iph->protocol == IPPROTO_ICMP && !filter[i]->icmpopts.enabled) - { - continue; - } - - // Do TCP options. - if (iph->protocol == IPPROTO_TCP && filter[i]->tcpopts.enabled) - { - // Source port. - if (filter[i]->tcpopts.do_sport && htons(filter[i]->tcpopts.sport) != tcph->source) - { - continue; - } - - // Destination port. - if (filter[i]->tcpopts.do_dport && htons(filter[i]->tcpopts.dport) != tcph->dest) - { - continue; - } - - // URG flag. - if (filter[i]->tcpopts.do_urg && filter[i]->tcpopts.urg != tcph->urg) - { - continue; - } - - // ACK flag. - if (filter[i]->tcpopts.do_ack && filter[i]->tcpopts.ack != tcph->ack) - { - continue; - } - - // RST flag. - if (filter[i]->tcpopts.do_rst && filter[i]->tcpopts.rst != tcph->rst) - { - continue; - } - - // PSH flag. - if (filter[i]->tcpopts.do_psh && filter[i]->tcpopts.psh != tcph->psh) - { - continue; - } - - // SYN flag. - if (filter[i]->tcpopts.do_syn && filter[i]->tcpopts.syn != tcph->syn) - { - continue; - } - - // FIN flag. - if (filter[i]->tcpopts.do_fin && filter[i]->tcpopts.fin != tcph->fin) - { - continue; - } - } - else if (iph->protocol == IPPROTO_UDP && filter[i]->udpopts.enabled) - { - // Source port. - if (filter[i]->udpopts.do_sport && htons(filter[i]->udpopts.sport) != udph->source) - { - continue; - } - - // Destination port. - if (filter[i]->udpopts.do_dport && htons(filter[i]->udpopts.dport) != udph->dest) - { - continue; - } - } - else if (iph->protocol == IPPROTO_ICMP && filter[i]->icmpopts.enabled) - { - // Code. - if (filter[i]->icmpopts.do_code && filter[i]->icmpopts.code != icmph->code) - { - continue; - } - - // Type. - if (filter[i]->icmpopts.do_type && filter[i]->icmpopts.type != icmph->type) - { - continue; - } - } - - // Matched. - #ifdef DEBUG - bpf_printk("Matched rule ID #%" PRIu8 ".\n", filter[i]->id); - #endif - - matched = 1; - action = filter[i]->action; - break; } - if (matched) + // Check if the rule is enabled. + if (!filter[i]->enabled) { - // Get stats map. - uint32_t key = 0; - struct xdpfw_stats *stats; + continue; + } - stats = bpf_map_lookup_elem(&stats_map, &key); + // Source address. + if (filter[i]->srcIP != 0 && iph->saddr != filter[i]->srcIP) + { + continue; + } - if (stats) + // Destination address. + if (filter[i]->dstIP != 0 && iph->daddr != filter[i]->dstIP) + { + continue; + } + + // Max TTL length. + if (filter[i]->do_max_ttl && filter[i]->max_ttl > iph->ttl) + { + continue; + } + + // Min TTL length. + if (filter[i]->do_min_ttl && filter[i]->min_ttl < iph->ttl) + { + continue; + } + + // Max packet length. + if (filter[i]->do_max_len && filter[i]->max_len > (ntohs(iph->tot_len) + sizeof(struct ethhdr))) + { + continue; + } + + // Min packet length. + if (filter[i]->do_min_len && filter[i]->min_len < (ntohs(iph->tot_len) + sizeof(struct ethhdr))) + { + continue; + } + + // TOS. + if (filter[i]->do_tos && filter[i]->tos != iph->tos) + { + continue; + } + + // PPS. + if (filter[i]->do_pps && pps <= filter[i]->pps) + { + continue; + } + + // BPS. + if (filter[i]->do_bps && bps <= filter[i]->bps) + { + continue; + } + + // Payload match. + /* + if (filter[i]->payloadLen > 0) + { + uint8_t found = 1; + + // Initialize packet data. + for (uint16_t j = 0; j < MAX_PCKT_LENGTH; j++) { - // Update stats map. - if (action == 0) + if ((j + 1) > filter[i]->payloadLen) { - stats->blocked++; - } - else - { - stats->allowed++; + break; } - key = 0; + uint8_t *byte = (data + sizeof(struct ethhdr) + (iph->ihl * 4) + l4headerLen + j); - bpf_map_update_elem(&stats_map, &key, stats, BPF_ANY); + if (byte + 1 > (uint8_t *)data_end) + { + break; + } + + if (*byte == filter[i]->payloadMatch[j]) + { + continue; + } + + found = 0; + + break; } - #ifdef DEBUG - //bpf_printk("Matched with protocol %" PRIu8 " and sAddr %" PRIu32 ".\n", iph->protocol, iph->saddr); - #endif + if (!found) + { + continue; + } } + */ + + // Check layer 4 filters. + if (iph->protocol == IPPROTO_TCP && !filter[i]->tcpopts.enabled) + { + continue; + } + else if (iph->protocol == IPPROTO_UDP && !filter[i]->udpopts.enabled) + { + continue; + } + else if (iph->protocol == IPPROTO_ICMP && !filter[i]->icmpopts.enabled) + { + continue; + } + + // Do TCP options. + if (iph->protocol == IPPROTO_TCP && filter[i]->tcpopts.enabled) + { + // Source port. + if (filter[i]->tcpopts.do_sport && htons(filter[i]->tcpopts.sport) != tcph->source) + { + continue; + } + + // Destination port. + if (filter[i]->tcpopts.do_dport && htons(filter[i]->tcpopts.dport) != tcph->dest) + { + continue; + } + + // URG flag. + if (filter[i]->tcpopts.do_urg && filter[i]->tcpopts.urg != tcph->urg) + { + continue; + } + + // ACK flag. + if (filter[i]->tcpopts.do_ack && filter[i]->tcpopts.ack != tcph->ack) + { + continue; + } + + // RST flag. + if (filter[i]->tcpopts.do_rst && filter[i]->tcpopts.rst != tcph->rst) + { + continue; + } + + // PSH flag. + if (filter[i]->tcpopts.do_psh && filter[i]->tcpopts.psh != tcph->psh) + { + continue; + } + + // SYN flag. + if (filter[i]->tcpopts.do_syn && filter[i]->tcpopts.syn != tcph->syn) + { + continue; + } + + // FIN flag. + if (filter[i]->tcpopts.do_fin && filter[i]->tcpopts.fin != tcph->fin) + { + continue; + } + } + else if (iph->protocol == IPPROTO_UDP && filter[i]->udpopts.enabled) + { + // Source port. + if (filter[i]->udpopts.do_sport && htons(filter[i]->udpopts.sport) != udph->source) + { + continue; + } + + // Destination port. + if (filter[i]->udpopts.do_dport && htons(filter[i]->udpopts.dport) != udph->dest) + { + continue; + } + } + else if (iph->protocol == IPPROTO_ICMP && filter[i]->icmpopts.enabled) + { + // Code. + if (filter[i]->icmpopts.do_code && filter[i]->icmpopts.code != icmph->code) + { + continue; + } + + // Type. + if (filter[i]->icmpopts.do_type && filter[i]->icmpopts.type != icmph->type) + { + continue; + } + } + + // Matched. + #ifdef DEBUG + bpf_printk("Matched rule ID #%" PRIu8 ".\n", filter[i]->id); + #endif + + matched = 1; + action = filter[i]->action; + blocktime = filter[i]->blockTime; + + break; + } + + if (matched) + { + // Get stats map. + uint32_t key = 0; + struct xdpfw_stats *stats; + + stats = bpf_map_lookup_elem(&stats_map, &key); + + if (stats) + { + // Update stats map. + if (action == 0) + { + stats->blocked++; + } + else + { + stats->allowed++; + } + + key = 0; + + bpf_map_update_elem(&stats_map, &key, stats, BPF_ANY); + } + + #ifdef DEBUG + //bpf_printk("Matched with protocol %" PRIu8 " and sAddr %" PRIu32 ".\n", iph->protocol, iph->saddr); + #endif } if (matched && action == 0) { + // Before dropping, update the blacklist map. + uint64_t newTime = now + (blocktime * 1000000000); + + bpf_map_update_elem(&ip_blacklist_map, &iph->saddr, &newTime, BPF_ANY); + return XDP_DROP; } diff --git a/src/xdpfw_loader.c b/src/xdpfw_loader.c index 6155228..d92ca26 100644 --- a/src/xdpfw_loader.c +++ b/src/xdpfw_loader.c @@ -344,6 +344,7 @@ int main(int argc, char *argv[]) fprintf(stdout, "TOS => %" PRIu8 "\n", conf->filters[i].tos); fprintf(stdout, "PPS => %" PRIu64 "\n", conf->filters[i].pps); fprintf(stdout, "BPS => %" PRIu64 "\n\n", conf->filters[i].bps); + fprintf(stdout, "Block Time => %" PRIu16 "\n\n", conf->filters[i].blockTime); // TCP Options. fprintf(stdout, "TCP Enabled => %" PRIu8 "\n", conf->filters[i].tcpopts.enabled);