diff --git a/include/nuttx/net/netfilter/ip_tables.h b/include/nuttx/net/netfilter/ip_tables.h index 4913df7d65..70f412a5f1 100644 --- a/include/nuttx/net/netfilter/ip_tables.h +++ b/include/nuttx/net/netfilter/ip_tables.h @@ -58,6 +58,10 @@ #define IPT_INV_PROTO XT_INV_PROTO #define IPT_INV_MASK 0x7F /* All possible flag bits mask. */ +/* Values for "inv" field for struct ipt_icmp. */ + +#define IPT_ICMP_INV 0x01 /* Invert the sense of type/code test */ + /* Standard return verdict, or do jump. */ #define IPT_STANDARD_TARGET XT_STANDARD_TARGET @@ -78,8 +82,10 @@ (entry) = (FAR struct ipt_entry *) \ ((FAR uint8_t *)(entry) + (entry)->next_offset)) -/* Get pointer to target from an entry pointer. */ +/* Get pointer to match / target from an entry pointer. */ +#define IPT_MATCH(e) \ + ((FAR struct xt_entry_match *)((FAR struct ipt_entry *)(e) + 1)) #define IPT_TARGET(e) \ ((FAR struct xt_entry_target *)((FAR uint8_t *)(e) + (e)->target_offset)) @@ -88,9 +94,10 @@ #define IPT_FILL_ENTRY(e, target_name) \ do \ { \ - (e)->entry.target_offset = sizeof((e)->entry); \ + (e)->entry.target_offset = offsetof(typeof(*(e)), target); \ (e)->entry.next_offset = sizeof(*(e)); \ - (e)->target.target.u.target_size = sizeof(*(e)) - sizeof((e)->entry); \ + (e)->target.target.u.target_size = sizeof(*(e)) - \ + (e)->entry.target_offset; \ strlcpy((e)->target.target.u.user.name, (target_name), \ sizeof((e)->target.target.u.user.name)); \ } \ @@ -269,6 +276,15 @@ struct ipt_get_entries struct ipt_entry entrytable[0]; }; +/* ICMP matching stuff */ + +struct ipt_icmp +{ + uint8_t type; /* type to match */ + uint8_t code[2]; /* range of code */ + uint8_t invflags; /* Inverse flags */ +}; + /**************************************************************************** * Inline functions ****************************************************************************/ diff --git a/include/nuttx/net/netfilter/x_tables.h b/include/nuttx/net/netfilter/x_tables.h index 77ba916f98..0d33cf9b81 100644 --- a/include/nuttx/net/netfilter/x_tables.h +++ b/include/nuttx/net/netfilter/x_tables.h @@ -39,9 +39,36 @@ #define XT_INV_PROTO 0x40 /* Invert the sense of PROTO. */ +/* Values for "inv" field in struct xt_tcp. */ + +#define XT_TCP_INV_SRCPT 0x01 /* Invert the sense of source ports. */ +#define XT_TCP_INV_DSTPT 0x02 /* Invert the sense of dest ports. */ +#define XT_TCP_INV_FLAGS 0x04 /* Invert the sense of TCP flags. */ +#define XT_TCP_INV_OPTION 0x08 /* Invert the sense of option test. */ +#define XT_TCP_INV_MASK 0x0F /* All possible flags. */ + +/* Values for "invflags" field in struct xt_udp. */ + +#define XT_UDP_INV_SRCPT 0x01 /* Invert the sense of source ports. */ +#define XT_UDP_INV_DSTPT 0x02 /* Invert the sense of dest ports. */ +#define XT_UDP_INV_MASK 0x03 /* All possible flags. */ + +/* Target names */ + #define XT_STANDARD_TARGET "" /* Standard return verdict, or do jump. */ #define XT_ERROR_TARGET "ERROR" #define XT_MASQUERADE_TARGET "MASQUERADE" +#define XT_REJECT_TARGET "REJECT" + +/* Match name to simplify our code */ + +#define XT_MATCH_NAME_TCP "tcp" +#define XT_MATCH_NAME_UDP "udp" +#define XT_MATCH_NAME_ICMP "icmp" + +/* Table name to simplify our code */ + +#define XT_TABLE_NAME_FILTER "filter" /* For standard target */ @@ -156,4 +183,25 @@ struct xt_entry_match unsigned char data[1]; }; +/* TCP matching stuff */ + +struct xt_tcp +{ + uint16_t spts[2]; /* Source port range. */ + uint16_t dpts[2]; /* Destination port range. */ + uint8_t option; /* TCP Option iff non-zero */ + uint8_t flg_mask; /* TCP flags mask byte */ + uint8_t flg_cmp; /* TCP flags compare byte */ + uint8_t invflags; /* Inverse flags */ +}; + +/* UDP matching stuff */ + +struct xt_udp +{ + uint16_t spts[2]; /* Source port range. */ + uint16_t dpts[2]; /* Destination port range. */ + uint8_t invflags; /* Inverse flags */ +}; + #endif /* __INCLUDE_NUTTX_NET_NETFILTER_X_TABLES_H */ diff --git a/net/netfilter/CMakeLists.txt b/net/netfilter/CMakeLists.txt index e68d60015c..46a13bf751 100644 --- a/net/netfilter/CMakeLists.txt +++ b/net/netfilter/CMakeLists.txt @@ -28,5 +28,9 @@ if(CONFIG_NET_IPTABLES) list(APPEND SRCS ipt_nat.c) endif() + if(CONFIG_NET_IPFILTER) + list(APPEND SRCS ipt_filter.c) + endif() + target_sources(net PRIVATE ${SRCS}) endif() diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig index 1ba5ddbd2e..4add680eb1 100644 --- a/net/netfilter/Kconfig +++ b/net/netfilter/Kconfig @@ -8,6 +8,6 @@ config NET_IPTABLES default y depends on NET_IPv4 depends on NET_SOCKOPTS - depends on NET_NAT # May change dependency if we have firewall later. + depends on NET_NAT || NET_IPFILTER ---help--- Enable or disable iptables compatible interface (for NAT). diff --git a/net/netfilter/Make.defs b/net/netfilter/Make.defs index 02d250a4b8..505052eedb 100644 --- a/net/netfilter/Make.defs +++ b/net/netfilter/Make.defs @@ -28,6 +28,10 @@ ifeq ($(CONFIG_NET_NAT),y) NET_CSRCS += ipt_nat.c endif +ifeq ($(CONFIG_NET_IPFILTER),y) +NET_CSRCS += ipt_filter.c +endif + # Include Netfilter build support DEPPATH += --dep-path netfilter diff --git a/net/netfilter/ipt_filter.c b/net/netfilter/ipt_filter.c new file mode 100644 index 0000000000..3f991750ff --- /dev/null +++ b/net/netfilter/ipt_filter.c @@ -0,0 +1,408 @@ +/**************************************************************************** + * net/netfilter/ipt_filter.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "ipfilter/ipfilter.h" +#include "netdev/netdev.h" +#include "netfilter/iptables.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define FILTER_VALID_HOOKS ((1 << NF_INET_LOCAL_IN) | \ + (1 << NF_INET_FORWARD) | \ + (1 << NF_INET_LOCAL_OUT)) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: convert_chain + * + * Description: + * Convert iptables chain to ipfilter chain. + * + ****************************************************************************/ + +static enum ipfilter_chain_e convert_chain(enum nf_inet_hooks hook) +{ + switch (hook) + { + case NF_INET_LOCAL_IN: + return IPFILTER_CHAIN_INPUT; + + case NF_INET_FORWARD: + return IPFILTER_CHAIN_FORWARD; + + case NF_INET_LOCAL_OUT: + default: + return IPFILTER_CHAIN_OUTPUT; + } +} + +/**************************************************************************** + * Name: convert_invflags + * + * Description: + * Convert iptables invflags to ipfilter invflags. + * + * Input Parameters: + * entry - The ipfilter entry to be filled. + * invflags - The iptables invflags to be converted. + * + ****************************************************************************/ + +static void convert_invflags(FAR struct ipfilter_entry_s *entry, + uint8_t invflags) +{ + entry->inv_indev = !!(invflags & IPT_INV_VIA_IN); + entry->inv_outdev = !!(invflags & IPT_INV_VIA_OUT); + entry->inv_proto = !!(invflags & IPT_INV_PROTO); + entry->inv_srcip = !!(invflags & IPT_INV_SRCIP); + entry->inv_dstip = !!(invflags & IPT_INV_DSTIP); +} + +/**************************************************************************** + * Name: convert_tcpudp + * + * Description: + * Convert iptables tcp/udp match to ipfilter entry. + * + * Input Parameters: + * entry - The ipfilter entry to be filled. + * spts - The source ports to be converted. + * dpts - The destination ports to be converted. + * invflags - The iptables tcp/udp invflags to be converted. + * + ****************************************************************************/ + +static void convert_tcpudp(FAR struct ipfilter_entry_s *entry, + uint16_t spts[2], uint16_t dpts[2], + uint8_t invflags) +{ + entry->match.tcpudp.sports[0] = spts[0]; + entry->match.tcpudp.sports[1] = spts[1]; + entry->match.tcpudp.dports[0] = dpts[0]; + entry->match.tcpudp.dports[1] = dpts[1]; + + entry->inv_sport = !!(invflags & XT_TCP_INV_SRCPT); + entry->inv_dport = !!(invflags & XT_TCP_INV_DSTPT); + + entry->match_tcpudp = 1; +} + +/**************************************************************************** + * Name: convert_icmp + * + * Description: + * Convert iptables icmp match to ipfilter entry. + * + * Input Parameters: + * entry - The ipfilter entry to be filled. + * type - The icmp type to be converted. + * invflags - The iptables icmp invflags to be converted. + * + ****************************************************************************/ + +static void convert_icmp(FAR struct ipfilter_entry_s *entry, uint8_t type, + uint8_t invflags) +{ + entry->match.icmp.type = type; + entry->inv_icmp = !!(invflags & IPT_ICMP_INV); + entry->match_icmp = 1; +} + +/**************************************************************************** + * Name: convert_target + * + * Description: + * Convert iptables target to ipfilter target. + * + * Input Parameters: + * target - The iptables target to be converted. + * + * Returned Value: + * The converted ipfilter target. + * + ****************************************************************************/ + +static uint8_t convert_target(FAR const struct xt_entry_target *target) +{ + if (strcmp(target->u.user.name, XT_REJECT_TARGET) == 0) + { + return IPFILTER_TARGET_REJECT; + } + + if (strcmp(target->u.user.name, XT_STANDARD_TARGET) == 0) + { + int verdict = ((FAR const struct xt_standard_target *)target)->verdict; + verdict = -verdict - 1; + + if (verdict == NF_ACCEPT) + { + return IPFILTER_TARGET_ACCEPT; + } + else if (verdict == NF_DROP) + { + return IPFILTER_TARGET_DROP; + } + } + + nwarn("WARNING: Unsupported target %s\n", target->u.user.name); + return IPFILTER_TARGET_DROP; +} + +/**************************************************************************** + * Name: convert_entry + * + * Description: + * Convert iptables entry to ipfilter entry. + * + * Input Parameters: + * entry - The iptables entry to be converted. + * + * Returned Value: + * The converted ipfilter entry. + * + ****************************************************************************/ + +static FAR struct ipv4_filter_entry_s * +convert_entry(FAR const struct ipt_entry *entry) +{ + FAR const struct xt_entry_match *match; + FAR const struct xt_entry_target *target; + FAR struct ipv4_filter_entry_s *filter = + (FAR struct ipv4_filter_entry_s *)ipfilter_cfg_alloc(PF_INET); + if (filter == NULL) + { + return NULL; + } + + match = IPT_MATCH(entry); + target = IPT_TARGET(entry); + + /* Convert common fields */ + + filter->sip = entry->ip.src.s_addr; + filter->dip = entry->ip.dst.s_addr; + filter->smsk = entry->ip.smsk.s_addr; + filter->dmsk = entry->ip.dmsk.s_addr; + + filter->common.indev = netdev_findbyname(entry->ip.iniface); + filter->common.outdev = netdev_findbyname(entry->ip.outiface); + filter->common.proto = entry->ip.proto; + filter->common.target = convert_target(target); + + convert_invflags(&filter->common, entry->ip.invflags); + + /* Convert match fields */ + + if (entry->target_offset < sizeof(struct xt_entry_match)) + { + ninfo("No match inside entry, skip match conversion.\n"); + goto skip_match; + } + + switch (entry->ip.proto) + { + case IPPROTO_TCP: + if (strcmp(match->u.user.name, XT_MATCH_NAME_TCP) == 0) + { + FAR struct xt_tcp *tcp = (FAR struct xt_tcp *)(match + 1); + convert_tcpudp(&filter->common, tcp->spts, tcp->dpts, + tcp->invflags); + } + break; + + case IPPROTO_UDP: + if (strcmp(match->u.user.name, XT_MATCH_NAME_TCP) == 0) + { + FAR struct xt_udp *udp = (FAR struct xt_udp *)(match + 1); + convert_tcpudp(&filter->common, udp->spts, udp->dpts, + udp->invflags); + } + break; + + case IPPROTO_ICMP: + if (strcmp(match->u.user.name, XT_MATCH_NAME_ICMP) == 0) + { + FAR struct ipt_icmp *icmp = (FAR struct ipt_icmp *)(match + 1); + convert_icmp(&filter->common, icmp->type, icmp->invflags); + } + break; + + default: + break; + } + +skip_match: + return filter; +} + +/**************************************************************************** + * Name: adjust_filter + * + * Description: + * Adjust filter config according to the iptables config. + * + * Input Parameters: + * repl - The config got from user space to control filter table. + * + ****************************************************************************/ + +static void adjust_filter(FAR const struct ipt_replace *repl) +{ + FAR const struct ipt_entry *entry; + FAR const uint8_t *head; + enum ipfilter_chain_e chain; + enum nf_inet_hooks hook; + size_t size; + + for (hook = NF_INET_LOCAL_IN; hook <= NF_INET_LOCAL_OUT; hook++) + { + /* Clear all filter config first. */ + + chain = convert_chain(hook); + ipfilter_cfg_clear(PF_INET, chain); + + /* Set filter config according to iptables config. */ + + head = (FAR const uint8_t *)repl->entries + repl->hook_entry[hook]; + size = repl->underflow[hook] - repl->hook_entry[hook]; + + /* We need the underflow entry as the default of the chain. */ + + size++; + + ipt_entry_for_every(entry, head, size) + { + FAR struct ipv4_filter_entry_s *filter = convert_entry(entry); + if (filter != NULL) + { + ipfilter_cfg_add(&filter->common, PF_INET, chain); + } + else + { + nwarn("WARNING: Failed to convert entry!\n"); + } + } + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ipt_filter_init + * + * Description: + * Init filter table data. + * + ****************************************************************************/ + +FAR struct ipt_replace *ipt_filter_init(void) +{ + return ipt_alloc_table(XT_TABLE_NAME_FILTER, FILTER_VALID_HOOKS); +} + +/**************************************************************************** + * Name: ipt_filter_apply + * + * Description: + * Try to apply filter rules, will do nothing if failed. + * + * Input Parameters: + * repl - The config got from user space to control filter table. + * + ****************************************************************************/ + +int ipt_filter_apply(FAR const struct ipt_replace *repl) +{ + FAR const struct ipt_entry *entry; + FAR const struct xt_entry_match *match; + FAR const struct xt_entry_target *target; + + /* Check config first. */ + + ipt_entry_for_every(entry, repl->entries, repl->size) + { + match = IPT_MATCH(entry); + target = IPT_TARGET(entry); + + /* Check match type matches the protocol */ + + if (entry->target_offset >= sizeof(struct xt_entry_match)) + { + if (strcmp(match->u.user.name, XT_MATCH_NAME_TCP) == 0 && + entry->ip.proto != IPPROTO_TCP) + { + nwarn("WARNING: TCP match for non-TCP protocol\n"); + return -EINVAL; + } + + if (strcmp(match->u.user.name, XT_MATCH_NAME_UDP) == 0 && + entry->ip.proto != IPPROTO_UDP) + { + nwarn("WARNING: UDP match for non-UDP protocol\n"); + return -EINVAL; + } + + if (strcmp(match->u.user.name, XT_MATCH_NAME_ICMP) == 0 && + entry->ip.proto != IPPROTO_ICMP) + { + nwarn("WARNING: ICMP match for non-ICMP protocol\n"); + return -EINVAL; + } + } + + /* Check target type */ + + if (strcmp(target->u.user.name, XT_REJECT_TARGET) != 0 && + strcmp(target->u.user.name, XT_STANDARD_TARGET) != 0 && + strcmp(target->u.user.name, XT_ERROR_TARGET) != 0) + { + nwarn("WARNING: Unsupported target %s\n", target->u.user.name); + return -EINVAL; + } + } + + /* Set config table into ip filter. */ + + adjust_filter(repl); + + return OK; +} diff --git a/net/netfilter/ipt_sockopt.c b/net/netfilter/ipt_sockopt.c index 52476c1670..f4c0f48472 100644 --- a/net/netfilter/ipt_sockopt.c +++ b/net/netfilter/ipt_sockopt.c @@ -81,6 +81,9 @@ static struct ipt_table_s g_tables[] = #ifdef CONFIG_NET_NAT {NULL, ipt_nat_init, ipt_nat_apply}, #endif +#ifdef CONFIG_NET_IPFILTER + {NULL, ipt_filter_init, ipt_filter_apply}, +#endif }; /**************************************************************************** diff --git a/net/netfilter/iptables.h b/net/netfilter/iptables.h index de6d945f62..d33e3e1bcc 100644 --- a/net/netfilter/iptables.h +++ b/net/netfilter/iptables.h @@ -105,5 +105,32 @@ FAR struct ipt_replace *ipt_nat_init(void); int ipt_nat_apply(FAR const struct ipt_replace *repl); #endif +/**************************************************************************** + * Name: ipt_filter_init + * + * Description: + * Init filter table data. + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IPFILTER +FAR struct ipt_replace *ipt_filter_init(void); +#endif + +/**************************************************************************** + * Name: ipt_filter_apply + * + * Description: + * Try to apply filter rules, will do nothing if failed. + * + * Input Parameters: + * repl - The config got from user space to control filter table. + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IPFILTER +int ipt_filter_apply(FAR const struct ipt_replace *repl); +#endif + #endif /* CONFIG_NET_IPTABLES */ #endif /* __NET_NETFILTER_IPTABLES_H */