Add feature to drop or accept specific packet transmit over edge network interface by rules. (#489)

* Add new file 'network_traffic_filter.c/.h"

* Add feature to drop or accept specific packet transmit over edge network interface by rules.

* fix CMakeLists.txt typo

* Update Rule String Format

* replace -F (filter) with -R (rule) for traffic restrictions.

* Update edge help (-h)  message. Update documents.
This commit is contained in:
joshuafc 2020-11-10 23:35:05 +08:00 committed by GitHub
parent e65fd984d7
commit a840aebb83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 978 additions and 4 deletions

2
.gitignore vendored
View File

@ -25,3 +25,5 @@ packages/etc/systemd/system/edge.service
packages/etc/systemd/system/edge@.service packages/etc/systemd/system/edge@.service
packages/etc/systemd/system/supernode.service packages/etc/systemd/system/supernode.service
*dSYM* *dSYM*
cmake-build-*/

View File

@ -115,7 +115,7 @@ add_library(n2n STATIC
src/tuntap_linux.c src/tuntap_linux.c
src/tuntap_osx.c src/tuntap_osx.c
src/n2n_regex.c src/n2n_regex.c
) src/network_traffic_filter.c)
if(DEFINED WIN32) if(DEFINED WIN32)
add_library(edge_utils_win32 src/edge_utils_win32.c) add_library(edge_utils_win32 src/edge_utils_win32.c)

View File

@ -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). 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 ## Contribution
You can contribute to n2n in various ways: You can contribute to n2n in various ways:

View File

@ -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`.

25
edge.8
View File

@ -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. But this method does not always work due to various local network device policy.
.TP .TP
\-R <rule_str>
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 \-v
more verbose logging (may be specified several times for more verbosity). more verbose logging (may be specified several times for more verbosity).
.SH ENVIRONMENT .SH ENVIRONMENT

View File

@ -254,6 +254,72 @@ typedef struct n2n_edge_callbacks {
} n2n_edge_callbacks_t; } 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 { typedef struct n2n_tuntap_priv_config {
char tuntap_dev_name[N2N_IFNAMSIZ]; 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 register_ttl; /**< TTL for registration packet when UDP NAT hole punching through supernode. */
int local_port; int local_port;
int mgmt_port; int mgmt_port;
filter_rule_t *network_traffic_filter_rules;
} n2n_edge_conf_t; } n2n_edge_conf_t;
@ -347,6 +414,8 @@ struct n2n_edge {
struct n2n_edge_stats stats; /**< Statistics */ struct n2n_edge_stats stats; /**< Statistics */
n2n_tuntap_priv_config_t tuntap_priv_conf; /**< Tuntap config */ n2n_tuntap_priv_config_t tuntap_priv_conf; /**< Tuntap config */
network_traffic_filter_t *network_traffic_filter;
}; };

View File

@ -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 <http://www.gnu.org/licenses/>
*
*/
//
// Zhou Bin <joshuafc@foxmail.com>
//
#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

View File

@ -32,6 +32,7 @@
#include <sys/capability.h> #include <sys/capability.h>
#include <sys/prctl.h> #include <sys/prctl.h>
#include "network_traffic_filter.h"
static cap_value_t cap_values[] = { static cap_value_t cap_values[] = {
//CAP_NET_RAW, /* Use RAW and PACKET sockets */ //CAP_NET_RAW, /* Use RAW and PACKET sockets */
@ -143,7 +144,7 @@ static void help() {
#ifndef __APPLE__ #ifndef __APPLE__
"[-D] " "[-D] "
#endif #endif
"[-r] [-E] [-v] [-i <reg_interval>] [-L <reg_ttl>] [-t <mgmt port>] [-A[<cipher>]] [-H] [-z[<compression algo>]] [-h]\n\n"); "[-r] [-E] [-v] [-i <reg_interval>] [-L <reg_ttl>] [-t <mgmt port>] [-A[<cipher>]] [-H] [-z[<compression algo>]] [-R <rule_str>] [-h]\n\n");
#if defined(N2N_CAN_NAME_IFACE) #if defined(N2N_CAN_NAME_IFACE)
printf("-d <tap device> | tap device name\n"); printf("-d <tap device> | tap device name\n");
@ -190,6 +191,8 @@ static void help() {
#endif #endif
printf("-n <cidr:gateway> | Route an IPv4 network via the gw. Use 0.0.0.0/0 for the default gw. Can be set multiple times.\n"); printf("-n <cidr:gateway> | 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("-v | Make more verbose. Repeat as required.\n");
printf("-R <rule_str> | 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 <port> | Management UDP Port (for multiple edges on a machine).\n"); printf("-t <port> | Management UDP Port (for multiple edges on a machine).\n");
printf("\nEnvironment variables:\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); setTraceLevel(getTraceLevel() + 1);
break; 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: default:
{ {
traceEvent(TRACE_WARNING, "Unknown option -%c: Ignored", (char)optkey); 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; u_char c;
while ((c = getopt_long(argc, argv, 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__ #ifdef __linux__
"T:" "T:"
#endif #endif

View File

@ -16,6 +16,7 @@
* *
*/ */
#include "network_traffic_filter.h"
#include "n2n.h" #include "n2n.h"
#include "edge_utils_win32.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; 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: //edge_init_success:
*rv = 0; *rv = 0;
return(eee); 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) { if(eee->cb.packet_from_peer) {
uint16_t tmp_eth_size = eth_size; uint16_t tmp_eth_size = eth_size;
if(eee->cb.packet_from_peer(eee, orig_sender, eth_payload, &tmp_eth_size) == N2N_DROP) { 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); traceEvent(TRACE_DEBUG, "DROP packet %u", (unsigned int)eth_size);
return(0); return(0);
} }
eth_size = tmp_eth_size; eth_size = tmp_eth_size;
@ -1724,6 +1733,11 @@ void edge_read_from_tap(n2n_edge_t * eee) {
} }
else 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) { if(eee->cb.packet_from_tap) {
uint16_t tmp_len = len; uint16_t tmp_len = len;
if(eee->cb.packet_from_tap(eee, eth_pkt, &tmp_len) == N2N_DROP) { 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); edge_cleanup_routes(eee);
destroy_network_traffic_filter(eee->network_traffic_filter);
closeTraceFile(); closeTraceFile();
free(eee); 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) { void edge_term_conf(n2n_edge_conf_t *conf) {
if (conf->routes) free(conf->routes); if (conf->routes) free(conf->routes);
if (conf->encrypt_key) free(conf->encrypt_key); 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);
}
}
} }
/* ************************************** */ /* ************************************** */

View File

@ -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 <http://www.gnu.org/licenses/>
*
*/
#include "network_traffic_filter.h"
#include "uthash.h"
#include "netinet/tcp.h"
#include <inttypes.h>
// 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;
}