diff --git a/.gitignore b/.gitignore index 950da59..e9a6b16 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ packages/etc/systemd/system/edge.service packages/etc/systemd/system/edge@.service packages/etc/systemd/system/supernode.service *dSYM* + +cmake-build-*/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e34d34..adf4b9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,7 +115,7 @@ add_library(n2n STATIC src/tuntap_linux.c src/tuntap_osx.c src/n2n_regex.c - ) + src/network_traffic_filter.c) if(DEFINED WIN32) add_library(edge_utils_win32 src/edge_utils_win32.c) diff --git a/README.md b/README.md index d945fd2..29f1fe9 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,10 @@ The [TAP Configuration Guide](https://github.com/ntop/n2n/blob/dev/doc/TapConfig Reaching a remote network or tunneling all the internet traffic via n2n are two common tasks which require a proper routing setup. n2n supports routing needs providing options for packet forwarding (`-r`) including broadcasts (`-E`) as well as temporarily modifying the routing table (`-n`). Details can be found in the [Routing document](doc/Routing.md). +## Traffic Restrictions + +It is possible to drop or accept specific packet transmit over edge network interface by rules. Rules can be specify by (`-R rule_str`) multiple times. Details can be found in the [Traffic Restrictions](doc/TrafficRestrictions.md). + ## Contribution You can contribute to n2n in various ways: diff --git a/doc/TrafficRestrictions.md b/doc/TrafficRestrictions.md new file mode 100644 index 0000000..8d6441a --- /dev/null +++ b/doc/TrafficRestrictions.md @@ -0,0 +1,41 @@ +# Traffic Restrictions + +It is possible to drop or accept specific packet transmit over edge network interface by rules. Rules can be specify by (`-R rule_str`) multiple times. + +## Rule String Format + +rule_str format: `src_ip/len:[b_port,e_port],dst_ip/len:[s_port,e_port],TCP+/-,UDP+/-,ICMP+/-` + +`ip/len` indicate a cidr block, len can be ignore, means single ip (not cidr block) will be use in filter rule. + +`+`,`-` after `TCP`,`UDP`,`ICMP` proto type indicate allow or drop packet of that proto. if any of above three proto missed, the rule will not take effect for that proto. + +Ports range `[s_port,e_port]` can be instead by single port number. If not specify, `[0,65535]` will be used. Ports range include start_port and end_port. + +examples: +`192.168.1.5/32:[0,65535],192.168.0.0/24:[8081,65535],TCP-,UDP-,ICMP+` +`192.168.1.5:[0,65535],192.168.0.0/24:8000,ICMP+` +`192.168.1.5,192.168.0.7,TCP-,UDP-,ICMP-` // packets by all proto of all ports from 192.158.1.5 to any ports of 192.168.0.7 will be dropped. + +## Multiple Rules + +`-R rule_str` can be used multiple times to add multiple rules. Each `-R rule_str` add one rule. for example: + +`edge -c xxxx -k xxxx -a 192.168.100.5 -l xxx.xxx.xxx.xxx:1234 -r -R 192.168.1.5/32:[0,65535],192.168.0.0/24:[8081,65535],TCP-,UDP-,ICMP+ -R 192.168.1.5:[0,65535],192.168.0.0/24:8000,ICMP+ -R 192.168.1.5,192.168.0.7,TCP-` + +## Matching Rules Priority + +If multiple rules matching packet's ips and ports, the rule with smaller cidr block(smaller address space) will be selected. That means rules with larger `len` value has higher priority. + +Actually, current implementation will add the `len` of src cidr and dst cidr of each matched rules as priority value, the rule with largest priority value will take effect. + +## Blocklist/Allowlist mode + +Packets that cannot match any rule will be accepted by default. Users can add rules to block traffics. + +This behavior can be change by add the rule : `0.0.0.0/0:[0,65535],0.0.0.0/0:[0,65535],TCP-,UDP-,ICMP-`. Then all traffic will be dropped, users need add rules to allow traffics. + +for example, `-R 0.0.0.0/0,0.0.0.0/0,TCP-,UDP-,ICMP- -R 192.168.100.0/24,192.168.100.0/24,ICMP+` dropped all traffic, except ICMP traffics inside `192.168.100.0/24`. + +More complex behavior can be set with the feature of `Matching Rules Priority`. + diff --git a/edge.8 b/edge.8 index 2e112c5..0258f05 100644 --- a/edge.8 +++ b/edge.8 @@ -131,6 +131,31 @@ traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets But this method does not always work due to various local network device policy. .TP +\-R +Add rule to drop or accept specific packet transmit over edge network interface. +-R rule_str can be used multiple times to add multiple rules. Each -R rule_str add +one rule. + +rule_str format:"src_ip/len:[b_port,e_port],dst_ip/len:[s_port,e_port],TCP+/-,UDP+/-,ICMP+/-". + +ip/len indicate a cidr block, len can be ignore, means single ip(not cidr block) +will be use in filter rule. + ++,- after TCP,UDP,ICMP proto type indicate allow or drop packet of that proto. +if any of above three proto missed, the rule will not take effect for that proto. + +Ports range [s_port,e_port] can be instead by single port number. If not specify, [0,65535] +will be used. Ports range include start_port and end_port. If multiple rules matching packet's +ips and ports, the rule with smaller cidr block(smaller address space) will be selected. That +means rules with larger len value has higher priority. + +Packets that cannot match any rule will be accepted by default. Users can add rules to +block traffics. This behavior can be change by add the rule : `0.0.0.0/0:[0,65535],0.0.0.0/0: +[0,65535],TCP-,UDP-,ICMP-`. Then all traffic will be dropped, users need add rules to allow +traffics. + +for example : `-R 0.0.0.0/0,0.0.0.0/0,TCP-,UDP-,ICMP- -R 192.168.100.0/24,192.168.100.0/24,ICMP+`, + \-v more verbose logging (may be specified several times for more verbosity). .SH ENVIRONMENT diff --git a/include/n2n.h b/include/n2n.h index ba0d7b7..30a4aa0 100644 --- a/include/n2n.h +++ b/include/n2n.h @@ -254,6 +254,72 @@ typedef struct n2n_edge_callbacks { } n2n_edge_callbacks_t; /* ***************************************************** */ +// network traffic filter + +typedef struct port_range{ + uint16_t start_port; // range contain 'start_port' self + uint16_t end_port; // range contain 'end_port' self +} port_range_t; + +typedef struct filter_rule_key +{ + in_addr_t src_net_cidr; + uint8_t src_net_bit_len; + port_range_t src_port_range; + in_addr_t dst_net_cidr; + uint8_t dst_net_bit_len; + port_range_t dst_port_range; + uint8_t bool_tcp_configured; + uint8_t bool_udp_configured; + uint8_t bool_icmp_configured; +} filter_rule_key_t; + +typedef struct filter_rule +{ + filter_rule_key_t key; + + uint8_t bool_accept_icmp; + uint8_t bool_accept_udp; + uint8_t bool_accept_tcp; + + UT_hash_handle hh; /* makes this structure hashable */ +} filter_rule_t; + + +//rule_str format: src_ip/len:[b_port,e_port],dst_ip/len:[s_port,e_port],TCP+/-,UDP+/-,ICMP+/- +// +//ip/len indicate a cidr block, len can be ignore, means single ip (not cidr block) will be use in filter rule. +// +//'+','-' after proto type indicate allow or disallow that proto transmit packet. if any of above three proto missed, it will be disallow. +// +//[s_port,e_port] can be instead by single port number, if not specify, 0-65535 ports will be used. ports range include start_port and end_port. +// +//examples: +//192.168.1.5/32:[0,65535],192.168.0.0/24:[8081,65535],TCP-,UDP-,ICMP+ +//192.168.1.5:[0,65535],192.168.0.0/24:8000,ICMP+ +//192.168.1.5,192.168.0.7 // packets by all proto of all ports from 192.158.1.5 to any ports of 192.168.0.7 will be disallow(dropped). +// +// for impl, see: network_traffic_filter.c +uint8_t process_traffic_filter_rule_str(const char* rule_str, filter_rule_t* rule_struct); + +/* + * network traffic filter interface + */ +typedef struct network_traffic_filter +{ +/* A packet has been received from a peer. N2N_DROP can be returned to + * drop the packet. The packet payload can be modified. This only allows + * the packet size to be reduced */ + n2n_verdict (*filter_packet_from_peer)(struct network_traffic_filter* filter, n2n_edge_t *eee, const n2n_sock_t *peer, uint8_t *payload, uint16_t payload_size); + +/* A packet has been received from the TAP interface. N2N_DROP can be + * returned to drop the packet. The packet payload can be modified. + * This only allows the packet size to be reduced */ + n2n_verdict (*filter_packet_from_tap)(struct network_traffic_filter* filter, n2n_edge_t *eee, uint8_t *payload, uint16_t payload_size); + +} network_traffic_filter_t; + +/* *************************************************** */ typedef struct n2n_tuntap_priv_config { char tuntap_dev_name[N2N_IFNAMSIZ]; @@ -295,6 +361,7 @@ typedef struct n2n_edge_conf { int register_ttl; /**< TTL for registration packet when UDP NAT hole punching through supernode. */ int local_port; int mgmt_port; + filter_rule_t *network_traffic_filter_rules; } n2n_edge_conf_t; @@ -347,6 +414,8 @@ struct n2n_edge { struct n2n_edge_stats stats; /**< Statistics */ n2n_tuntap_priv_config_t tuntap_priv_conf; /**< Tuntap config */ + + network_traffic_filter_t *network_traffic_filter; }; diff --git a/include/network_traffic_filter.h b/include/network_traffic_filter.h new file mode 100644 index 0000000..b1c4fd1 --- /dev/null +++ b/include/network_traffic_filter.h @@ -0,0 +1,45 @@ +/** + * (C) 2007-20 - ntop.org and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not see see + * + */ + +// +// Zhou Bin +// + +#ifndef N2N_NETWORK_TRAFFIC_FILTER_H +#define N2N_NETWORK_TRAFFIC_FILTER_H + +#include "n2n.h" + +/* + * add feature to drop or accept specific packet transmit over edge network interface by rules. + * + * below structs and function used 'n2n_verdict' and other structs, so defined in 'n2n.h', to avoid header files circular dependency. + * port_range_t, filter_rule_key_t, filter_rule_t, network_traffic_filter_t + * uint8_t process_traffic_filter_rule_str(const char* rule_str, filter_rule_t* rule_struct); + */ + + + +network_traffic_filter_t* create_network_traffic_filter(); + +void destroy_network_traffic_filter(network_traffic_filter_t* filter); + +void network_traffic_filter_add_rule(network_traffic_filter_t* filter, filter_rule_t* rules); + + +#endif //N2N_NETWORK_TRAFFIC_FILTER_H diff --git a/src/edge.c b/src/edge.c index 693c5e7..cc9b15b 100644 --- a/src/edge.c +++ b/src/edge.c @@ -32,6 +32,7 @@ #include #include +#include "network_traffic_filter.h" static cap_value_t cap_values[] = { //CAP_NET_RAW, /* Use RAW and PACKET sockets */ @@ -143,7 +144,7 @@ static void help() { #ifndef __APPLE__ "[-D] " #endif - "[-r] [-E] [-v] [-i ] [-L ] [-t ] [-A[]] [-H] [-z[]] [-h]\n\n"); + "[-r] [-E] [-v] [-i ] [-L ] [-t ] [-A[]] [-H] [-z[]] [-R ] [-h]\n\n"); #if defined(N2N_CAN_NAME_IFACE) printf("-d | tap device name\n"); @@ -190,6 +191,8 @@ static void help() { #endif printf("-n | Route an IPv4 network via the gw. Use 0.0.0.0/0 for the default gw. Can be set multiple times.\n"); printf("-v | Make more verbose. Repeat as required.\n"); + printf("-R | Drop or accept packets by rules. Can be set multiple times. \n"); + printf(" | Rule format: src_ip/len:[b_port,e_port],dst_ip/len:[s_port,e_port],TCP+/-,UDP+/-,ICMP+/- \n"); printf("-t | Management UDP Port (for multiple edges on a machine).\n"); printf("\nEnvironment variables:\n"); @@ -516,6 +519,21 @@ static int setOption(int optkey, char *optargument, n2n_tuntap_priv_config_t *ec setTraceLevel(getTraceLevel() + 1); break; + case 'R': /* network traffic filter */ + { + filter_rule_t *new_rule = malloc(sizeof(filter_rule_t)); + memset(new_rule, 0, sizeof(filter_rule_t)); + if(process_traffic_filter_rule_str(optargument, new_rule) ) + { + HASH_ADD(hh, conf->network_traffic_filter_rules, key, sizeof(filter_rule_key_t), new_rule); + }else{ + free(new_rule); + traceEvent(TRACE_WARNING, "Invalid filter rule: %s", optargument); + return(-1); + } + break; + } + default: { traceEvent(TRACE_WARNING, "Unknown option -%c: Ignored", (char)optkey); @@ -547,7 +565,7 @@ static int loadFromCLI(int argc, char *argv[], n2n_edge_conf_t *conf, n2n_tuntap u_char c; while ((c = getopt_long(argc, argv, - "k:a:bc:Eu:g:m:M:s:d:l:p:fvhrt:i:I:SDL:z::A::Hn:" + "k:a:bc:Eu:g:m:M:s:d:l:p:fvhrt:i:I:SDL:z::A::Hn:R:" #ifdef __linux__ "T:" #endif diff --git a/src/edge_utils.c b/src/edge_utils.c index 2c5bb0c..fe7a72d 100644 --- a/src/edge_utils.c +++ b/src/edge_utils.c @@ -16,6 +16,7 @@ * */ +#include "network_traffic_filter.h" #include "n2n.h" #include "edge_utils_win32.h" @@ -270,6 +271,9 @@ n2n_edge_t* edge_init(const n2n_edge_conf_t *conf, int *rv) { goto edge_init_error; } + eee->network_traffic_filter = create_network_traffic_filter(); + network_traffic_filter_add_rule(eee->network_traffic_filter, eee->conf.network_traffic_filter_rules); + //edge_init_success: *rv = 0; return(eee); @@ -1173,11 +1177,16 @@ static int handle_PACKET(n2n_edge_t * eee, } } + if( eee->network_traffic_filter->filter_packet_from_peer( eee->network_traffic_filter, eee, orig_sender, + eth_payload, eth_size ) == N2N_DROP){ + traceEvent(TRACE_DEBUG, "Filtered packet %u", (unsigned int)eth_size); + return(0); + } + if(eee->cb.packet_from_peer) { uint16_t tmp_eth_size = eth_size; if(eee->cb.packet_from_peer(eee, orig_sender, eth_payload, &tmp_eth_size) == N2N_DROP) { traceEvent(TRACE_DEBUG, "DROP packet %u", (unsigned int)eth_size); - return(0); } eth_size = tmp_eth_size; @@ -1724,6 +1733,11 @@ void edge_read_from_tap(n2n_edge_t * eee) { } else { + if( eee->network_traffic_filter->filter_packet_from_tap( eee->network_traffic_filter, eee, eth_pkt, + len) == N2N_DROP){ + traceEvent(TRACE_DEBUG, "Filtered packet %u", (unsigned int)len); + return; + } if(eee->cb.packet_from_tap) { uint16_t tmp_len = len; if(eee->cb.packet_from_tap(eee, eth_pkt, &tmp_len) == N2N_DROP) { @@ -2295,6 +2309,8 @@ void edge_term(n2n_edge_t * eee) { edge_cleanup_routes(eee); + destroy_network_traffic_filter(eee->network_traffic_filter); + closeTraceFile(); free(eee); @@ -2793,6 +2809,17 @@ void edge_init_conf_defaults(n2n_edge_conf_t *conf) { void edge_term_conf(n2n_edge_conf_t *conf) { if (conf->routes) free(conf->routes); if (conf->encrypt_key) free(conf->encrypt_key); + + if(conf->network_traffic_filter_rules) + { + filter_rule_t *el = 0, *tmp = 0; + HASH_ITER(hh, conf->network_traffic_filter_rules, el, tmp) + { + HASH_DEL(conf->network_traffic_filter_rules, el); + free(el); + } + } + } /* ************************************** */ diff --git a/src/network_traffic_filter.c b/src/network_traffic_filter.c new file mode 100644 index 0000000..e49d958 --- /dev/null +++ b/src/network_traffic_filter.c @@ -0,0 +1,743 @@ +/** + * (C) 2007-20 - ntop.org and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not see see + * + */ + +#include "network_traffic_filter.h" +#include "uthash.h" + +#include "netinet/tcp.h" +#include + +// cache that hit less than 10 while 10000 package processed will be delete; +#define CLEAR_CACHE_EVERY_X_COUNT 10000 +#define CLAER_CACHE_ACTIVE_COUNT 10 + +typedef enum { + FPP_UNKNOWN=0, + FPP_ARP = 1, + FPP_TCP=2, + FPP_UDP=3, + FPP_ICMP=4, + FPP_IGMP=5 +} filter_packet_proto; + +const char* get_filter_packet_proto_name(filter_packet_proto proto) +{ + switch (proto) + { + case FPP_ARP: + return "ARP"; + case FPP_TCP: + return "TCP"; + case FPP_UDP: + return "UDP"; + case FPP_ICMP: + return "ICMP"; + case FPP_IGMP: + return "IGMP"; + default: + return "UNKNOWN_PROTO"; + } +} + +typedef struct packet_address_proto_info{ + in_addr_t src_ip; + uint16_t src_port; + in_addr_t dst_ip; + uint16_t dst_port; + filter_packet_proto proto; +}packet_address_proto_info_t; + +const char* get_filter_packet_info_log_string(packet_address_proto_info_t* info) +{ + static char buf[1024] = {0}; + switch (info->proto) + { + case FPP_ARP: + case FPP_ICMP: + case FPP_IGMP: + return get_filter_packet_proto_name(info->proto); + case FPP_TCP: + case FPP_UDP: + { + struct in_addr src, dst; + src.s_addr = info->src_ip; + dst.s_addr = info->dst_ip; + const char* proto = get_filter_packet_proto_name(info->proto); + char src_ip[64] = {0}; char dst_ip[64] = {0}; + strcpy( src_ip, inet_ntoa(src)); strcpy(dst_ip, inet_ntoa(dst)); + sprintf(buf, "%s\t%s:%d->%s:%d", proto, src_ip, info->src_port, dst_ip, info->dst_port); + return buf; + } + default: + return "UNKNOWN_PROTO"; + } +} + +void collect_packet_info(packet_address_proto_info_t* out_info, unsigned char *buffer, int size) { + struct ethhdr *hdr_ether = (struct ethhdr*)buffer; + memset(out_info, 0, sizeof(packet_address_proto_info_t)); + uint16_t ether_type = ntohs(hdr_ether->h_proto); + switch (ether_type) { + case 0x0800: + { + buffer += ETH_HLEN; size -= ETH_HLEN; if(size <= 0) return; + struct iphdr *hdr_ip = (struct iphdr*)buffer; + switch (hdr_ip->version) + { + case 4: + { + out_info->src_ip = hdr_ip->saddr; + out_info->dst_ip = hdr_ip->daddr; + switch (hdr_ip->protocol) { + case 0x01: + out_info->proto = FPP_ICMP; + break; + case 0x02: + out_info->proto = FPP_IGMP; + break; + case 0x06: + { + out_info->proto = FPP_TCP; + buffer += hdr_ip->ihl * 4; size -= hdr_ip->ihl * 4; if(size <= 0) return; + struct tcphdr *hdr_tcp = (struct tcphdr*)buffer; + out_info->src_port = ntohs(hdr_tcp->th_sport); + out_info->dst_port = ntohs(hdr_tcp->th_dport); + break; + } + case 0x11: + { + out_info->proto = FPP_UDP; + buffer += hdr_ip->ihl * 4; size -= hdr_ip->ihl * 4; if(size <= 0) return; + struct udphdr *udp_hdr = (struct tcphdr*)buffer; + out_info->src_port = ntohs(udp_hdr->uh_sport); + out_info->dst_port = ntohs(udp_hdr->uh_dport); + break; + } + default: + out_info->proto = FPP_UNKNOWN; + }; + break; + } + case 6: + { + // TODO: IPV6 Not Support + out_info->proto = FPP_UNKNOWN; + break; + } + default: + out_info->proto = FPP_UNKNOWN; + } + break; + } + case 0x0806: + out_info->proto = FPP_ARP; + break; + case 0x86DD: + out_info->proto = FPP_UNKNOWN; + break; + default: + printf("EtherType 0x%04X", ether_type); + }; +} + +const char* get_filter_rule_info_log_string(filter_rule_t* rule) +{ + static char buf[1024] = {0}; + char* print_start = buf; + char src_net[64] = {0}; char dst_net[64] = {0}; + struct in_addr src, dst; + src.s_addr = rule->key.src_net_cidr; + dst.s_addr = rule->key.dst_net_cidr; + strcpy(src_net, inet_ntoa(src)); strcpy(dst_net, inet_ntoa(dst)); + print_start += sprintf(print_start, "%s/%d:[%d,%d],%s/%d:[%d,%d]", + src_net, rule->key.src_net_bit_len, rule->key.src_port_range.start_port, rule->key.src_port_range.end_port, + dst_net, rule->key.dst_net_bit_len, rule->key.dst_port_range.start_port, rule->key.dst_port_range.end_port, + rule->bool_accept_tcp ? '+' : '-', rule->bool_accept_udp ? '+' : '-', rule->bool_accept_icmp ? '+' : '-'); + if(rule->key.bool_tcp_configured) + print_start += sprintf(print_start, ",TCP%c", rule->bool_accept_tcp ? '+' : '-'); + if(rule->key.bool_udp_configured) + print_start += sprintf(print_start, ",UDP%c", rule->bool_accept_udp ? '+' : '-'); + if(rule->key.bool_icmp_configured) + print_start += sprintf(print_start, ",ICMP%c", rule->bool_accept_icmp ? '+' : '-'); + return buf; +} + +typedef struct filter_rule_pair_cache +{ + packet_address_proto_info_t key; + + uint8_t bool_allow_traffic; + + uint32_t active_count; + + UT_hash_handle hh; /* makes this structure hashable */ +} filter_rule_pair_cache_t; + +uint8_t march_cidr_and_address(in_addr_t network, uint8_t net_bitlen, in_addr_t ip_addr) +{ + in_addr_t mask = 0, ip_addr_network = 0; + network = ntohl(network), ip_addr = ntohl(ip_addr); + uint32_t mask1 = net_bitlen != 0 ? ((~mask) << (32u-net_bitlen)) : 0; + ip_addr_network = ip_addr & mask1; + if( network == ip_addr_network ) + return net_bitlen + 1; // march 0.0.0.0/0 still march success, that case return 1 + else + return 0; +} + +// if ports march, compare cidr. if cidr ok, return sum of src&dst cidr net_bitlen. means always select larger net_bitlen record when multi record is marched. +uint8_t march_rule_and_cache_key(filter_rule_key_t *rule_key, packet_address_proto_info_t *pkt_addr_info) +{ + // march failed if proto is not configured at the rule. + switch (pkt_addr_info->proto) + { + case FPP_ICMP: + if(!rule_key->bool_icmp_configured) return 0; + break; + case FPP_UDP: + if(!rule_key->bool_udp_configured) return 0; + break; + case FPP_TCP: + if(!rule_key->bool_tcp_configured) return 0; + break; + default: + return 0; + } + + // ignore ports for ICMP proto. + if( pkt_addr_info->proto == FPP_ICMP || (rule_key->src_port_range.start_port <= pkt_addr_info->src_port + && pkt_addr_info->src_port <= rule_key->src_port_range.end_port + && rule_key->dst_port_range.start_port <= pkt_addr_info->dst_port + && pkt_addr_info->dst_port <= rule_key->dst_port_range.end_port) ) + { + uint8_t march_src_score = march_cidr_and_address(rule_key->src_net_cidr, rule_key->src_net_bit_len, pkt_addr_info->src_ip); + uint8_t march_dst_score = march_cidr_and_address(rule_key->dst_net_cidr, rule_key->dst_net_bit_len, pkt_addr_info->dst_ip); + if( march_src_score > 0 && march_dst_score > 0 ) + return march_src_score + march_dst_score; + }else{ + return 0; + } +} + +filter_rule_t* get_filter_rule(filter_rule_t **rules, packet_address_proto_info_t *pkt_addr_info) +{ + filter_rule_t *item = 0, *tmp = 0, *marched_rule = 0; + int march_score = 0; + + HASH_ITER(hh, *rules, item, tmp) { + /* ... it is safe to delete and free s here */ + uint8_t cur_march_score = march_rule_and_cache_key(&(item->key), pkt_addr_info); + if( cur_march_score > march_score ) + { + marched_rule = item; + march_score = cur_march_score; + } + } + return marched_rule; +} + +typedef struct network_traffic_filter_impl +{ + n2n_verdict (*filter_packet_from_peer)(struct network_traffic_filter* filter, n2n_edge_t *eee, const n2n_sock_t *peer, uint8_t *payload, uint16_t payload_size); + + n2n_verdict (*filter_packet_from_tap)(struct network_traffic_filter* filter, n2n_edge_t *eee, uint8_t *payload, uint16_t payload_size); + + filter_rule_t *rules; + + filter_rule_pair_cache_t *connections_rule_cache; + + uint32_t work_count_scene_last_clear; +}network_traffic_filter_impl_t; + +void update_and_clear_cache_if_need(network_traffic_filter_impl_t *filter) +{ + if( ++(filter->work_count_scene_last_clear) > CLEAR_CACHE_EVERY_X_COUNT) + { + filter_rule_pair_cache_t *item = NULL, *tmp = NULL; + HASH_ITER(hh, filter->connections_rule_cache, item, tmp) { + /* ... it is safe to delete and free s here */ + if( item->active_count < CLAER_CACHE_ACTIVE_COUNT ) + { + traceEvent(TRACE_DEBUG, "### DELETE filter cache %s", get_filter_packet_info_log_string(&item->key)); + HASH_DEL(filter->connections_rule_cache, item); + free(item); + }else{ + item->active_count = 0; + } + } + filter->work_count_scene_last_clear = 0; + } +} + +filter_rule_pair_cache_t* get_or_create_filter_rule_cache(network_traffic_filter_impl_t *filter, packet_address_proto_info_t *pkt_addr_info) +{ + filter_rule_pair_cache_t* rule_cache_find_result = 0; + HASH_FIND(hh, filter->connections_rule_cache, pkt_addr_info, sizeof(packet_address_proto_info_t), rule_cache_find_result); + if( !rule_cache_find_result ) + { + filter_rule_t* rule = get_filter_rule(&filter->rules, pkt_addr_info); + if( !rule ) + return NULL; + + rule_cache_find_result = malloc(sizeof(filter_rule_pair_cache_t)); + memset(rule_cache_find_result, 0, sizeof(filter_rule_pair_cache_t)); + rule_cache_find_result->key = *pkt_addr_info; + switch(rule_cache_find_result->key.proto) + { + case FPP_ICMP: + rule_cache_find_result->bool_allow_traffic = rule->bool_accept_icmp; + break; + case FPP_UDP: + rule_cache_find_result->bool_allow_traffic = rule->bool_accept_udp; + break; + case FPP_TCP: + rule_cache_find_result->bool_allow_traffic = rule->bool_accept_tcp; + break; + default: + traceEvent(TRACE_WARNING, "### Generate filter rule cache failed!"); + return NULL; + } + traceEvent(TRACE_DEBUG, "### ADD filter cache %s", get_filter_packet_info_log_string(&rule_cache_find_result->key)); + HASH_ADD(hh, filter->connections_rule_cache, key, sizeof(packet_address_proto_info_t), rule_cache_find_result); + } + ++(rule_cache_find_result->active_count); + update_and_clear_cache_if_need(filter); + return rule_cache_find_result; +} + + +n2n_verdict filter_packet_from_peer(network_traffic_filter_impl_t *filter, n2n_edge_t *eee, const n2n_sock_t *peer, uint8_t *payload, uint16_t payload_size) +{ + filter_rule_pair_cache_t *cur_pkt_rule = 0; + packet_address_proto_info_t pkt_info; + collect_packet_info(&pkt_info, payload, payload_size); + cur_pkt_rule = get_or_create_filter_rule_cache(filter, &pkt_info); + if( cur_pkt_rule && !cur_pkt_rule->bool_allow_traffic) + { + traceEvent(TRACE_DEBUG, "### DROP %s", get_filter_packet_info_log_string(&pkt_info)); + return N2N_DROP; + } + return N2N_ACCEPT; +} + + +n2n_verdict filter_packet_from_tap(network_traffic_filter_impl_t *filter, n2n_edge_t *eee, uint8_t *payload, uint16_t payload_size) +{ + filter_rule_pair_cache_t *cur_pkt_rule = 0; + packet_address_proto_info_t pkt_info; + collect_packet_info(&pkt_info, payload, payload_size); + cur_pkt_rule = get_or_create_filter_rule_cache(filter, &pkt_info); + if( cur_pkt_rule && !cur_pkt_rule->bool_allow_traffic) + { + traceEvent(TRACE_DEBUG, "### DROP %s", get_filter_packet_info_log_string(&pkt_info)); + return N2N_DROP; + } + return N2N_ACCEPT; +} + +network_traffic_filter_t *create_network_traffic_filter() { + network_traffic_filter_impl_t *filter = malloc(sizeof(network_traffic_filter_impl_t)); + memset(filter, 0, sizeof(network_traffic_filter_impl_t)); + filter->filter_packet_from_peer = filter_packet_from_peer; + filter->filter_packet_from_tap = filter_packet_from_tap; + return filter; +} + +void destroy_network_traffic_filter(network_traffic_filter_t *filter) { + network_traffic_filter_impl_t *_filter = filter; + + { + filter_rule_t *el = 0, *tmp = 0; + HASH_ITER(hh, _filter->rules, el, tmp) + { + HASH_DEL(_filter->rules, el); + free(el); + } + } + + { + filter_rule_pair_cache_t *el = 0, *tmp = 0; + HASH_ITER(hh, _filter->connections_rule_cache, el, tmp) + { + HASH_DEL(_filter->connections_rule_cache, el); + free(el); + } + } + + free(filter); +} + +void network_traffic_filter_add_rule(network_traffic_filter_t* filter, filter_rule_t* rules) { + filter_rule_t *item=NULL, *tmp=NULL; + HASH_ITER(hh, rules, item, tmp) { + network_traffic_filter_impl_t *_filter = filter; + filter_rule_t *new_rule = malloc(sizeof(filter_rule_t)); + memcpy(new_rule, item, sizeof(filter_rule_t)); + HASH_ADD(hh, _filter->rules, key, sizeof(filter_rule_key_t), new_rule); + traceEvent(TRACE_NORMAL, "### ADD network traffic filter %s", get_filter_rule_info_log_string(new_rule)); + } +} + +in_addr_t get_int32_addr_from_ip_string(const char* begin, const char* next_pos_of_last_char) +{ + char buf[16] = {0}; + if( next_pos_of_last_char - begin > 15 ) { + traceEvent(TRACE_WARNING, "Internal Error"); + return -1; + } + memcpy(buf, begin, next_pos_of_last_char - begin); + struct in_addr addr; + if(1 == inet_aton(buf, &addr) ) + return addr.s_addr; + else + return -1; +} + +int get_int32_from_number_string(const char* begin, const char* next_pos_of_last_char) +{ + char buf[6] = {0}; + if( next_pos_of_last_char - begin > 5 ) // max is 65535, 5 char + { + traceEvent(TRACE_WARNING, "Internal Error"); + return 0; + } + memcpy(buf, begin, next_pos_of_last_char - begin); + return atoi(buf); +} + +void process_traffic_filter_proto(const char* begin, const char* next_pos_of_last_char, filter_rule_t *rule_struct) +{ + char buf[6] = {0}; + if( next_pos_of_last_char - begin > 5 ) // max length str is "ICMP+", 5 char + { + traceEvent(TRACE_WARNING, "Internal Error"); + } + memcpy(buf, begin, next_pos_of_last_char - begin); + + if(strstr(buf, "TCP")){ + rule_struct->key.bool_tcp_configured = 1; + rule_struct->bool_accept_tcp = buf[3] == '+'; + } + else if(strstr(buf, "UDP")){ + rule_struct->key.bool_udp_configured = 1; + rule_struct->bool_accept_udp = buf[3] == '+'; + } + else if(strstr(buf, "ICMP")) + { + rule_struct->key.bool_icmp_configured = 1; + rule_struct->bool_accept_icmp = buf[4] == '+'; + } + else + traceEvent(TRACE_WARNING, "Invalid Proto : %s", buf); +} + +typedef enum +{ + FPS_SRC_NET = 1, + FPS_SRC_NET_BIT_LEN, + FPS_SRC_PORT_SINGLE, + FPS_SRC_PORT_RANGE, + FPS_SRC_PORT_START, + FPS_SRC_PORT_END, + FPS_DST_NET, + FPS_DST_NET_BIT_LEN, + FPS_DST_PORT_SINGLE, + FPS_DST_PORT_RANGE, + FPS_DST_PORT_START, + FPS_DST_PORT_END, + FPS_PROTO +} filter_process_stage; + +uint8_t process_traffic_filter_rule_str(const char *rule_str, filter_rule_t *rule_struct) { + const char *cur_pos = rule_str, *stage_begin_pos = rule_str; + filter_process_stage stage = FPS_SRC_NET; + while(1) + { + switch(stage) + { + case FPS_SRC_NET: + { + if( (*cur_pos >= '0' && *cur_pos <= '9') || *cur_pos == '.') + ; // Normal FPS_SRC_NET, next char + else if( *cur_pos == '/' ) { + // FPS_SRC_NET finish, next is FPS_SRC_NET_BIT_LEN + rule_struct->key.src_net_cidr = get_int32_addr_from_ip_string(stage_begin_pos, cur_pos); + stage_begin_pos = cur_pos + 1; + stage = FPS_SRC_NET_BIT_LEN; + }else if( *cur_pos == ':') { + // FPS_SRC_NET finish, ignore FPS_SRC_NET_BIT_LEN(default 32), next is one of FPS_SRC_PORT_RANGE/FPS_SRC_PORT_SINGLE + rule_struct->key.src_net_cidr = get_int32_addr_from_ip_string(stage_begin_pos, cur_pos); + rule_struct->key.src_net_bit_len = 32; + stage_begin_pos = cur_pos + 1; + if( *(cur_pos+1) == '[' ) + stage = FPS_SRC_PORT_RANGE; + else + stage = FPS_SRC_PORT_SINGLE; + }else if( *cur_pos == ','){ + // FPS_SRC_NET finish, ignore FPS_SRC_NET_BIT_LEN(default 32), ignore FPS_SRC_PORT(default all), + // next is FPS_DST_NET + rule_struct->key.src_net_cidr = get_int32_addr_from_ip_string(stage_begin_pos, cur_pos); + rule_struct->key.src_net_bit_len = 32; + rule_struct->key.src_port_range.start_port = 0; + rule_struct->key.src_port_range.end_port = 65535; + stage_begin_pos = cur_pos + 1; + stage = FPS_DST_NET; + } else { + traceEvent(TRACE_WARNING, "process filter rule with error char %c at pos %d", *cur_pos, cur_pos - rule_str); + return 0; + } + break; + } + case FPS_SRC_NET_BIT_LEN: + { + if( *cur_pos >= '0' && *cur_pos <= '9') + ; // Normal FPS_SRC_NET_BIT_LEN, next char + else if( *cur_pos == ':') { + // FPS_SRC_NET_BIT_LEN finish, next is one of FPS_SRC_PORT_RANGE/FPS_SRC_PORT_SINGLE + rule_struct->key.src_net_bit_len = get_int32_from_number_string(stage_begin_pos, cur_pos); + stage_begin_pos = cur_pos + 1; + if( *(cur_pos+1) == '[' ) + stage = FPS_SRC_PORT_RANGE; + else + stage = FPS_SRC_PORT_SINGLE; + }else if( *cur_pos == ','){ + // FPS_SRC_NET_BIT_LEN finish, ignore FPS_SRC_PORT(default all), next is FPS_DST_NET + rule_struct->key.src_net_bit_len = get_int32_from_number_string(stage_begin_pos, cur_pos);; + rule_struct->key.src_port_range.start_port = 0; + rule_struct->key.src_port_range.end_port = 65535; + stage_begin_pos = cur_pos + 1; + stage = FPS_DST_NET; + }else{ + traceEvent(TRACE_WARNING, "process filter rule with error char %c at pos %d", *cur_pos, cur_pos - rule_str); + return 0; + } + break; + } + case FPS_SRC_PORT_SINGLE: + { + if( *cur_pos >= '0' && *cur_pos <= '9') + ; // Normal FPS_SRC_PORT_SINGLE, next char + else if(*cur_pos == ','){ + // FPS_SRC_PORT_SINGLE finish, next is FPS_DST_NET + rule_struct->key.src_port_range.start_port = get_int32_from_number_string(stage_begin_pos, cur_pos); + rule_struct->key.src_port_range.end_port = rule_struct->key.src_port_range.start_port; + stage_begin_pos = cur_pos + 1; + stage = FPS_DST_NET; + }else{ + traceEvent(TRACE_WARNING, "process filter rule with error char %c at pos %d", *cur_pos, cur_pos - rule_str); + return 0; + } + break; + } + case FPS_SRC_PORT_RANGE: + { + if(*cur_pos == '[') + { + stage_begin_pos = cur_pos + 1; + stage = FPS_SRC_PORT_START; + }else{ + traceEvent(TRACE_WARNING, "process filter rule with error char %c at pos %d", *cur_pos, cur_pos - rule_str); + return 0; + } + break; + } + case FPS_SRC_PORT_START: + { + if( *cur_pos >= '0' && *cur_pos <= '9') + ; // Normal FPS_SRC_PORT_START, next char + else if(*cur_pos == ',') + { + // FPS_SRC_PORT_START finish, next is FPS_SRC_PORT_END + rule_struct->key.src_port_range.start_port = get_int32_from_number_string(stage_begin_pos, cur_pos); + stage_begin_pos = cur_pos + 1; + stage = FPS_SRC_PORT_END; + }else{ + traceEvent(TRACE_WARNING, "process filter rule with error char %c at pos %d", *cur_pos, cur_pos - rule_str); + return 0; + } + break; + } + case FPS_SRC_PORT_END: + { + if( *cur_pos >= '0' && *cur_pos <= '9') + ; // Normal FPS_SRC_PORT_END, next char + else if(*cur_pos == ']' && *(cur_pos + 1) == ',') + { + // FPS_SRC_PORT_END finish, next is FPS_DST_NET + rule_struct->key.src_port_range.end_port = get_int32_from_number_string(stage_begin_pos, cur_pos); + stage_begin_pos = cur_pos + 2; + stage = FPS_DST_NET; + ++cur_pos; //skip next char ',' + }else{ + traceEvent(TRACE_WARNING, "process filter rule with error char %c at pos %d", *cur_pos, cur_pos - rule_str); + return 0; + } + break; + } + case FPS_DST_NET: + { + if( (*cur_pos >= '0' && *cur_pos <= '9') || *cur_pos == '.') + ; // Normal FPS_DST_NET, next char + else if( *cur_pos == '/' ) { + // FPS_DST_NET finish, next is FPS_DST_NET_BIT_LEN + rule_struct->key.dst_net_cidr = get_int32_addr_from_ip_string(stage_begin_pos, cur_pos); + stage_begin_pos = cur_pos + 1; + stage = FPS_DST_NET_BIT_LEN; + }else if( *cur_pos == ':') { + // FPS_DST_NET finish, ignore FPS_DST_NET_BIT_LEN(default 32), next is one of FPS_DST_PORT_RANGE/FPS_DST_PORT_SINGLE + rule_struct->key.dst_net_cidr = get_int32_addr_from_ip_string(stage_begin_pos, cur_pos); + rule_struct->key.dst_net_bit_len = 32; + stage_begin_pos = cur_pos + 1; + if( *(cur_pos+1) == '[' ) + stage = FPS_DST_PORT_RANGE; + else + stage = FPS_DST_PORT_SINGLE; + }else if( *cur_pos == ',' || *cur_pos == 0){ + // FPS_DST_NET finish, ignore FPS_DST_NET_BIT_LEN(default 32), ignore FPS_DST_PORT(default all), + // next is FPS_PROTO + rule_struct->key.dst_net_cidr = get_int32_addr_from_ip_string(stage_begin_pos, cur_pos); + rule_struct->key.dst_net_bit_len = 32; + rule_struct->key.dst_port_range.start_port = 0; + rule_struct->key.dst_port_range.end_port = 65535; + stage_begin_pos = cur_pos + 1; + stage = FPS_PROTO; + } else { + traceEvent(TRACE_WARNING, "process filter rule with error char %c at pos %d", *cur_pos, cur_pos - rule_str); + return 0; + } + break; + } + case FPS_DST_NET_BIT_LEN: + { + if( *cur_pos >= '0' && *cur_pos <= '9') + ; // Normal FPS_DST_NET_BIT_LEN, next char + else if( *cur_pos == ':') { + // FPS_DST_NET_BIT_LEN finish, next is one of FPS_DST_PORT_RANGE/FPS_DST_PORT_SINGLE + rule_struct->key.dst_net_bit_len = get_int32_from_number_string(stage_begin_pos, cur_pos); + stage_begin_pos = cur_pos + 1; + if( *(cur_pos+1) == '[' ) + stage = FPS_DST_PORT_RANGE; + else + stage = FPS_DST_PORT_SINGLE; + }else if( *cur_pos == ',' || *cur_pos == 0){ + // FPS_DST_NET_BIT_LEN finish, ignore FPS_DST_PORT(default all), next is FPS_PROTO + rule_struct->key.dst_net_bit_len = get_int32_from_number_string(stage_begin_pos, cur_pos);; + rule_struct->key.dst_port_range.start_port = 0; + rule_struct->key.dst_port_range.end_port = 65535; + stage_begin_pos = cur_pos + 1; + stage = FPS_PROTO; + }else{ + traceEvent(TRACE_WARNING, "process filter rule with error char %c at pos %d", *cur_pos, cur_pos - rule_str); + return 0; + } + break; + } + case FPS_DST_PORT_SINGLE: + { + if( *cur_pos >= '0' && *cur_pos <= '9') + ; // Normal FPS_DST_PORT_SINGLE, next char + else if(*cur_pos == ',' || *cur_pos == 0){ + // FPS_DST_PORT_SINGLE finish, next is FPS_PROTO + rule_struct->key.dst_port_range.start_port = get_int32_from_number_string(stage_begin_pos, cur_pos); + rule_struct->key.dst_port_range.end_port = rule_struct->key.dst_port_range.start_port; + stage_begin_pos = cur_pos + 1; + stage = FPS_PROTO; + }else{ + traceEvent(TRACE_WARNING, "process filter rule with error char %c at pos %d", *cur_pos, cur_pos - rule_str); + return 0; + } + break; + } + case FPS_DST_PORT_RANGE: + { + if(*cur_pos == '[') + { + stage_begin_pos = cur_pos + 1; + stage = FPS_DST_PORT_START; + }else{ + traceEvent(TRACE_WARNING, "process filter rule with error char %c at pos %d", *cur_pos, cur_pos - rule_str); + return 0; + } + break; + } + case FPS_DST_PORT_START: + { + if( *cur_pos >= '0' && *cur_pos <= '9') + ; // Normal FPS_DST_PORT_START, next char + else if(*cur_pos == ',') + { + // FPS_DST_PORT_START finish, next is FPS_DST_PORT_END + rule_struct->key.dst_port_range.start_port = get_int32_from_number_string(stage_begin_pos, cur_pos); + stage_begin_pos = cur_pos + 1; + stage = FPS_DST_PORT_END; + }else{ + traceEvent(TRACE_WARNING, "process filter rule with error char %c at pos %d", *cur_pos, cur_pos - rule_str); + return 0; + } + break; + } + case FPS_DST_PORT_END: + { + if( *cur_pos >= '0' && *cur_pos <= '9') + ; // Normal FPS_DST_PORT_END, next char + else if(*cur_pos == ']') + { + // FPS_DST_PORT_END finish, next is FPS_PROTO + rule_struct->key.dst_port_range.end_port = get_int32_from_number_string(stage_begin_pos, cur_pos); + stage = FPS_PROTO; + if(*(cur_pos + 1) == ',') { + stage_begin_pos = cur_pos + 2; + ++cur_pos; //skip next char ',' + }else if(*(cur_pos + 1) != 0){ + traceEvent(TRACE_WARNING, "process filter rule with error char %c at pos %d", *cur_pos, cur_pos - rule_str); + return 0; + } + }else{ + traceEvent(TRACE_WARNING, "process filter rule with error char %c at pos %d", *cur_pos, cur_pos - rule_str); + return 0; + } + break; + } + case FPS_PROTO: + { + if(*cur_pos != '-' && *cur_pos != '+' && *cur_pos != ',') + ; // Normal FPS_PROTO. next char + else if( *cur_pos != ',' ) + { + process_traffic_filter_proto(stage_begin_pos, cur_pos + 1, rule_struct); + if( *(cur_pos+1) == 0 ) // end of whole rule string + break; + else{ // new proto info, and skip next char ',' + stage_begin_pos = cur_pos + 2; + ++cur_pos; + } + } + else { + traceEvent(TRACE_WARNING, "Internal Error: ',' should skiped", *cur_pos, cur_pos - rule_str); + return 0; + } + break; + } + } + if(0 == *cur_pos) + break; + ++cur_pos; + } + + return 1; +}