From 12d0a8702cf8834c0feef1ee239741d40d680716 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Fri, 1 Jun 2012 14:20:42 +0200 Subject: [PATCH 01/11] xt_psd: consider protocol when searching port list If we saw a TCP packet on port X, and we receive a UDP packet from the same host to port X, we counted this as "port X", and did not see this as a new packet. Change compare to also consider protocol number and move it to a helper to de-bloat the overlay large match function. This change makes psd more aggressive with mixed TCP/UDP traffic. --- extensions/xt_psd.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/extensions/xt_psd.c b/extensions/xt_psd.c index b67b64e..6724439 100644 --- a/extensions/xt_psd.c +++ b/extensions/xt_psd.c @@ -106,6 +106,19 @@ static inline int hashfunc(struct in_addr addr) return hash & (HASH_SIZE - 1); } +static bool port_in_list(struct host *host, uint8_t proto, uint16_t port) +{ + unsigned int i; + + for (i = 0; i < host->count; ++i) { + if (host->ports[i].proto != proto) + continue; + if (host->ports[i].number == port) + return true; + } + return false; +} + static bool xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) { @@ -121,7 +134,7 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) u_int8_t proto; unsigned long now; struct host *curr, *last, **head; - int hash, index, count; + int hash, count; /* Parameters from userspace */ const struct xt_psd_info *psdinfo = match->matchinfo; @@ -182,14 +195,8 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) if (now - curr->timestamp <= (psdinfo->delay_threshold*HZ)/100 && time_after_eq(now, curr->timestamp)) { - /* Just update the appropriate list entry if we've seen this port already */ - for (index = 0; index < curr->count; index++) { - if (curr->ports[index].number == dest_port) { - curr->ports[index].proto = proto; - goto out_no_match; - } - } - + if (port_in_list(curr, proto, dest_port)) + goto out_no_match; /* TCP/ACK and/or TCP/RST to a new port? This could be an outgoing connection. */ if (proto == IPPROTO_TCP && (tcph->ack || tcph->rst)) goto out_no_match; From 2f18ab31ec72cf5f8b6d33c9567f55d15d0213bd Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Thu, 14 Jun 2012 12:16:05 +0200 Subject: [PATCH 02/11] xt_psd: move parts of main match function to helpers The match function is way too large, start to split this into smaller chunks. --- extensions/xt_psd.c | 55 ++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/extensions/xt_psd.c b/extensions/xt_psd.c index 6724439..1f4fd4f 100644 --- a/extensions/xt_psd.c +++ b/extensions/xt_psd.c @@ -119,6 +119,36 @@ static bool port_in_list(struct host *host, uint8_t proto, uint16_t port) return false; } +static uint16_t get_port_weight(const struct xt_psd_info *psd, __be16 port) +{ + return ntohs(port) < 1024 ? psd->lo_ports_weight : psd->hi_ports_weight; +} + +static bool +is_portscan(struct host *host, const struct xt_psd_info *psdinfo, + uint8_t proto, __be16 dest_port) +{ + host->timestamp = jiffies; + + if (host->weight >= psdinfo->weight_threshold) /* already matched */ + return true; + + /* Update the total weight */ + host->weight += get_port_weight(psdinfo, dest_port); + + /* Got enough destination ports to decide that this is a scan? */ + if (host->weight >= psdinfo->weight_threshold) + return true; + + /* Remember the new port */ + if (host->count < ARRAY_SIZE(host->ports)) { + host->ports[host->count].number = dest_port; + host->ports[host->count].proto = proto; + host->count++; + } + return false; +} + static bool xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) { @@ -201,31 +231,10 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) if (proto == IPPROTO_TCP && (tcph->ack || tcph->rst)) goto out_no_match; - /* Packet to a new port, and not TCP/ACK: update the timestamp */ - curr->timestamp = now; - - /* Matched this scan already? Then Leave. */ - if (curr->weight >= psdinfo->weight_threshold) + if (is_portscan(curr, psdinfo, proto, dest_port)) goto out_match; - - /* Update the total weight */ - curr->weight += (ntohs(dest_port) < 1024) ? - psdinfo->lo_ports_weight : psdinfo->hi_ports_weight; - - /* Got enough destination ports to decide that this is a scan? */ - if (curr->weight >= psdinfo->weight_threshold) - goto out_match; - - /* Remember the new port */ - if (curr->count < ARRAY_SIZE(curr->ports)) { - curr->ports[curr->count].number = dest_port; - curr->ports[curr->count].proto = proto; - curr->count++; - } - goto out_no_match; } - /* We know this address, but the entry is outdated. Mark it unused, and * remove from the hash table. We'll allocate a new entry instead since * this one might get re-used too soon. */ @@ -288,7 +297,7 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) curr->dest_addr.s_addr = iph->daddr; curr->src_port = src_port; curr->count = 1; - curr->weight = (ntohs(dest_port) < 1024) ? psdinfo->lo_ports_weight : psdinfo->hi_ports_weight; + curr->weight = get_port_weight(psdinfo, dest_port); curr->ports[0].number = dest_port; curr->ports[0].proto = proto; From 57d25f22f19f7af6599dec9ec2e3239462feaa72 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Thu, 16 Aug 2012 12:01:09 +0200 Subject: [PATCH 03/11] xt_psd: avoid if (c=h) do {..} while (c = c->next) It is aquivalent to c=h; while (c) { ..; c = c->next; } which is a bit easier to read. --- extensions/xt_psd.c | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/extensions/xt_psd.c b/extensions/xt_psd.c index 1f4fd4f..197dbf9 100644 --- a/extensions/xt_psd.c +++ b/extensions/xt_psd.c @@ -163,8 +163,8 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) u_int16_t src_port,dest_port; u_int8_t proto; unsigned long now; - struct host *curr, *last, **head; - int hash, count; + struct host *curr, *last = NULL, **head; + int hash, count = 0; /* Parameters from userspace */ const struct xt_psd_info *psdinfo = match->matchinfo; @@ -205,20 +205,21 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) } now = jiffies; + hash = hashfunc(addr); + head = &state.hash[hash]; spin_lock(&state.lock); /* Do we know this source address already? */ - count = 0; - last = NULL; - if ((curr = *(head = &state.hash[hash = hashfunc(addr)])) != NULL) - do { - if (curr->src_addr.s_addr == addr.s_addr) - break; - count++; - if (curr->next != NULL) - last = curr; - } while ((curr = curr->next) != NULL); + curr = *head; + while (curr != NULL) { + if (curr->src_addr.s_addr == addr.s_addr) + break; + count++; + if (curr->next != NULL) + last = curr; + curr = curr->next; + } if (curr != NULL) { /* We know this address, and the entry isn't too old. Update it. */ @@ -266,12 +267,13 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) else head = &last; last = NULL; - if ((curr = *head) != NULL) - do { - if (curr == &state.list[state.index]) - break; - last = curr; - } while ((curr = curr->next) != NULL); + curr = *head; + while (curr != NULL) { + if (curr == &state.list[state.index]) + break; + last = curr; + curr = curr->next; + } /* Then, remove it */ if (curr != NULL) { From 093f3b0a975d2be97ba33426ceead8d205bca2f6 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Thu, 16 Aug 2012 13:05:05 +0200 Subject: [PATCH 04/11] xt_psd: move match functionality to helpers Reduce line count and to allow code reuse when IPv6 support will be introduced. --- extensions/xt_psd.c | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/extensions/xt_psd.c b/extensions/xt_psd.c index 197dbf9..dbe422b 100644 --- a/extensions/xt_psd.c +++ b/extensions/xt_psd.c @@ -149,6 +149,29 @@ is_portscan(struct host *host, const struct xt_psd_info *psdinfo, return false; } +static struct host *host_get_next(struct host *h, struct host **last) +{ + if (h->next != NULL) + *last = h; + return h->next; +} + +static void ht_unlink(struct host **head, struct host *last) +{ + if (last != NULL) + last->next = last->next->next; + else if (*head != NULL) + *head = (*head)->next; +} + +static bool +entry_is_recent(const struct host *h, unsigned long delay_threshold, + unsigned long now) +{ + return now - h->timestamp <= (delay_threshold * HZ) / 100 && + time_after_eq(now, h->timestamp); +} + static bool xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) { @@ -216,16 +239,12 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) if (curr->src_addr.s_addr == addr.s_addr) break; count++; - if (curr->next != NULL) - last = curr; - curr = curr->next; + curr = host_get_next(curr, &last); } if (curr != NULL) { /* We know this address, and the entry isn't too old. Update it. */ - if (now - curr->timestamp <= (psdinfo->delay_threshold*HZ)/100 && - time_after_eq(now, curr->timestamp)) { - + if (entry_is_recent(curr, psdinfo->delay_threshold, now)) { if (port_in_list(curr, proto, dest_port)) goto out_no_match; /* TCP/ACK and/or TCP/RST to a new port? This could be an outgoing connection. */ @@ -240,10 +259,7 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) * remove from the hash table. We'll allocate a new entry instead since * this one might get re-used too soon. */ curr->src_addr.s_addr = 0; - if (last != NULL) - last->next = last->next->next; - else if (*head != NULL) - *head = (*head)->next; + ht_unlink(head, last); last = NULL; } From 61d2be172d93da4c2312fdcef1dfc46b07beedbf Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Thu, 16 Aug 2012 13:59:41 +0200 Subject: [PATCH 05/11] xt_psd: remove unneeded variables, make hash unsigned - dest port and dest address were only written, never read - struct inaddr isn't needed either, just look at iph->saddr --- extensions/xt_psd.c | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/extensions/xt_psd.c b/extensions/xt_psd.c index dbe422b..0219a8a 100644 --- a/extensions/xt_psd.c +++ b/extensions/xt_psd.c @@ -69,7 +69,6 @@ struct host { struct host *next; unsigned long timestamp; struct in_addr src_addr; - struct in_addr dest_addr; __be16 src_port; uint16_t count; uint8_t weight; @@ -92,12 +91,12 @@ static struct { /* * Convert an IP address into a hash table index. */ -static inline int hashfunc(struct in_addr addr) +static unsigned int hashfunc(__be32 addr) { unsigned int value; - int hash; + unsigned int hash; - value = addr.s_addr; + value = addr; hash = 0; do { hash ^= value; @@ -182,12 +181,12 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) struct tcphdr tcph; struct udphdr udph; } _buf; - struct in_addr addr; - u_int16_t src_port,dest_port; + u_int16_t dest_port; u_int8_t proto; unsigned long now; struct host *curr, *last = NULL, **head; - int hash, count = 0; + int count = 0; + unsigned int hash; /* Parameters from userspace */ const struct xt_psd_info *psdinfo = match->matchinfo; @@ -198,10 +197,9 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) } proto = iph->protocol; - addr.s_addr = iph->saddr; /* We're using IP address 0.0.0.0 for a special purpose here, so don't let * them spoof us. [DHCP needs this feature - HW] */ - if (addr.s_addr == 0) { + if (iph->saddr == 0) { pr_debug("spoofed source address (0.0.0.0)\n"); return false; } @@ -213,14 +211,12 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) return false; /* Yep, it's dirty */ - src_port = tcph->source; dest_port = tcph->dest; } else if (proto == IPPROTO_UDP || proto == IPPROTO_UDPLITE) { udph = skb_header_pointer(pskb, match->thoff, sizeof(_buf.udph), &_buf.udph); if (udph == NULL) return false; - src_port = udph->source; dest_port = udph->dest; } else { pr_debug("protocol not supported\n"); @@ -228,7 +224,7 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) } now = jiffies; - hash = hashfunc(addr); + hash = hashfunc(iph->saddr); head = &state.hash[hash]; spin_lock(&state.lock); @@ -236,7 +232,7 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) /* Do we know this source address already? */ curr = *head; while (curr != NULL) { - if (curr->src_addr.s_addr == addr.s_addr) + if (curr->src_addr.s_addr == iph->saddr) break; count++; curr = host_get_next(curr, &last); @@ -279,7 +275,7 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) /* First, find it */ if (state.list[state.index].src_addr.s_addr != 0) - head = &state.hash[hashfunc(state.list[state.index].src_addr)]; + head = &state.hash[hashfunc(state.list[state.index].src_addr.s_addr)]; else head = &last; last = NULL; @@ -310,10 +306,8 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) *head = curr; /* And fill in the fields */ + curr->src_addr.s_addr = iph->saddr; curr->timestamp = now; - curr->src_addr = addr; - curr->dest_addr.s_addr = iph->daddr; - curr->src_port = src_port; curr->count = 1; curr->weight = get_port_weight(psdinfo, dest_port); curr->ports[0].number = dest_port; From 54ac2a899a58c8b799985d3463a8fd0e9e3f4b1b Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Thu, 16 Aug 2012 13:46:10 +0200 Subject: [PATCH 06/11] xt_psd: split struct host into generic and AF-dependent structure --- extensions/xt_psd.c | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/extensions/xt_psd.c b/extensions/xt_psd.c index 0219a8a..e6482c0 100644 --- a/extensions/xt_psd.c +++ b/extensions/xt_psd.c @@ -68,13 +68,25 @@ struct port { struct host { struct host *next; unsigned long timestamp; - struct in_addr src_addr; __be16 src_port; uint16_t count; uint8_t weight; struct port ports[SCAN_MAX_COUNT-1]; }; +/** + * Information we keep per ipv4 source address. + */ +struct host4 { + struct host host; + __be32 saddr; +}; + +static struct host4 *host_to_host4(const struct host *h) +{ + return (struct host4 *)h; +} + /** * State information. * @list: list of source addresses @@ -83,7 +95,7 @@ struct host { */ static struct { spinlock_t lock; - struct host list[LIST_SIZE]; + struct host4 list[LIST_SIZE]; struct host *hash[HASH_SIZE]; int index; } state; @@ -185,6 +197,7 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) u_int8_t proto; unsigned long now; struct host *curr, *last = NULL, **head; + struct host4 *curr4; int count = 0; unsigned int hash; /* Parameters from userspace */ @@ -232,7 +245,8 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) /* Do we know this source address already? */ curr = *head; while (curr != NULL) { - if (curr->src_addr.s_addr == iph->saddr) + curr4 = host_to_host4(curr); + if (curr4->saddr == iph->saddr) break; count++; curr = host_get_next(curr, &last); @@ -254,7 +268,8 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) /* We know this address, but the entry is outdated. Mark it unused, and * remove from the hash table. We'll allocate a new entry instead since * this one might get re-used too soon. */ - curr->src_addr.s_addr = 0; + curr4 = host_to_host4(curr); + curr4->saddr = 0; ht_unlink(head, last); last = NULL; } @@ -274,14 +289,15 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) * hash table already because of the HASH_MAX check above). */ /* First, find it */ - if (state.list[state.index].src_addr.s_addr != 0) - head = &state.hash[hashfunc(state.list[state.index].src_addr.s_addr)]; + if (state.list[state.index].saddr != 0) + head = &state.hash[hashfunc(state.list[state.index].saddr)]; else head = &last; last = NULL; curr = *head; while (curr != NULL) { - if (curr == &state.list[state.index]) + curr4 = host_to_host4(curr); + if (curr4 == &state.list[state.index]) break; last = curr; curr = curr->next; @@ -296,7 +312,8 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) } /* Get our list entry */ - curr = &state.list[state.index++]; + curr4 = &state.list[state.index++]; + curr = &curr4->host; if (state.index >= LIST_SIZE) state.index = 0; @@ -306,7 +323,8 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) *head = curr; /* And fill in the fields */ - curr->src_addr.s_addr = iph->saddr; + curr4 = host_to_host4(curr); + curr4->saddr = iph->saddr; curr->timestamp = now; curr->count = 1; curr->weight = get_port_weight(psdinfo, dest_port); From 651e60f8d7158fc73c4d07aa4c0f4dc9e41cc977 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Thu, 16 Aug 2012 14:39:25 +0200 Subject: [PATCH 07/11] xt_psd: move table cleanup into helper --- extensions/xt_psd.c | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/extensions/xt_psd.c b/extensions/xt_psd.c index e6482c0..1104d2d 100644 --- a/extensions/xt_psd.c +++ b/extensions/xt_psd.c @@ -183,6 +183,27 @@ entry_is_recent(const struct host *h, unsigned long delay_threshold, time_after_eq(now, h->timestamp); } +static void remove_oldest(struct host **head, struct host *curr) +{ + struct host *h, *last = NULL; + + /* + * We are going to re-use the oldest list entry, so remove it from the + * hash table first, if it is really already in use. + */ + h = *head; + while (h != NULL) { + if (curr == h) + break; + last = h; + h = h->next; + } + + /* Then, remove it */ + if (h != NULL) + ht_unlink(head, last); +} + static bool xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) { @@ -284,36 +305,15 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) if (count >= HASH_MAX && last != NULL) last->next = NULL; - /* We're going to re-use the oldest list entry, so remove it from the hash - * table first (if it is really already in use, and isn't removed from the - * hash table already because of the HASH_MAX check above). */ - - /* First, find it */ if (state.list[state.index].saddr != 0) head = &state.hash[hashfunc(state.list[state.index].saddr)]; else head = &last; - last = NULL; - curr = *head; - while (curr != NULL) { - curr4 = host_to_host4(curr); - if (curr4 == &state.list[state.index]) - break; - last = curr; - curr = curr->next; - } - - /* Then, remove it */ - if (curr != NULL) { - if (last != NULL) - last->next = last->next->next; - else if (*head != NULL) - *head = (*head)->next; - } /* Get our list entry */ curr4 = &state.list[state.index++]; curr = &curr4->host; + remove_oldest(head, curr); if (state.index >= LIST_SIZE) state.index = 0; From 77240e0918d05eeb43dec301290e537470d79628 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Fri, 17 Aug 2012 14:01:50 +0200 Subject: [PATCH 08/11] xt_psd: use tcph->dest directly This allows us to move more code away from the main match function. --- extensions/xt_psd.c | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/extensions/xt_psd.c b/extensions/xt_psd.c index 1104d2d..eb750f5 100644 --- a/extensions/xt_psd.c +++ b/extensions/xt_psd.c @@ -137,15 +137,25 @@ static uint16_t get_port_weight(const struct xt_psd_info *psd, __be16 port) static bool is_portscan(struct host *host, const struct xt_psd_info *psdinfo, - uint8_t proto, __be16 dest_port) + const struct tcphdr *tcph, uint8_t proto) { + if (port_in_list(host, proto, tcph->dest)) + return false; + + /* + * TCP/ACK and/or TCP/RST to a new port? This could be an + * outgoing connection. + */ + if (proto == IPPROTO_TCP && (tcph->ack || tcph->rst)) + return false; + host->timestamp = jiffies; if (host->weight >= psdinfo->weight_threshold) /* already matched */ return true; /* Update the total weight */ - host->weight += get_port_weight(psdinfo, dest_port); + host->weight += get_port_weight(psdinfo, tcph->dest); /* Got enough destination ports to decide that this is a scan? */ if (host->weight >= psdinfo->weight_threshold) @@ -153,7 +163,7 @@ is_portscan(struct host *host, const struct xt_psd_info *psdinfo, /* Remember the new port */ if (host->count < ARRAY_SIZE(host->ports)) { - host->ports[host->count].number = dest_port; + host->ports[host->count].number = tcph->dest; host->ports[host->count].proto = proto; host->count++; } @@ -209,12 +219,10 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) { const struct iphdr *iph; const struct tcphdr *tcph = NULL; - const struct udphdr *udph; union { struct tcphdr tcph; struct udphdr udph; } _buf; - u_int16_t dest_port; u_int8_t proto; unsigned long now; struct host *curr, *last = NULL, **head; @@ -243,15 +251,11 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) sizeof(_buf.tcph), &_buf.tcph); if (tcph == NULL) return false; - - /* Yep, it's dirty */ - dest_port = tcph->dest; } else if (proto == IPPROTO_UDP || proto == IPPROTO_UDPLITE) { - udph = skb_header_pointer(pskb, match->thoff, + tcph = skb_header_pointer(pskb, match->thoff, sizeof(_buf.udph), &_buf.udph); - if (udph == NULL) + if (tcph == NULL) return false; - dest_port = udph->dest; } else { pr_debug("protocol not supported\n"); return false; @@ -276,13 +280,7 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) if (curr != NULL) { /* We know this address, and the entry isn't too old. Update it. */ if (entry_is_recent(curr, psdinfo->delay_threshold, now)) { - if (port_in_list(curr, proto, dest_port)) - goto out_no_match; - /* TCP/ACK and/or TCP/RST to a new port? This could be an outgoing connection. */ - if (proto == IPPROTO_TCP && (tcph->ack || tcph->rst)) - goto out_no_match; - - if (is_portscan(curr, psdinfo, proto, dest_port)) + if (is_portscan(curr, psdinfo, tcph, proto)) goto out_match; goto out_no_match; } @@ -327,8 +325,8 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) curr4->saddr = iph->saddr; curr->timestamp = now; curr->count = 1; - curr->weight = get_port_weight(psdinfo, dest_port); - curr->ports[0].number = dest_port; + curr->weight = get_port_weight(psdinfo, tcph->dest); + curr->ports[0].number = tcph->dest; curr->ports[0].proto = proto; out_no_match: From 2ba833fe479d1f41b8cfd4574df0f7756fca91eb Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Fri, 17 Aug 2012 14:21:04 +0200 Subject: [PATCH 09/11] xt_psd: move L4 header fetching into helper Also start splitting psd_match into two functions, one to do initial sanity checking and header retrieval, one to do the actual work. --- extensions/xt_psd.c | 100 +++++++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 43 deletions(-) diff --git a/extensions/xt_psd.c b/extensions/xt_psd.c index eb750f5..02b626c 100644 --- a/extensions/xt_psd.c +++ b/extensions/xt_psd.c @@ -214,52 +214,36 @@ static void remove_oldest(struct host **head, struct host *curr) ht_unlink(head, last); } -static bool -xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) +static void * +get_header_pointer4(const struct sk_buff *skb, unsigned int thoff, void *mem) +{ + const struct iphdr *iph = ip_hdr(skb); + int hdrlen; + + switch (iph->protocol) { + case IPPROTO_TCP: + hdrlen = sizeof(struct tcphdr); + break; + case IPPROTO_UDP: + case IPPROTO_UDPLITE: + hdrlen = sizeof(struct udphdr); + break; + default: + return NULL; + } + + return skb_header_pointer(skb, thoff, hdrlen, mem); +} + +static bool +handle_packet4(const struct iphdr *iph, const struct tcphdr *tcph, + const struct xt_psd_info *psdinfo) { - const struct iphdr *iph; - const struct tcphdr *tcph = NULL; - union { - struct tcphdr tcph; - struct udphdr udph; - } _buf; - u_int8_t proto; unsigned long now; struct host *curr, *last = NULL, **head; struct host4 *curr4; int count = 0; unsigned int hash; - /* Parameters from userspace */ - const struct xt_psd_info *psdinfo = match->matchinfo; - - iph = ip_hdr(pskb); - if (iph->frag_off & htons(IP_OFFSET)) { - pr_debug("sanity check failed\n"); - return false; - } - - proto = iph->protocol; - /* We're using IP address 0.0.0.0 for a special purpose here, so don't let - * them spoof us. [DHCP needs this feature - HW] */ - if (iph->saddr == 0) { - pr_debug("spoofed source address (0.0.0.0)\n"); - return false; - } - - if (proto == IPPROTO_TCP) { - tcph = skb_header_pointer(pskb, match->thoff, - sizeof(_buf.tcph), &_buf.tcph); - if (tcph == NULL) - return false; - } else if (proto == IPPROTO_UDP || proto == IPPROTO_UDPLITE) { - tcph = skb_header_pointer(pskb, match->thoff, - sizeof(_buf.udph), &_buf.udph); - if (tcph == NULL) - return false; - } else { - pr_debug("protocol not supported\n"); - return false; - } now = jiffies; hash = hashfunc(iph->saddr); @@ -280,7 +264,7 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) if (curr != NULL) { /* We know this address, and the entry isn't too old. Update it. */ if (entry_is_recent(curr, psdinfo->delay_threshold, now)) { - if (is_portscan(curr, psdinfo, tcph, proto)) + if (is_portscan(curr, psdinfo, tcph, iph->protocol)) goto out_match; goto out_no_match; } @@ -294,7 +278,7 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) } /* We don't need an ACK from a new source address */ - if (proto == IPPROTO_TCP && tcph->ack) + if (iph->protocol == IPPROTO_TCP && tcph->ack) goto out_no_match; /* Got too many source addresses with the same hash value? Then remove the @@ -327,7 +311,7 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) curr->count = 1; curr->weight = get_port_weight(psdinfo, tcph->dest); curr->ports[0].number = tcph->dest; - curr->ports[0].proto = proto; + curr->ports[0].proto = iph->protocol; out_no_match: spin_unlock(&state.lock); @@ -338,6 +322,36 @@ out_match: return true; } +static bool +xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) +{ + struct iphdr *iph = ip_hdr(pskb); + struct tcphdr _tcph; + struct tcphdr *tcph; + /* Parameters from userspace */ + const struct xt_psd_info *psdinfo = match->matchinfo; + + if (iph->frag_off & htons(IP_OFFSET)) { + pr_debug("sanity check failed\n"); + return false; + } + + /* + * We are using IP address 0.0.0.0 for a special purpose here, so do + * not let them spoof us. [DHCP needs this feature - HW] + */ + if (iph->saddr == 0) { + pr_debug("spoofed source address (0.0.0.0)\n"); + return false; + } + + tcph = get_header_pointer4(pskb, match->thoff, &_tcph); + if (tcph == NULL) + return false; + + return handle_packet4(iph, tcph, psdinfo); +} + static int psd_mt_check(const struct xt_mtchk_param *par) { const struct xt_psd_info *info = par->matchinfo; From 0a97126f5bb0ac70ab4c6ffae1409ee897155cfe Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Fri, 17 Aug 2012 14:31:57 +0200 Subject: [PATCH 10/11] xt_psd: move IPv4 state locking responsibility to caller The former psd_match function is now < 72 lines. --- extensions/xt_psd.c | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/extensions/xt_psd.c b/extensions/xt_psd.c index 02b626c..77985b4 100644 --- a/extensions/xt_psd.c +++ b/extensions/xt_psd.c @@ -237,20 +237,16 @@ get_header_pointer4(const struct sk_buff *skb, unsigned int thoff, void *mem) static bool handle_packet4(const struct iphdr *iph, const struct tcphdr *tcph, - const struct xt_psd_info *psdinfo) + const struct xt_psd_info *psdinfo, unsigned int hash) { unsigned long now; struct host *curr, *last = NULL, **head; struct host4 *curr4; int count = 0; - unsigned int hash; now = jiffies; - hash = hashfunc(iph->saddr); head = &state.hash[hash]; - spin_lock(&state.lock); - /* Do we know this source address already? */ curr = *head; while (curr != NULL) { @@ -263,11 +259,9 @@ handle_packet4(const struct iphdr *iph, const struct tcphdr *tcph, if (curr != NULL) { /* We know this address, and the entry isn't too old. Update it. */ - if (entry_is_recent(curr, psdinfo->delay_threshold, now)) { - if (is_portscan(curr, psdinfo, tcph, iph->protocol)) - goto out_match; - goto out_no_match; - } + if (entry_is_recent(curr, psdinfo->delay_threshold, now)) + return is_portscan(curr, psdinfo, tcph, iph->protocol); + /* We know this address, but the entry is outdated. Mark it unused, and * remove from the hash table. We'll allocate a new entry instead since * this one might get re-used too soon. */ @@ -279,7 +273,7 @@ handle_packet4(const struct iphdr *iph, const struct tcphdr *tcph, /* We don't need an ACK from a new source address */ if (iph->protocol == IPPROTO_TCP && tcph->ack) - goto out_no_match; + return false; /* Got too many source addresses with the same hash value? Then remove the * oldest one from the hash table, so that they can't take too much of our @@ -312,14 +306,7 @@ handle_packet4(const struct iphdr *iph, const struct tcphdr *tcph, curr->weight = get_port_weight(psdinfo, tcph->dest); curr->ports[0].number = tcph->dest; curr->ports[0].proto = iph->protocol; - -out_no_match: - spin_unlock(&state.lock); return false; - -out_match: - spin_unlock(&state.lock); - return true; } static bool @@ -328,6 +315,8 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) struct iphdr *iph = ip_hdr(pskb); struct tcphdr _tcph; struct tcphdr *tcph; + bool matched; + unsigned int hash; /* Parameters from userspace */ const struct xt_psd_info *psdinfo = match->matchinfo; @@ -349,7 +338,12 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) if (tcph == NULL) return false; - return handle_packet4(iph, tcph, psdinfo); + hash = hashfunc(iph->saddr); + + spin_lock(&state.lock); + matched = handle_packet4(iph, tcph, psdinfo, hash); + spin_unlock(&state.lock); + return matched; } static int psd_mt_check(const struct xt_mtchk_param *par) From 3a6e73e9866070e4dad32dcefb3d331c45e72e08 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Fri, 17 Aug 2012 16:32:35 +0200 Subject: [PATCH 11/11] xt_psd: add IPv6 support Because most users will probably only use IPv4 psd, allocate most of the state6 storage when the first IPv6 psd rule is added, and not at module load time via .bss. --- extensions/libxt_psd.c | 2 +- extensions/xt_psd.c | 235 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 221 insertions(+), 16 deletions(-) diff --git a/extensions/libxt_psd.c b/extensions/libxt_psd.c index bd03480..3f88ac4 100644 --- a/extensions/libxt_psd.c +++ b/extensions/libxt_psd.c @@ -142,7 +142,7 @@ static struct xtables_match psd_mt_reg = { .name = "psd", .version = XTABLES_VERSION, .revision = 1, - .family = NFPROTO_IPV4, + .family = NFPROTO_UNSPEC, .size = XT_ALIGN(sizeof(struct xt_psd_info)), .userspacesize = XT_ALIGN(sizeof(struct xt_psd_info)), .help = psd_mt_help, diff --git a/extensions/xt_psd.c b/extensions/xt_psd.c index 77985b4..08ce508 100644 --- a/extensions/xt_psd.c +++ b/extensions/xt_psd.c @@ -22,13 +22,14 @@ #define pr_fmt(x) KBUILD_MODNAME ": " x #include -#include #include -#include -#include +#include +#include #include -#include #include +#include +#include +#include #include "xt_psd.h" #include "compat_xtables.h" @@ -39,6 +40,7 @@ MODULE_AUTHOR("Jan Rekorajski "); MODULE_AUTHOR(" Mohd Nawawi Mohamad Jamili "); MODULE_DESCRIPTION("Xtables: PSD - portscan detection"); MODULE_ALIAS("ipt_psd"); +MODULE_ALIAS("ip6t_psd"); /* * Keep track of up to LIST_SIZE source addresses, using a hash table of @@ -50,6 +52,10 @@ MODULE_ALIAS("ipt_psd"); #define HASH_SIZE (1 << HASH_LOG) #define HASH_MAX 0x10 +#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE) +# define WITH_IPV6 1 +#endif + /* * Information we keep per each target port */ @@ -87,8 +93,13 @@ static struct host4 *host_to_host4(const struct host *h) return (struct host4 *)h; } +struct host6 { + struct host host; + struct in6_addr saddr; +}; + /** - * State information. + * State information for IPv4 portscan detection. * @list: list of source addresses * @hash: pointers into the list * @index: oldest entry to be replaced @@ -100,6 +111,46 @@ static struct { int index; } state; +#ifdef WITH_IPV6 +/** + * State information for IPv6 portscan detection. + * @list: list of source addresses + * @hash: pointers into the list + * @index: oldest entry to be replaced + */ +static struct { + spinlock_t lock; + struct host6 *list; + struct host **hash; + int index; +} state6; + +static struct host6 *host_to_host6(const struct host *h) +{ + return (struct host6 *) h; +} + +/** + * allocate state6 memory only when needed + */ +static bool state6_alloc_mem(void) +{ + if (state6.hash != NULL) + return true; + + state6.list = vzalloc(LIST_SIZE * sizeof(struct host6)); + if (state6.list == NULL) + return false; + + state6.hash = vzalloc(HASH_SIZE * sizeof(struct host*)); + if (state6.hash == NULL) { + vfree(state6.list); + return false; + } + return true; +} +#endif + /* * Convert an IP address into a hash table index. */ @@ -117,6 +168,12 @@ static unsigned int hashfunc(__be32 addr) return hash & (HASH_SIZE - 1); } +static inline unsigned int hashfunc6(const struct in6_addr *addr) +{ + __be32 h = addr->s6_addr32[0] ^ addr->s6_addr32[1]; + return hashfunc(h ^ addr->s6_addr32[2] ^ addr->s6_addr32[3]); +} + static bool port_in_list(struct host *host, uint8_t proto, uint16_t port) { unsigned int i; @@ -346,6 +403,126 @@ xt_psd_match(const struct sk_buff *pskb, struct xt_action_param *match) return matched; } +#ifdef WITH_IPV6 +static bool +handle_packet6(const struct ipv6hdr *ip6h, const struct tcphdr *tcph, + const struct xt_psd_info *psdinfo, uint8_t proto, int hash) +{ + unsigned long now; + struct host *curr, *last = NULL, **head; + struct host6 *curr6; + int count = 0; + + now = jiffies; + head = &state6.hash[hash]; + + curr = *head; + while (curr != NULL) { + curr6 = host_to_host6(curr); + if (ipv6_addr_equal(&curr6->saddr, &ip6h->saddr)) + break; + count++; + curr = host_get_next(curr, &last); + } + + if (curr != NULL) { + if (entry_is_recent(curr, psdinfo->delay_threshold, now)) + return is_portscan(curr, psdinfo, tcph, proto); + curr6 = host_to_host6(curr); + memset(&curr6->saddr, 0, sizeof(curr6->saddr)); + ht_unlink(head, last); + last = NULL; + } + + if (proto == IPPROTO_TCP && tcph->ack) + return false; + + if (count >= HASH_MAX && last != NULL) + last->next = NULL; + + if (!ipv6_addr_any(&state6.list[state6.index].saddr)) + head = &state6.hash[hashfunc6(&state6.list[state6.index].saddr)]; + else + head = &last; + + curr6 = &state6.list[state6.index++]; + curr = &curr6->host; + remove_oldest(head, curr); + if (state6.index >= LIST_SIZE) + state6.index = 0; + + head = &state6.hash[hash]; + curr->next = *head; + *head = curr; + + curr6 = host_to_host6(curr); + curr6->saddr = ip6h->saddr; + curr->timestamp = now; + curr->count = 1; + curr->weight = get_port_weight(psdinfo, tcph->dest); + curr->ports[0].number = tcph->dest; + curr->ports[0].proto = proto; + return false; +} + +static void * +get_header_pointer6(const struct sk_buff *skb, void *mem, uint8_t *proto) +{ + static const uint8_t types[] = {IPPROTO_TCP, + IPPROTO_UDP, IPPROTO_UDPLITE}; + unsigned int i, offset = 0; + int err; + size_t hdrlen; + + for (i = 0; i < ARRAY_SIZE(types); ++i) { + err = ipv6_find_hdr(skb, &offset, types[i], NULL, NULL); + if (err < 0) + continue; + + switch (types[i]) { + case IPPROTO_TCP: + hdrlen = sizeof(struct tcphdr); + break; + case IPPROTO_UDP: + case IPPROTO_UDPLITE: + hdrlen = sizeof(struct udphdr); + break; + default: + return NULL; + } + *proto = types[i]; + return skb_header_pointer(skb, offset, hdrlen, mem); + } + return NULL; +} + +static bool +xt_psd_match6(const struct sk_buff *pskb, struct xt_action_param *match) +{ + const struct ipv6hdr *ip6h = ipv6_hdr(pskb); + struct tcphdr _tcph; + struct tcphdr *tcph; + uint8_t proto = 0; + bool matched; + int hash; + const struct xt_psd_info *psdinfo = match->matchinfo; + + if (ipv6_addr_any(&ip6h->saddr)) + return false; + + tcph = get_header_pointer6(pskb, &_tcph, &proto); + if (tcph == NULL) + return false; + + hash = hashfunc6(&ip6h->saddr); + + spin_lock(&state6.lock); + matched = handle_packet6(ip6h, tcph, psdinfo, proto, hash); + spin_unlock(&state6.lock); + return matched; +} +#endif + static int psd_mt_check(const struct xt_mtchk_param *par) { const struct xt_psd_info *info = par->matchinfo; @@ -367,25 +544,53 @@ static int psd_mt_check(const struct xt_mtchk_param *par) return 0; } -static struct xt_match xt_psd_reg __read_mostly = { - .name = "psd", - .family = NFPROTO_IPV4, - .revision = 1, - .checkentry = psd_mt_check, - .match = xt_psd_match, - .matchsize = sizeof(struct xt_psd_info), - .me = THIS_MODULE, +#ifdef WITH_IPV6 +static int psd_mt_check6(const struct xt_mtchk_param *par) +{ + if (!state6_alloc_mem()) + return -ENOMEM; + return psd_mt_check(par); +} +#endif + +static struct xt_match xt_psd_reg[] __read_mostly = { + { + .name = "psd", + .family = NFPROTO_IPV4, + .revision = 1, + .checkentry = psd_mt_check, + .match = xt_psd_match, + .matchsize = sizeof(struct xt_psd_info), + .me = THIS_MODULE, +#ifdef WITH_IPV6 + }, { + .name = "psd", + .family = NFPROTO_IPV6, + .revision = 1, + .checkentry = psd_mt_check6, + .match = xt_psd_match6, + .matchsize = sizeof(struct xt_psd_info), + .me = THIS_MODULE, +#endif + } }; static int __init xt_psd_init(void) { spin_lock_init(&(state.lock)); - return xt_register_match(&xt_psd_reg); +#ifdef WITH_IPV6 + spin_lock_init(&(state6.lock)); +#endif + return xt_register_matches(xt_psd_reg, ARRAY_SIZE(xt_psd_reg)); } static void __exit xt_psd_exit(void) { - xt_unregister_match(&xt_psd_reg); + xt_unregister_matches(xt_psd_reg, ARRAY_SIZE(xt_psd_reg)); +#ifdef WITH_IPV6 + vfree(state6.list); + vfree(state6.hash); +#endif } module_init(xt_psd_init);