blob: 216778b5f6ef29fed930e0b9e39a281042656bdc [file] [log] [blame]
/****************************************************************************
* net/netfilter/ipt_filter.c
*
* SPDX-License-Identifier: Apache-2.0
*
* 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 <errno.h>
#include <stdint.h>
#include <string.h>
#include <nuttx/kmalloc.h>
#include <nuttx/net/netfilter/ip_tables.h>
#include <nuttx/net/netfilter/x_tables.h>
#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.
*
****************************************************************************/
#ifdef CONFIG_NET_IPv4
static FAR struct ipv4_filter_entry_s *
convert_ipv4entry(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;
}
#endif
#ifdef CONFIG_NET_IPv6
static FAR struct ipv6_filter_entry_s *
convert_ipv6entry(FAR const struct ip6t_entry *entry)
{
FAR const struct xt_entry_match *match;
FAR const struct xt_entry_target *target;
FAR struct ipv6_filter_entry_s *filter =
(FAR struct ipv6_filter_entry_s *)ipfilter_cfg_alloc(PF_INET6);
if (filter == NULL)
{
return NULL;
}
match = IP6T_MATCH(entry);
target = IP6T_TARGET(entry);
/* Convert common fields */
net_ipv6addr_copy(filter->sip, entry->ipv6.src.s6_addr16);
net_ipv6addr_copy(filter->dip, entry->ipv6.dst.s6_addr16);
net_ipv6addr_copy(filter->smsk, entry->ipv6.smsk.s6_addr16);
net_ipv6addr_copy(filter->dmsk, entry->ipv6.dmsk.s6_addr16);
filter->common.indev = netdev_findbyname(entry->ipv6.iniface);
filter->common.outdev = netdev_findbyname(entry->ipv6.outiface);
filter->common.proto = entry->ipv6.proto;
filter->common.target = convert_target(target);
convert_invflags(&filter->common, entry->ipv6.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->ipv6.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_ICMP6:
if (strcmp(match->u.user.name, XT_MATCH_NAME_ICMP6) == 0)
{
FAR struct ip6t_icmp *icmp6 =
(FAR struct ip6t_icmp *)(match + 1);
convert_icmp(&filter->common, icmp6->type, icmp6->invflags);
}
break;
default:
break;
}
skip_match:
return filter;
}
#endif
/****************************************************************************
* 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.
*
****************************************************************************/
#ifdef CONFIG_NET_IPv4
static void adjust_ipv4filter(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_ipv4entry(entry);
if (filter != NULL)
{
ipfilter_cfg_add(&filter->common, PF_INET, chain);
}
else
{
nwarn("WARNING: Failed to convert entry!\n");
}
}
}
}
#endif
#ifdef CONFIG_NET_IPv6
static void adjust_ipv6filter(FAR const struct ip6t_replace *repl)
{
FAR const struct ip6t_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_INET6, 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++;
ip6t_entry_for_every(entry, head, size)
{
FAR struct ipv6_filter_entry_s *filter = convert_ipv6entry(entry);
if (filter != NULL)
{
ipfilter_cfg_add(&filter->common, PF_INET6, chain);
}
else
{
nwarn("WARNING: Failed to convert entry!\n");
}
}
}
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: ipt_filter_init
*
* Description:
* Init filter table data.
*
****************************************************************************/
#ifdef CONFIG_NET_IPv4
FAR struct ipt_replace *ipt_filter_init(void)
{
return ipt_alloc_table(XT_TABLE_NAME_FILTER, FILTER_VALID_HOOKS);
}
#endif
#ifdef CONFIG_NET_IPv6
FAR struct ip6t_replace *ip6t_filter_init(void)
{
return ip6t_alloc_table(XT_TABLE_NAME_FILTER, FILTER_VALID_HOOKS);
}
#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_IPv4
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_ipv4filter(repl);
return OK;
}
#endif
#ifdef CONFIG_NET_IPv6
int ip6t_filter_apply(FAR const struct ip6t_replace *repl)
{
FAR const struct ip6t_entry *entry;
FAR const struct xt_entry_match *match;
FAR const struct xt_entry_target *target;
/* Check config first. */
ip6t_entry_for_every(entry, repl->entries, repl->size)
{
match = IP6T_MATCH(entry);
target = IP6T_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->ipv6.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->ipv6.proto != IPPROTO_UDP)
{
nwarn("WARNING: UDP match for non-UDP protocol\n");
return -EINVAL;
}
if (strcmp(match->u.user.name, XT_MATCH_NAME_ICMP6) == 0 &&
entry->ipv6.proto != IPPROTO_ICMP6)
{
nwarn("WARNING: ICMP6 match for non-ICMP6 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_ipv6filter(repl);
return OK;
}
#endif