nuttx-apps/system/iptables/iptables.c

523 lines
14 KiB
C
Raw Normal View History

/****************************************************************************
* apps/system/iptables/iptables.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 <arpa/inet.h>
#include <nuttx/net/netfilter/ip_tables.h>
#include "iptables.h"
#include "netutils/netlib.h"
/****************************************************************************
* Private Types
****************************************************************************/
typedef CODE int
(*iptables_command_func_t)(FAR const struct iptables_args_s *args);
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: iptables_addr2str
*
* Description:
* Format address and mask to ip/preflen string.
*
****************************************************************************/
static FAR char *iptables_addr2str(struct in_addr addr, struct in_addr msk,
FAR char *buf, size_t bufflen)
{
unsigned int preflen = popcount(msk.s_addr);
if (preflen != 0)
{
snprintf(buf, bufflen, "%s/%d", inet_ntoa(addr), preflen);
}
else
{
snprintf(buf, bufflen, "anywhere");
}
return buf;
}
/****************************************************************************
* Name: iptables_print_chain
*
* Description:
* Print all rules in a chain
*
****************************************************************************/
static void iptables_print_chain(FAR const struct ipt_replace *repl,
enum nf_inet_hooks hook)
{
/* Format: target !prot !idev !odev !saddr !daddr match */
const char fmt[] = "%-12s %1s%-4s %1s%-4s %1s%-4s %1s%-18s %1s%-18s %s\n";
char src[INET_ADDRSTRLEN + 3]; /* Format: 123.123.123.123/24 */
char dst[INET_ADDRSTRLEN + 3];
FAR struct ipt_entry *entry;
FAR struct xt_entry_match *match;
FAR struct xt_entry_target *target;
FAR uint8_t *head = (FAR uint8_t *)repl->entries + repl->hook_entry[hook];
int size = repl->underflow[hook] - repl->hook_entry[hook];
/* The underflow entry contains the default rule. */
entry = (FAR struct ipt_entry *)(head + size);
target = IPT_TARGET(entry);
printf("Chain %s (policy %s)\n",
iptables_hook2str(hook), iptables_target2str(target));
printf(fmt, "target", "", "prot", "", "idev", "", "odev",
"", "source", "", "destination", "");
ipt_entry_for_every(entry, head, size)
{
target = IPT_TARGET(entry);
match = entry->target_offset >= sizeof(struct xt_entry_match) ?
IPT_MATCH(entry) : NULL;
printf(fmt, iptables_target2str(target),
INV_FLAG_STR(entry->ip.invflags & IPT_INV_PROTO),
iptables_proto2str(entry->ip.proto),
INV_FLAG_STR(entry->ip.invflags & IPT_INV_VIA_IN),
iptables_iface2str(entry->ip.iniface),
INV_FLAG_STR(entry->ip.invflags & IPT_INV_VIA_OUT),
iptables_iface2str(entry->ip.outiface),
INV_FLAG_STR(entry->ip.invflags & IPT_INV_SRCIP),
iptables_addr2str(entry->ip.src, entry->ip.smsk, src, sizeof(src)),
INV_FLAG_STR(entry->ip.invflags & IPT_INV_DSTIP),
iptables_addr2str(entry->ip.dst, entry->ip.dmsk, dst, sizeof(dst)),
iptables_match2str(match));
}
printf("\n");
}
/****************************************************************************
* Name: iptables_list
*
* Description:
* List all rules in a table
*
****************************************************************************/
static int iptables_list(FAR const char *table, enum nf_inet_hooks hook)
{
FAR struct ipt_replace *repl = netlib_ipt_prepare(table);
unsigned int cur_hook;
if (repl == NULL)
{
printf("Failed to read table %s from kernel!\n", table);
return -EIO;
}
for (cur_hook = 0; cur_hook < NF_INET_NUMHOOKS; cur_hook++)
{
if ((repl->valid_hooks & (1 << cur_hook)) != 0 &&
(hook == NF_INET_NUMHOOKS || hook == cur_hook))
{
iptables_print_chain(repl, cur_hook);
}
}
free(repl);
return OK;
}
/****************************************************************************
* Name: iptables_finish_command
*
* Description:
* Do a command and commit it
*
****************************************************************************/
static int iptables_finish_command(FAR const struct iptables_args_s *args,
FAR struct ipt_replace **repl,
FAR struct ipt_entry *entry)
{
int ret;
switch (args->cmd)
{
case COMMAND_APPEND:
ret = netlib_ipt_append(repl, entry, args->hook);
break;
case COMMAND_INSERT:
ret = netlib_ipt_insert(repl, entry, args->hook, args->rulenum);
break;
case COMMAND_DELETE:
ret = netlib_ipt_delete(*repl, entry, args->hook, args->rulenum);
break;
default: /* Other commands should not call into this function. */
ret = -EINVAL;
break;
}
if (ret == OK)
{
ret = netlib_ipt_commit(*repl);
}
return ret;
}
/****************************************************************************
* Name: iptables_finish_directly
*
* Description:
* Finish command directly without preparing any entry
*
****************************************************************************/
static int iptables_finish_directly(FAR const struct iptables_args_s *args)
{
FAR struct ipt_replace *repl = netlib_ipt_prepare(args->table);
int ret;
if (repl == NULL)
{
printf("Failed to read table '%s' from kernel!\n", args->table);
return -EIO;
}
ret = iptables_finish_command(args, &repl, NULL);
free(repl);
return ret;
}
/****************************************************************************
* Name: iptables_nat_command
*
* Description:
* Do a NAT command
*
****************************************************************************/
#ifdef CONFIG_NET_NAT
static int iptables_nat_command(FAR const struct iptables_args_s *args)
{
FAR struct ipt_replace *repl;
FAR struct ipt_entry *entry = NULL;
int ret;
if (args->outifname == NULL || args->outifname[0] == '\0')
{
printf("Table '" TABLE_NAME_NAT "' needs an out interface!\n");
return -EINVAL;
}
if (args->target != NULL &&
strcmp(args->target, XT_MASQUERADE_TARGET))
{
printf("Only target '" XT_MASQUERADE_TARGET
"' is supported for table '" TABLE_NAME_NAT "'!\n");
return -EINVAL;
}
repl = netlib_ipt_prepare(TABLE_NAME_NAT);
if (repl == NULL)
{
printf("Failed to read table '" TABLE_NAME_NAT "' from kernel!\n");
return -EIO;
}
entry = netlib_ipt_masquerade_entry(args->outifname);
if (entry == NULL)
{
printf("Failed to prepare entry for dev %s!\n", args->outifname);
ret = -ENOMEM;
goto errout_with_repl;
}
ret = iptables_finish_command(args, &repl, entry);
free(entry);
errout_with_repl:
free(repl);
return ret;
}
#endif
/****************************************************************************
* Name: iptables_filter_command
*
* Description:
* Do a FILTER command
*
****************************************************************************/
#ifdef CONFIG_NET_IPFILTER
static int iptables_filter_command(FAR const struct iptables_args_s *args)
{
FAR struct xt_entry_match *match;
FAR struct ipt_replace *repl = netlib_ipt_prepare(XT_TABLE_NAME_FILTER);
FAR struct ipt_entry *entry = NULL;
int ret;
if (repl == NULL)
{
printf("Failed to read table '" XT_TABLE_NAME_FILTER
"' from kernel!\n");
return -EIO;
}
/* Get entry and fill in proto-specific details. */
if (args->sport != NULL || args->dport != NULL)
{
FAR struct xt_udp *tcpudp;
if (args->protocol != IPPROTO_TCP && args->protocol != IPPROTO_UDP)
{
printf("Source/destination port is only supported for TCP/UDP!\n");
ret = -EINVAL;
goto errout;
}
entry = netlib_ipt_filter_entry(args->target, args->verdict,
args->protocol);
if (entry == NULL)
{
goto errout_prepare_entry;
}
match = IPT_MATCH(entry);
tcpudp = (FAR struct xt_udp *)(match + 1);
switch (args->protocol)
{
case IPPROTO_TCP:
((FAR struct xt_tcp *)tcpudp)->invflags = args->tcpudpinv;
break;
case IPPROTO_UDP:
((FAR struct xt_udp *)tcpudp)->invflags = args->tcpudpinv;
break;
}
ret = iptables_parse_ports(args->sport, tcpudp->spts);
if (ret < 0)
{
printf("Failed to parse source port!\n");
goto errout;
}
ret = iptables_parse_ports(args->dport, tcpudp->dpts);
if (ret < 0)
{
printf("Failed to parse destination port!\n");
goto errout;
}
}
else if (args->icmp_type != NULL)
{
FAR struct ipt_icmp *icmp;
if (args->protocol != IPPROTO_ICMP)
{
printf("ICMP type is only supported for ICMP protocol!\n");
ret = -EINVAL;
goto errout;
}
entry = netlib_ipt_filter_entry(args->target, args->verdict,
args->protocol);
if (entry == NULL)
{
goto errout_prepare_entry;
}
match = IPT_MATCH(entry);
icmp = (FAR struct ipt_icmp *)(match + 1);
ret = iptables_parse_icmp(args->icmp_type);
if (ret < 0)
{
printf("Failed to parse ICMP type!\n");
goto errout;
}
icmp->type = ret;
icmp->invflags = args->icmpinv;
}
else
{
entry = netlib_ipt_filter_entry(args->target, args->verdict, 0);
if (entry == NULL)
{
goto errout_prepare_entry;
}
}
/* Fill in common details. */
ret = netlib_ipt_fillifname(entry, args->inifname, args->outifname);
if (ret < 0)
{
printf("Failed to fill in interface names!\n");
goto errout;
}
if (args->saddr != NULL)
{
ret = iptables_parse_ip(args->saddr, &entry->ip.src, &entry->ip.smsk,
AF_INET);
if (ret < 0)
{
printf("Failed to parse source address %s!\n", args->saddr);
goto errout;
}
}
if (args->daddr != NULL)
{
ret = iptables_parse_ip(args->daddr, &entry->ip.dst, &entry->ip.dmsk,
AF_INET);
if (ret < 0)
{
printf("Failed to parse destination address %s!\n", args->daddr);
goto errout;
}
}
entry->ip.proto = args->protocol;
entry->ip.invflags = args->ipinv;
/* Finish command. */
ret = iptables_finish_command(args, &repl, entry);
errout:
free(entry);
free(repl);
return ret;
errout_prepare_entry:
printf("Failed to prepare entry!\n");
ret = -ENOMEM;
goto errout;
}
#endif
/****************************************************************************
* Name: iptables_apply
*
* Description:
* Apply rules for corresponding table
*
****************************************************************************/
static int iptables_apply(FAR const struct iptables_args_s *args,
iptables_command_func_t command_func)
{
switch (args->cmd)
{
case COMMAND_FLUSH:
return netlib_ipt_flush(args->table, args->hook);
case COMMAND_LIST:
return iptables_list(args->table, args->hook);
case COMMAND_POLICY:
return netlib_ipt_policy(args->table, args->hook, args->verdict);
case COMMAND_DELETE:
/* Delete rule with rulenum can be done directly. */
if (args->rulenum > 0)
{
return iptables_finish_directly(args);
}
/* Fall through. */
case COMMAND_APPEND:
case COMMAND_INSERT:
return command_func(args);
default:
printf("No supported command specified!\n");
return -EINVAL;
}
}
/****************************************************************************
* Public Functions
****************************************************************************/
int main(int argc, FAR char *argv[])
{
struct iptables_args_s args;
int ret = iptables_parse(&args, argc, argv);
if (ret < 0 || args.cmd == COMMAND_INVALID)
{
iptables_showusage(argv[0]);
return ret;
}
#ifdef CONFIG_NET_IPFILTER
if (args.table == NULL || strcmp(args.table, XT_TABLE_NAME_FILTER) == 0)
{
args.table = XT_TABLE_NAME_FILTER;
ret = iptables_apply(&args, iptables_filter_command);
if (ret < 0)
{
printf("iptables got error on filter: %d!\n", ret);
}
}
else
#endif
#ifdef CONFIG_NET_NAT
if (strcmp(args.table, TABLE_NAME_NAT) == 0)
{
ret = iptables_apply(&args, iptables_nat_command);
if (ret < 0)
{
printf("iptables got error on NAT: %d!\n", ret);
}
}
else
#endif
{
printf("Unknown table: %s\n", args.table);
}
return ret;
}