From f7181676b7d9917b725dafe896a39111d11b5733 Mon Sep 17 00:00:00 2001 From: Zhe Weng Date: Thu, 8 Feb 2024 16:00:00 +0800 Subject: [PATCH] net: Support IP packet filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a firewall compatible with Linux's iptables and ip6tables, with chains at similar points in the packet processing path. NIC ─> ipv[46]_input ┬> ipv[46]_forward ─> [FORWARD] ┬> devif_poll_out ─> NIC │ │ │ ┌> tcp ┐ │ │ ├> udp ┤ │ └> [INPUT] ┼> icmp ┼> [OUTPUT] ┘ ├> icmp6 ┤ └> ... ┘ Signed-off-by: Zhe Weng --- net/Kconfig | 1 + net/Makefile | 1 + net/devif/devif_poll.c | 35 +- net/devif/ipv4_input.c | 15 +- net/devif/ipv6_input.c | 15 +- net/ipfilter/CMakeLists.txt | 27 ++ net/ipfilter/Kconfig | 16 + net/ipfilter/Make.defs | 32 ++ net/ipfilter/ipfilter.c | 632 +++++++++++++++++++++++++++++++++++ net/ipfilter/ipfilter.h | 266 +++++++++++++++ net/ipforward/ipv4_forward.c | 22 ++ net/ipforward/ipv6_forward.c | 22 ++ 12 files changed, 1078 insertions(+), 6 deletions(-) create mode 100644 net/ipfilter/CMakeLists.txt create mode 100644 net/ipfilter/Kconfig create mode 100644 net/ipfilter/Make.defs create mode 100644 net/ipfilter/ipfilter.c create mode 100644 net/ipfilter/ipfilter.h diff --git a/net/Kconfig b/net/Kconfig index d07ae2c7e4..5961a091bd 100644 --- a/net/Kconfig +++ b/net/Kconfig @@ -359,6 +359,7 @@ menuconfig NET_6LOWPAN source "net/sixlowpan/Kconfig" source "net/ipforward/Kconfig" source "net/nat/Kconfig" +source "net/ipfilter/Kconfig" source "net/netfilter/Kconfig" source "net/ipfrag/Kconfig" diff --git a/net/Makefile b/net/Makefile index 0a8463bd7e..7d81fa8bc3 100644 --- a/net/Makefile +++ b/net/Makefile @@ -47,6 +47,7 @@ include sixlowpan/Make.defs include bluetooth/Make.defs include ieee802154/Make.defs include devif/Make.defs +include ipfilter/Make.defs include ipforward/Make.defs include nat/Make.defs include netfilter/Make.defs diff --git a/net/devif/devif_poll.c b/net/devif/devif_poll.c index 8be17bce97..b33a957c12 100644 --- a/net/devif/devif_poll.c +++ b/net/devif/devif_poll.c @@ -47,6 +47,7 @@ #include "mld/mld.h" #include "ipforward/ipforward.h" #include "sixlowpan/sixlowpan.h" +#include "ipfilter/ipfilter.h" #include "ipfrag/ipfrag.h" #include "inet/inet.h" @@ -184,6 +185,32 @@ static void devif_packet_conversion(FAR struct net_driver_s *dev, # define devif_packet_conversion(dev,pkttype) #endif /* CONFIG_NET_6LOWPAN */ +/**************************************************************************** + * Name: devif_poll_local_out + * + * Description: + * Generic callback before device output to build L2 headers before sending + * with packet filter for TCP/UDP/ICMP(v6). + * + * Assumptions: + * This function is called from the MAC device driver with the network + * locked. + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IPFILTER +static int devif_poll_local_out(FAR struct net_driver_s *dev, + devif_poll_callback_t callback) +{ + /* Maybe we need to reply REJECT to ourself, so filter before loopback. */ + + ipfilter_out(dev); + return devif_poll_out(dev, callback); +} +#else +# define devif_poll_local_out(dev, callback) devif_poll_out(dev, callback) +#endif /* CONFIG_NET_IPFILTER */ + /**************************************************************************** * Name: devif_poll_pkt_connections * @@ -397,7 +424,7 @@ static inline int devif_poll_icmp(FAR struct net_driver_s *dev, /* Call back into the driver */ - bstop = devif_poll_out(dev, callback); + bstop = devif_poll_local_out(dev, callback); } } @@ -444,7 +471,7 @@ static inline int devif_poll_icmpv6(FAR struct net_driver_s *dev, /* Call back into the driver */ - bstop = devif_poll_out(dev, callback); + bstop = devif_poll_local_out(dev, callback); } } while (!bstop && (conn = icmpv6_nextconn(conn)) != NULL); @@ -581,7 +608,7 @@ static int devif_poll_udp_connections(FAR struct net_driver_s *dev, /* Call back into the driver */ - bstop = devif_poll_out(dev, callback); + bstop = devif_poll_local_out(dev, callback); } } @@ -626,7 +653,7 @@ static inline int devif_poll_tcp_connections(FAR struct net_driver_s *dev, /* Call back into the driver */ - bstop = devif_poll_out(dev, callback); + bstop = devif_poll_local_out(dev, callback); } } diff --git a/net/devif/ipv4_input.c b/net/devif/ipv4_input.c index 0875c786a8..cced7577ee 100644 --- a/net/devif/ipv4_input.c +++ b/net/devif/ipv4_input.c @@ -105,6 +105,7 @@ #include "ipforward/ipforward.h" #include "devif/devif.h" #include "nat/nat.h" +#include "ipfilter/ipfilter.h" #include "ipfrag/ipfrag.h" #include "utils/utils.h" @@ -392,6 +393,14 @@ static int ipv4_in(FAR struct net_driver_s *dev) goto drop; } +#ifdef CONFIG_NET_IPFILTER + if (ipv4_filter_in(dev) != IPFILTER_TARGET_ACCEPT) + { + ninfo("Drop/Reject INPUT packet due to filter.\n"); + goto done; + } +#endif + /* Now process the incoming packet according to the protocol. */ switch (ipv4->proto) @@ -434,7 +443,11 @@ static int ipv4_in(FAR struct net_driver_s *dev) goto drop; } -#if defined(CONFIG_NET_IPFORWARD) || \ +#ifdef CONFIG_NET_IPFILTER + ipfilter_out(dev); +#endif + +#if defined(CONFIG_NET_IPFORWARD) || defined(CONFIG_NET_IPFILTER) || \ (defined(CONFIG_NET_BROADCAST) && defined(NET_UDP_HAVE_STACK)) done: #endif diff --git a/net/devif/ipv6_input.c b/net/devif/ipv6_input.c index b6e133c78d..41a662ce65 100644 --- a/net/devif/ipv6_input.c +++ b/net/devif/ipv6_input.c @@ -52,6 +52,7 @@ #include "ipforward/ipforward.h" #include "inet/inet.h" #include "devif/devif.h" +#include "ipfilter/ipfilter.h" #include "ipfrag/ipfrag.h" /**************************************************************************** @@ -420,6 +421,14 @@ static int ipv6_in(FAR struct net_driver_s *dev) } #endif +#ifdef CONFIG_NET_IPFILTER + if (ipv6_filter_in(dev) != IPFILTER_TARGET_ACCEPT) + { + ninfo("Drop/Reject INPUT packet due to filter.\n"); + goto done; + } +#endif + /* Now process the incoming packet according to the protocol specified in * the next header IPv6 field. */ @@ -518,7 +527,11 @@ static int ipv6_in(FAR struct net_driver_s *dev) goto drop; } -#ifdef CONFIG_NET_IPFORWARD +#ifdef CONFIG_NET_IPFILTER + ipfilter_out(dev); +#endif + +#if defined(CONFIG_NET_IPFORWARD) || defined(CONFIG_NET_IPFILTER) done: #endif diff --git a/net/ipfilter/CMakeLists.txt b/net/ipfilter/CMakeLists.txt new file mode 100644 index 0000000000..4843a5f114 --- /dev/null +++ b/net/ipfilter/CMakeLists.txt @@ -0,0 +1,27 @@ +# ############################################################################## +# net/ipfilter/CMakeLists.txt +# +# 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. +# +# ############################################################################## + +# IP filter source files + +if(CONFIG_NET_IPFILTER) + + target_sources(net PRIVATE ipfilter.c) + +endif() diff --git a/net/ipfilter/Kconfig b/net/ipfilter/Kconfig new file mode 100644 index 0000000000..0489e62a1a --- /dev/null +++ b/net/ipfilter/Kconfig @@ -0,0 +1,16 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config NET_IPFILTER + bool "Enable IP packet filter (firewall)" + default n + depends on NET_IPv4 || NET_IPv6 + ---help--- + Enable this option to enable the IP packet filter (firewall). + Our IP packet filter is a netfilter-like packet filter that + operates on the IP (and transport) layer. It is a stateless + packet filter that can be used to filter packets based on + source and destination IP addresses, source and destination + ports, protocol, and interface. diff --git a/net/ipfilter/Make.defs b/net/ipfilter/Make.defs new file mode 100644 index 0000000000..57b16b1865 --- /dev/null +++ b/net/ipfilter/Make.defs @@ -0,0 +1,32 @@ +############################################################################ +# net/ipfilter/Make.defs +# +# 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. +# +############################################################################ + +# IP filter source files + +ifeq ($(CONFIG_NET_IPFILTER),y) + +NET_CSRCS += ipfilter.c + +# Include IP filter build support + +DEPPATH += --dep-path ipfilter +VPATH += :ipfilter + +endif diff --git a/net/ipfilter/ipfilter.c b/net/ipfilter/ipfilter.c new file mode 100644 index 0000000000..8bcc89864d --- /dev/null +++ b/net/ipfilter/ipfilter.c @@ -0,0 +1,632 @@ +/**************************************************************************** + * net/ipfilter/ipfilter.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 "icmp/icmp.h" +#include "icmpv6/icmpv6.h" +#include "ipfilter/ipfilter.h" +#include "utils/utils.h" + +#ifdef CONFIG_NET_IPFILTER + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define SPORT_MATCH(entry, sport) \ + (((sport) >= (entry)->match.tcpudp.sports[0] && \ + (sport) <= (entry)->match.tcpudp.sports[1]) ^ (entry)->inv_sport) + +#define DPORT_MATCH(entry, dport) \ + (((dport) >= (entry)->match.tcpudp.dports[0] && \ + (dport) <= (entry)->match.tcpudp.dports[1]) ^ (entry)->inv_dport) + +#define ICMP_MATCH(entry, icmphdr) \ + (((entry)->match.icmp.type == 0xFF || \ + (entry)->match.icmp.type == (icmphdr)->type) ^ (entry)->inv_icmp) + +/* Getting L4 header from IPv4/IPv6 header. */ + +#define IPv4_L4HDR(ipv4) \ + ((FAR void *)((FAR uint8_t *)(ipv4) + (((ipv4)->vhl & IPv4_HLMASK) << 2))) + +#define IPv6_L4HDR(ipv6, proto) \ + ((FAR void *)(net_ipv6_payload((FAR struct ipv6_hdr_s *)(ipv6), &(proto)))) + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +#ifdef CONFIG_NET_IPv4 +static sq_queue_t g_ipv4_filters[IPFILTER_CHAIN_MAX]; +#endif +#ifdef CONFIG_NET_IPv6 +static sq_queue_t g_ipv6_filters[IPFILTER_CHAIN_MAX]; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ipfilter_match_device + * + * Description: + * Match the packet with the filter entries on devices. + * + * Input Parameters: + * entry - The filter entry to match + * indev - The network device that the packet comes from + * outdev - The network device that the packet goes to + * + * Returned Value: + * true - The input packet is matched + * false - The input packet is not matched + * + ****************************************************************************/ + +static bool ipfilter_match_device(FAR const struct ipfilter_entry_s *entry, + FAR const struct net_driver_s *indev, + FAR const struct net_driver_s *outdev) +{ + bool matched; + + if (indev != NULL && entry->indev != NULL) + { + matched = (indev == entry->indev) ^ entry->inv_indev; + if (!matched) + { + return false; + } + } + + if (outdev != NULL && entry->outdev != NULL) + { + matched = (outdev == entry->outdev) ^ entry->inv_outdev; + if (!matched) + { + return false; + } + } + + return true; +} + +/**************************************************************************** + * Name: ipfilter_match_proto + * + * Description: + * + * Input Parameters: + * + * Returned Value: + * + ****************************************************************************/ + +static bool ipfilter_match_proto(FAR const struct ipfilter_entry_s *entry, + FAR const void *l4hdr, uint8_t proto) +{ + bool matched; + + if (entry->proto) + { + matched = (entry->proto == proto) ^ entry->inv_proto; + if (!matched) + { + return false; + } + } + + if (entry->inv_proto) + { + /* Cannot match port/type for inversed proto, just success. */ + + return true; + } + + switch (proto) + { + case IP_PROTO_TCP: + case IP_PROTO_UDP: + if (entry->match_tcpudp) + { + /* Ports in TCP & UDP headers have same offset. */ + + FAR const struct udp_hdr_s *udp = l4hdr; + return SPORT_MATCH(entry, NTOHS(udp->srcport)) && + DPORT_MATCH(entry, NTOHS(udp->destport)); + } + + case IP_PROTO_ICMP: + if (entry->match_icmp) + { + FAR const struct icmp_hdr_s *icmp = l4hdr; + return ICMP_MATCH(entry, icmp); + } + + case IP_PROTO_ICMP6: + if (entry->match_icmp) + { + FAR const struct icmpv6_hdr_s *icmpv6 = l4hdr; + return ICMP_MATCH(entry, icmpv6); + } + + default: + return true; + } +} + +/**************************************************************************** + * Name: ipv4_filter_match / ipv6_filter_match + * + * Description: + * Match the input packet with the filter entries in the specified chain. + * + * Input Parameters: + * indev - The network device that the packet comes from + * outdev - The network device that the packet goes to + * ipv4/ipv6 - The IPv4/IPv6 header + * chain - The chain to match the filter entries + * + * Returned Value: + * IPFILTER_TARGET_ACCEPT(0) - The input packet is accepted + * IPFILTER_TARGET_DROP(-1) - The input packet needs to be dropped + * IPFILTER_TARGET_REJECT(-2) - The input packet is rejected + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IPv4 +static int ipv4_filter_match(FAR const struct net_driver_s *indev, + FAR const struct net_driver_s *outdev, + FAR const struct ipv4_hdr_s *ipv4, + enum ipfilter_chain_e chain) +{ + FAR const struct ipv4_filter_entry_s *filter; + FAR const sq_queue_t *queue = &g_ipv4_filters[chain]; + FAR const sq_entry_t *entry; + FAR const void *l4hdr; + in_addr_t ipaddr; + bool matched; + + /* Handle unexpected status, return ACCEPT to indicate doing nothing. */ + + if ((indev == NULL && outdev == NULL) || ipv4 == NULL) + { + return IPFILTER_TARGET_ACCEPT; + } + + l4hdr = IPv4_L4HDR(ipv4); + + sq_for_every(queue, entry) + { + filter = (FAR struct ipv4_filter_entry_s *)entry; + + /* Match device */ + + if (!ipfilter_match_device(&filter->common, indev, outdev)) + { + continue; + } + + /* Match addresses */ + + ipaddr = net_ip4addr_conv32(ipv4->srcipaddr); + matched = net_ipv4addr_maskcmp(filter->sip, ipaddr, filter->smsk) + ^ filter->common.inv_srcip; + if (!matched) + { + continue; + } + + ipaddr = net_ip4addr_conv32(ipv4->destipaddr); + matched = net_ipv4addr_maskcmp(filter->dip, ipaddr, filter->dmsk) + ^ filter->common.inv_dstip; + if (!matched) + { + continue; + } + + /* Match protocol */ + + if (!ipfilter_match_proto(&filter->common, l4hdr, ipv4->proto)) + { + continue; + } + + /* Return the target action if matched. */ + + return filter->common.target; + } + + /* Normally there should be a default rule in chain, won't reach here. */ + + ninfo("No filter matched, maybe uninitialized.\n"); + return IPFILTER_TARGET_ACCEPT; +} +#endif + +#ifdef CONFIG_NET_IPv6 +static int ipv6_filter_match(FAR const struct net_driver_s *indev, + FAR const struct net_driver_s *outdev, + FAR const struct ipv6_hdr_s *ipv6, + enum ipfilter_chain_e chain) +{ + FAR const struct ipv6_filter_entry_s *filter; + FAR const sq_queue_t *queue = &g_ipv6_filters[chain]; + FAR const sq_entry_t *entry; + FAR const void *l4hdr; + uint8_t proto; + bool matched; + + /* Handle unexpected status, return ACCEPT to indicate doing nothing. */ + + if ((indev == NULL && outdev == NULL) || ipv6 == NULL) + { + return IPFILTER_TARGET_ACCEPT; + } + + l4hdr = IPv6_L4HDR(ipv6, proto); + + sq_for_every(queue, entry) + { + filter = (FAR struct ipv6_filter_entry_s *)entry; + + /* Match device */ + + if (!ipfilter_match_device(&filter->common, indev, outdev)) + { + continue; + } + + /* Match addresses */ + + matched = net_ipv6addr_maskcmp(filter->sip, ipv6->srcipaddr, + filter->smsk) + ^ filter->common.inv_srcip; + if (!matched) + { + continue; + } + + matched = net_ipv6addr_maskcmp(filter->dip, ipv6->destipaddr, + filter->dmsk) + ^ filter->common.inv_dstip; + if (!matched) + { + continue; + } + + /* Match protocol */ + + if (!ipfilter_match_proto(&filter->common, l4hdr, proto)) + { + continue; + } + + /* Return the target action if matched. */ + + return filter->common.target; + } + + /* Normally there should be a default rule in chain, won't reach here. */ + + ninfo("No filter matched, maybe uninitialized.\n"); + return IPFILTER_TARGET_ACCEPT; +} +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ipfilter_cfg_alloc + * + * Description: + * Allocate a new filter configuration entry for the given address family. + * + * Input Parameters: + * family - The address family of the filter entry + * + * Returned Value: + * A pointer to the newly allocated filter entry. NULL is returned on + * failure. + * + ****************************************************************************/ + +FAR struct ipfilter_entry_s *ipfilter_cfg_alloc(sa_family_t family) +{ + /* We may optimize alloc / free later if we really have lots of configs. */ + +#ifdef CONFIG_NET_IPv4 + if (family == PF_INET) + { + return kmm_zalloc(sizeof(struct ipv4_filter_entry_s)); + } +#endif + +#ifdef CONFIG_NET_IPv6 + if (family == PF_INET6) + { + return kmm_zalloc(sizeof(struct ipv6_filter_entry_s)); + } +#endif + + return NULL; +} + +/**************************************************************************** + * Name: ipfilter_cfg_add + * + * Description: + * Add a new filter configuration entry for the given address family to the + * end of specified chain. + * + * Input Parameters: + * entry - The filter entry to add + * family - The address family of the filter entry + * chain - The chain to add the filter entry to + * + * Returned Value: + * None + * + ****************************************************************************/ + +void ipfilter_cfg_add(FAR struct ipfilter_entry_s *entry, + sa_family_t family, enum ipfilter_chain_e chain) +{ +#ifdef CONFIG_NET_IPv4 + if (family == PF_INET) + { + sq_addlast((FAR sq_entry_t *)entry, &g_ipv4_filters[chain]); + } +#endif + +#ifdef CONFIG_NET_IPv6 + if (family == PF_INET6) + { + sq_addlast((FAR sq_entry_t *)entry, &g_ipv6_filters[chain]); + } +#endif +} + +/**************************************************************************** + * Name: ipfilter_cfg_clear + * + * Description: + * Clear all filter configuration entries for the given address family from + * the specified chain. + * + * Input Parameters: + * family - The address family of the filter entry to clear + * chain - The chain to clear the filter entries from + * + * Returned Value: + * None + * + ****************************************************************************/ + +void ipfilter_cfg_clear(sa_family_t family, enum ipfilter_chain_e chain) +{ +#ifdef CONFIG_NET_IPv4 + if (family == PF_INET) + { + FAR sq_queue_t *queue = &g_ipv4_filters[chain]; + while (!sq_empty(queue)) + { + kmm_free(sq_remfirst(queue)); + } + } +#endif + +#ifdef CONFIG_NET_IPv6 + if (family == PF_INET6) + { + FAR sq_queue_t *queue = &g_ipv6_filters[chain]; + while (!sq_empty(queue)) + { + kmm_free(sq_remfirst(queue)); + } + } +#endif +} + +/**************************************************************************** + * Name: ipv4_filter_in / ipv6_filter_in + * + * Description: + * Handling IPv4/IPv6 filter on local input. Do nothing if the input + * packet is accepted, set d_len to 0 if the input packet needs to be + * dropped, and set d_iob to reject reply if the input packet is rejected. + * + * Input Parameters: + * dev - The network device that the packet comes from + * + * Returned Value: + * IPFILTER_TARGET_ACCEPT(0) - The input packet is accepted + * IPFILTER_TARGET_DROP(-1) - The input packet needs to be dropped + * IPFILTER_TARGET_REJECT(-2) - The input packet is rejected + * + * Assumptions: + * The network is locked. The d_iob and d_len in the dev are set. + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IPv4 +int ipv4_filter_in(FAR struct net_driver_s *dev) +{ + FAR struct ipv4_hdr_s *ipv4 = IPv4BUF; + int ret = ipv4_filter_match(dev, NULL, ipv4, IPFILTER_CHAIN_INPUT); + + if (ret == IPFILTER_TARGET_DROP) + { + dev->d_len = 0; + } + + if (ret == IPFILTER_TARGET_REJECT) + { + /* TODO: Support more --reject-with types later. */ + + icmp_reply(dev, ICMP_DEST_UNREACHABLE, ICMP_NET_UNREACH); + } + + return ret; +} +#endif + +#ifdef CONFIG_NET_IPv6 +int ipv6_filter_in(FAR struct net_driver_s *dev) +{ + FAR struct ipv6_hdr_s *ipv6 = IPv6BUF; + int ret = ipv6_filter_match(dev, NULL, ipv6, IPFILTER_CHAIN_INPUT); + + if (ret == IPFILTER_TARGET_DROP) + { + dev->d_len = 0; + } + + if (ret == IPFILTER_TARGET_REJECT) + { + /* TODO: Support more --reject-with types later. */ + + icmpv6_reply(dev, ICMPv6_DEST_UNREACHABLE, ICMPv6_ADDR_UNREACH, 0); + } + + return ret; +} +#endif + +/**************************************************************************** + * Name: ipfilter_out + * + * Description: + * Handling filter on local output. Do nothing if the output packet + * is accepted, set d_len to 0 if the output packet needs to be dropped, + * and set d_iob to reject reply if the output packet is rejected. + * + * Input Parameters: + * dev - The network device that the packet goes to + * + * Returned Value: + * None + * + * Assumptions: + * The network is locked. Only IPv4 and IPv6 packets call this function. + * + ****************************************************************************/ + +void ipfilter_out(FAR struct net_driver_s *dev) +{ + if (dev->d_iob == NULL || dev->d_len == 0) + { + return; + } + +#ifdef CONFIG_NET_IPv4 + if (IFF_IS_IPv4(dev->d_flags)) + { + FAR struct ipv4_hdr_s *ipv4 = IPv4BUF; + int ret = ipv4_filter_match(NULL, dev, ipv4, IPFILTER_CHAIN_OUTPUT); + + if (ret == IPFILTER_TARGET_DROP) + { + dev->d_len = 0; + } + + if (ret == IPFILTER_TARGET_REJECT) + { + icmp_reply(dev, ICMP_DEST_UNREACHABLE, ICMP_NET_UNREACH); + } + } +#endif + +#ifdef CONFIG_NET_IPv6 + if (IFF_IS_IPv6(dev->d_flags)) + { + FAR struct ipv6_hdr_s *ipv6 = IPv6BUF; + int ret = ipv6_filter_match(NULL, dev, ipv6, IPFILTER_CHAIN_OUTPUT); + + if (ret == IPFILTER_TARGET_DROP) + { + dev->d_len = 0; + } + + if (ret == IPFILTER_TARGET_REJECT) + { + icmpv6_reply(dev, ICMPv6_DEST_UNREACHABLE, ICMPv6_ADDR_UNREACH, 0); + } + } +#endif +} + +/**************************************************************************** + * Name: ipv4_filter_fwd / ipv6_filter_fwd + * + * Description: + * Handling IPv4/IPv6 filter on forwarding. Just return the target action, + * and relies on the caller to do the actual drop / reject. + * + * Input Parameters: + * indev - The network device that the packet comes from + * outdev - The network device that the packet goes to + * ipv4/ipv6 - The IPv4/IPv6 header + * + * Returned Value: + * IPFILTER_TARGET_ACCEPT(0) - The input packet is accepted + * IPFILTER_TARGET_DROP(-1) - The input packet needs to be dropped + * IPFILTER_TARGET_REJECT(-2) - The input packet needs to be rejected + * + * Assumptions: + * The network is locked. + * + ****************************************************************************/ + +#if defined(CONFIG_NET_IPFORWARD) && defined(CONFIG_NET_IPv4) +int ipv4_filter_fwd(FAR struct net_driver_s *indev, + FAR struct net_driver_s *outdev, + FAR struct ipv4_hdr_s *ipv4) +{ + return ipv4_filter_match(indev, outdev, ipv4, IPFILTER_CHAIN_FORWARD); +} +#endif + +#if defined(CONFIG_NET_IPFORWARD) && defined(CONFIG_NET_IPv6) +int ipv6_filter_fwd(FAR struct net_driver_s *indev, + FAR struct net_driver_s *outdev, + FAR struct ipv6_hdr_s *ipv6) +{ + return ipv6_filter_match(indev, outdev, ipv6, IPFILTER_CHAIN_FORWARD); +} +#endif + +#endif /* CONFIG_NET_IPFILTER */ diff --git a/net/ipfilter/ipfilter.h b/net/ipfilter/ipfilter.h new file mode 100644 index 0000000000..7b99a43647 --- /dev/null +++ b/net/ipfilter/ipfilter.h @@ -0,0 +1,266 @@ +/**************************************************************************** + * net/ipfilter/ipfilter.h + * + * 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. + * + ****************************************************************************/ + +#ifndef __NET_IPFILTER_IPFILTER_H +#define __NET_IPFILTER_IPFILTER_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +#include +#include + +#ifdef CONFIG_NET_IPFILTER + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define IPFILTER_TARGET_ACCEPT (0) +#define IPFILTER_TARGET_DROP (-1) +#define IPFILTER_TARGET_REJECT (-2) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +enum ipfilter_chain_e +{ + IPFILTER_CHAIN_INPUT = 0, /* = NF_IP_LOCAL_IN - 1, Input chain */ + IPFILTER_CHAIN_FORWARD = 1, /* = NF_IP_FORWARD - 1, Forward chain */ + IPFILTER_CHAIN_OUTPUT = 2, /* = NF_IP_LOCAL_OUT - 1, Output chain */ + IPFILTER_CHAIN_MAX +}; + +/* The filter configuration entry */ + +struct ipfilter_entry_s +{ + FAR struct ipfilter_entry_s *flink; + + FAR struct net_driver_s *indev; + FAR struct net_driver_s *outdev; + + union /* Matches in host byte order (because it's range) */ + { + struct + { + uint16_t sports[2]; /* Source port range */ + uint16_t dports[2]; /* Destination port range */ + } tcpudp; + + struct + { + uint8_t type; /* Type to match, 0xFF = ALL (Same as Linux) */ + } icmp; + } match; + + uint8_t proto; /* Protocol to match, 0 = ALL (Same as Linux) */ + int8_t target; + + /* Match flags, whether we need to match protocol in detail */ + + uint8_t match_tcpudp : 1; /* Match TCP/UDP */ + uint8_t match_icmp : 1; /* Match ICMP */ + + /* Inverse flags */ + + uint8_t inv_indev : 1; /* Inverse input device */ + uint8_t inv_outdev : 1; /* Inverse output device */ + uint8_t inv_proto : 1; /* Inverse protocol */ + uint8_t inv_srcip : 1; /* Inverse source IP */ + uint8_t inv_dstip : 1; /* Inverse destination IP */ + uint8_t inv_sport : 1; /* Inverse source port */ + uint8_t inv_dport : 1; /* Inverse destination port */ + uint8_t inv_icmp : 1; /* Inverse ICMP type */ +}; + +struct ipv4_filter_entry_s +{ + struct ipfilter_entry_s common; + + /* Addresses in network byte order */ + + in_addr_t sip; + in_addr_t dip; + in_addr_t smsk; + in_addr_t dmsk; +}; + +struct ipv6_filter_entry_s +{ + struct ipfilter_entry_s common; + + /* Addresses in network byte order */ + + net_ipv6addr_t sip; + net_ipv6addr_t dip; + net_ipv6addr_t smsk; + net_ipv6addr_t dmsk; +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: ipfilter_cfg_alloc + * + * Description: + * Allocate a new filter configuration entry for the given address family. + * + * Input Parameters: + * family - The address family of the filter entry + * + * Returned Value: + * A pointer to the newly allocated filter entry. NULL is returned on + * failure. + * + ****************************************************************************/ + +FAR struct ipfilter_entry_s *ipfilter_cfg_alloc(sa_family_t family); + +/**************************************************************************** + * Name: ipfilter_cfg_add + * + * Description: + * Add a new filter configuration entry for the given address family to the + * end of specified chain. + * + * Input Parameters: + * entry - The filter entry to add + * family - The address family of the filter entry + * chain - The chain to add the filter entry to + * + * Returned Value: + * None + * + ****************************************************************************/ + +void ipfilter_cfg_add(FAR struct ipfilter_entry_s *entry, + sa_family_t family, enum ipfilter_chain_e chain); + +/**************************************************************************** + * Name: ipfilter_cfg_clear + * + * Description: + * Clear all filter configuration entries for the given address family from + * the specified chain. + * + * Input Parameters: + * family - The address family of the filter entry to clear + * chain - The chain to clear the filter entries from + * + * Returned Value: + * None + * + ****************************************************************************/ + +void ipfilter_cfg_clear(sa_family_t family, enum ipfilter_chain_e chain); + +/**************************************************************************** + * Name: ipv4_filter_in / ipv6_filter_in + * + * Description: + * Handling IPv4/IPv6 filter on local input. Do nothing if the input + * packet is accepted, set d_len to 0 if the input packet needs to be + * dropped, and set d_iob to reject reply if the input packet is rejected. + * + * Input Parameters: + * dev - The network device that the packet comes from + * + * Returned Value: + * IPFILTER_TARGET_ACCEPT(0) - The input packet is accepted + * IPFILTER_TARGET_DROP(-1) - The input packet needs to be dropped + * IPFILTER_TARGET_REJECT(-2) - The input packet is rejected + * + * Assumptions: + * The network is locked. The d_iob and d_len in the dev are set. + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IPv4 +int ipv4_filter_in(FAR struct net_driver_s *dev); +#endif +#ifdef CONFIG_NET_IPv6 +int ipv6_filter_in(FAR struct net_driver_s *dev); +#endif + +/**************************************************************************** + * Name: ipfilter_out + * + * Description: + * Handling filter on local output. Do nothing if the output packet + * is accepted, set d_len to 0 if the output packet needs to be dropped, + * and set d_iob to reject reply if the output packet is rejected. + * + * Input Parameters: + * dev - The network device that the packet goes to + * + * Returned Value: + * None + * + * Assumptions: + * The network is locked. Only IPv4 and IPv6 packets call this function. + * + ****************************************************************************/ + +void ipfilter_out(FAR struct net_driver_s *dev); + +/**************************************************************************** + * Name: ipv4_filter_fwd / ipv6_filter_fwd + * + * Description: + * Handling IPv4/IPv6 filter on forwarding. Just return the target action, + * and relies on the caller to do the actual drop / reject. + * + * Input Parameters: + * indev - The network device that the packet comes from + * outdev - The network device that the packet goes to + * ipv4/ipv6 - The IPv4/IPv6 header + * + * Returned Value: + * IPFILTER_TARGET_ACCEPT(0) - The input packet is accepted + * IPFILTER_TARGET_DROP(-1) - The input packet needs to be dropped + * IPFILTER_TARGET_REJECT(-2) - The input packet needs to be rejected + * + * Assumptions: + * The network is locked. + * + ****************************************************************************/ + +#if defined(CONFIG_NET_IPFORWARD) && defined(CONFIG_NET_IPv4) +int ipv4_filter_fwd(FAR struct net_driver_s *indev, + FAR struct net_driver_s *outdev, + FAR struct ipv4_hdr_s *ipv4); +#endif +#if defined(CONFIG_NET_IPFORWARD) && defined(CONFIG_NET_IPv6) +int ipv6_filter_fwd(FAR struct net_driver_s *indev, + FAR struct net_driver_s *outdev, + FAR struct ipv6_hdr_s *ipv6); +#endif + +#endif /* CONFIG_NET_IPFILTER */ +#endif /* __NET_IPFILTER_IPFILTER_H */ diff --git a/net/ipforward/ipv4_forward.c b/net/ipforward/ipv4_forward.c index 8554546933..c7e7744941 100644 --- a/net/ipforward/ipv4_forward.c +++ b/net/ipforward/ipv4_forward.c @@ -38,6 +38,7 @@ #include "utils/utils.h" #include "sixlowpan/sixlowpan.h" #include "icmp/icmp.h" +#include "ipfilter/ipfilter.h" #include "ipforward/ipforward.h" #include "nat/nat.h" #include "devif/devif.h" @@ -214,6 +215,27 @@ static int ipv4_dev_forward(FAR struct net_driver_s *dev, #endif int ret; +#ifdef CONFIG_NET_IPFILTER + /* Do filter before forwarding, to make sure we drop silently before + * replying any other errors. + */ + + ret = ipv4_filter_fwd(dev, fwddev, ipv4); + if (ret < 0) + { + ninfo("Drop/Reject FORWARD packet due to filter %d\n", ret); + + /* Let ipv4_forward reply the reject. */ + + if (ret == IPFILTER_TARGET_REJECT) + { + ret = -ENETUNREACH; + } + + goto errout; + } +#endif + /* Verify that the full packet will fit within the forwarding device's MTU * if DF is set. */ diff --git a/net/ipforward/ipv6_forward.c b/net/ipforward/ipv6_forward.c index 5f39a0ad08..f5b5b79ee1 100644 --- a/net/ipforward/ipv6_forward.c +++ b/net/ipforward/ipv6_forward.c @@ -40,6 +40,7 @@ #include "sixlowpan/sixlowpan.h" #include "devif/devif.h" #include "icmpv6/icmpv6.h" +#include "ipfilter/ipfilter.h" #include "ipforward/ipforward.h" #if defined(CONFIG_NET_IPFORWARD) && defined(CONFIG_NET_IPv6) @@ -337,6 +338,27 @@ static int ipv6_dev_forward(FAR struct net_driver_s *dev, #endif int ret; +#ifdef CONFIG_NET_IPFILTER + /* Do filter before forwarding, to make sure we drop silently before + * replying any other errors. + */ + + ret = ipv6_filter_fwd(dev, fwddev, ipv6); + if (ret < 0) + { + ninfo("Drop/Reject FORWARD packet due to filter %d\n", ret); + + /* Let ipv6_forward reply the reject. */ + + if (ret == IPFILTER_TARGET_REJECT) + { + ret = -ENETUNREACH; + } + + goto errout; + } +#endif + /* If the interface isn't "up", we can't forward. */ if ((fwddev->d_flags & IFF_UP) == 0)