nuttx/net/ipfilter/ipfilter.c

633 lines
17 KiB
C
Raw Normal View History

/****************************************************************************
* 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 <nuttx/config.h>
#include <debug.h>
#include <nuttx/kmalloc.h>
#include <nuttx/net/icmpv6.h>
#include <nuttx/net/netdev.h>
#include <nuttx/net/udp.h>
#include <nuttx/queue.h>
#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 */