diff --git a/include/netutils/netlib.h b/include/netutils/netlib.h index 56ca1f2af95..065b74a5a4c 100644 --- a/include/netutils/netlib.h +++ b/include/netutils/netlib.h @@ -313,6 +313,30 @@ ssize_t netlib_get_route(FAR struct rtentry *rtelist, int netlib_icmpv6_autoconfiguration(FAR const char *ifname); #endif +#ifdef CONFIG_NET_IPTABLES +/* iptables interface support */ + +struct ipt_replace; /* Forward reference */ +struct ipt_entry; /* Forward reference */ +enum nf_inet_hooks; /* Forward reference */ + +FAR struct ipt_replace *netlib_ipt_prepare(FAR const char *table); +int netlib_ipt_commit(FAR const struct ipt_replace *repl); +int netlib_ipt_flush(FAR const char *table, enum nf_inet_hooks hook); +int netlib_ipt_append(FAR struct ipt_replace **repl, + FAR const struct ipt_entry *entry, + enum nf_inet_hooks hook); +int netlib_ipt_insert(FAR struct ipt_replace **repl, + FAR const struct ipt_entry *entry, + enum nf_inet_hooks hook, int rulenum); +int netlib_ipt_delete(FAR struct ipt_replace *repl, + FAR const struct ipt_entry *entry, + enum nf_inet_hooks hook, int rulenum); +# ifdef CONFIG_NET_NAT +FAR struct ipt_entry *netlib_ipt_masquerade_entry(FAR const char *ifname); +# endif +#endif + /* HTTP support */ int netlib_parsehttpurl(FAR const char *url, uint16_t *port, diff --git a/netutils/netlib/Makefile b/netutils/netlib/Makefile index f13f229d0de..cbc15f2f393 100644 --- a/netutils/netlib/Makefile +++ b/netutils/netlib/Makefile @@ -50,6 +50,9 @@ endif ifeq ($(CONFIG_NET_ROUTE),y) CSRCS += netlib_ipv4route.c netlib_ipv4router.c endif +ifeq ($(CONFIG_NET_IPTABLES),y) +CSRCS += netlib_iptables.c +endif endif ifeq ($(CONFIG_NET_IPv6),y) diff --git a/netutils/netlib/netlib_iptables.c b/netutils/netlib/netlib_iptables.c new file mode 100644 index 00000000000..079ad4dc9a8 --- /dev/null +++ b/netutils/netlib/netlib_iptables.c @@ -0,0 +1,569 @@ +/**************************************************************************** + * apps/netutils/netlib/netlib_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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "netutils/netlib.h" + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Following struct represents the layout of an entry with masquerade + * target (without matches). Mainly used to simplify entry creation. + */ + +struct ipt_masquerade_entry_s +{ + struct ipt_entry entry; + struct /* compatible with IPT_FILL_ENTRY and standard/error target */ + { + struct xt_entry_target target; + struct nf_nat_ipv4_multi_range_compat cfg; + } target; +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: netlib_ipt_entry_by_rulenum + * + * Description: + * Get entry in repl at rulenum (1 = first) in hook. + * + * Input Parameters: + * repl - The config (to set into kernel later). + * hook - The hook of the entry. + * rulenum - The place to get. + * allow_last - Whether allow to get last entry (at underflow), may insert + * entry just before last entry, but don't delete last entry. + * + ****************************************************************************/ + +static FAR struct ipt_entry * +netlib_ipt_entry_by_rulenum(FAR struct ipt_replace *repl, + enum nf_inet_hooks hook, int rulenum, + bool allow_last) +{ + FAR struct ipt_entry *e; + FAR uint8_t *head = (FAR uint8_t *)repl->entries + repl->hook_entry[hook]; + int size = repl->underflow[hook] - repl->hook_entry[hook]; + + ipt_entry_for_every(e, head, size) + { + if (--rulenum <= 0) + { + return e; + } + } + + return (allow_last && rulenum == 1) ? e : NULL; +} + +/**************************************************************************** + * Name: netlib_ipt_insert_internal + * + * Description: + * Insert an entry into config at insert_point. + * + * Input Parameters: + * repl - The config (to set into kernel later). + * entry - The entry to insert. + * hook - The hook of the entry. + * insert_point - The offset to put the entry. + * + ****************************************************************************/ + +static int netlib_ipt_insert_internal(FAR struct ipt_replace **replace, + FAR const struct ipt_entry *entry, + enum nf_inet_hooks hook, + unsigned int insert_point) +{ + FAR struct ipt_replace *repl = *replace; + FAR uint8_t *base; + size_t new_size; + + new_size = sizeof(*repl) + repl->size + entry->next_offset; + repl = realloc(repl, new_size); + if (repl == NULL) + { + return -ENOMEM; + } + + /* Insert new entry into entry table. */ + + base = (FAR uint8_t *)repl->entries; + memmove(base + insert_point + entry->next_offset, base + insert_point, + repl->size - insert_point); + memcpy(base + insert_point, entry, entry->next_offset); + + /* Adjust metadata. */ + + repl->num_entries++; + repl->size += entry->next_offset; + + /* Adjust hook_entry and underflow. */ + + repl->underflow[hook++] += entry->next_offset; + for (; hook < NF_INET_NUMHOOKS; hook++) + { + if (repl->valid_hooks & (1 << hook)) + { + repl->hook_entry[hook] += entry->next_offset; + repl->underflow[hook] += entry->next_offset; + } + } + + *replace = repl; + return OK; +} + +/**************************************************************************** + * Name: netlib_ipt_delete_internal + * + * Description: + * Delete an entry from config. + * + * Input Parameters: + * repl - The config (to set into kernel later). + * entry - The entry to remove, should be in repl. + * hook - The hook of the entry. + * + ****************************************************************************/ + +static void netlib_ipt_delete_internal(FAR struct ipt_replace *repl, + FAR struct ipt_entry *entry, + enum nf_inet_hooks hook) +{ + unsigned int delete_len = entry->next_offset; + + /* Adjust metadata. */ + + repl->num_entries--; + repl->size -= delete_len; + + /* Remove entry from entry table. */ + + memmove((FAR uint8_t *)entry, (FAR uint8_t *)entry + delete_len, + repl->size - ((uintptr_t)entry - (uintptr_t)repl->entries)); + + /* Adjust hook_entry and underflow. */ + + repl->underflow[hook++] -= delete_len; + for (; hook < NF_INET_NUMHOOKS; hook++) + { + if (repl->valid_hooks & (1 << hook)) + { + repl->hook_entry[hook] -= delete_len; + repl->underflow[hook] -= delete_len; + } + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: netlib_ipt_prepare + * + * Description: + * Read current config from kernel space. + * + * Input Parameters: + * table - The table name to read from. + * + * Returned Value: + * The pointer to the config, or NULL if failed. + * Caller must free it after use. + * + ****************************************************************************/ + +FAR struct ipt_replace *netlib_ipt_prepare(FAR const char *table) +{ + struct ipt_getinfo info; + FAR struct ipt_get_entries *entries; + FAR struct ipt_replace *repl = NULL; + socklen_t len; + int sockfd; + + if (table == NULL) + { + return NULL; + } + + sockfd = socket(NET_SOCK_FAMILY, NET_SOCK_TYPE, NET_SOCK_PROTOCOL); + if (sockfd < 0) + { + fprintf(stderr, "Failed to create socket %d!\n", errno); + return NULL; + } + + strcpy(info.name, table); + len = sizeof(info); + + if (getsockopt(sockfd, IPPROTO_IP, IPT_SO_GET_INFO, &info, &len) < 0) + { + fprintf(stderr, "Failed to get info for table %s %d!\n", table, errno); + goto errout; + } + + len = sizeof(*entries) + info.size; + entries = malloc(len); + if (entries == NULL) + { + goto errout; + } + + strcpy(entries->name, table); + entries->size = info.size; + if (getsockopt(sockfd, IPPROTO_IP, IPT_SO_GET_ENTRIES, entries, &len) < 0) + { + fprintf(stderr, "Failed to get entries for table %s %d!\n", + table, errno); + goto errout_with_entries; + } + + repl = malloc(sizeof(*repl) + info.size); + if (repl == NULL) + { + goto errout_with_entries; + } + + strcpy(repl->name, table); + + repl->valid_hooks = info.valid_hooks; + repl->num_entries = info.num_entries; + repl->size = info.size; + repl->num_counters = 0; + repl->counters = NULL; + + memcpy(repl->hook_entry, info.hook_entry, sizeof(repl->hook_entry)); + memcpy(repl->underflow, info.underflow, sizeof(repl->underflow)); + memcpy(repl->entries, entries->entrytable, info.size); + +errout_with_entries: + free(entries); + +errout: + close(sockfd); + return repl; +} + +/**************************************************************************** + * Name: netlib_ipt_commit + * + * Description: + * Set config into kernel space. + * + * Input Parameters: + * repl - The config to commit. + * + ****************************************************************************/ + +int netlib_ipt_commit(FAR const struct ipt_replace *repl) +{ + int ret; + int sockfd; + + if (repl == NULL) + { + return -EINVAL; + } + + sockfd = socket(NET_SOCK_FAMILY, NET_SOCK_TYPE, NET_SOCK_PROTOCOL); + if (sockfd < 0) + { + fprintf(stderr, "Failed to create socket %d!\n", errno); + return -errno; + } + + ret = setsockopt(sockfd, IPPROTO_IP, IPT_SO_SET_REPLACE, repl, + sizeof(*repl) + repl->size); + if (ret < 0) + { + ret = -errno; + fprintf(stderr, "Failed to commit %d!\n", ret); + } + + close(sockfd); + return ret; +} + +/**************************************************************************** + * Name: netlib_ipt_flush + * + * Description: + * Flush all config in the table. + * + * Input Parameters: + * table - The table name to flush. + * hook - The hook to flush, NF_INET_NUMHOOKS for all. + * + ****************************************************************************/ + +int netlib_ipt_flush(FAR const char *table, enum nf_inet_hooks hook) +{ + FAR struct ipt_replace *repl = netlib_ipt_prepare(table); + unsigned int cur_hook; + int ret; + + if (repl == NULL) + { + fprintf(stderr, "Failed to read table %s from kernel!\n", table); + return -EIO; + } + + if (hook != NF_INET_NUMHOOKS && (repl->valid_hooks & (1 << hook)) == 0) + { + fprintf(stderr, "Invalid hook number %d for table %s!\n", hook, table); + return -EINVAL; + } + + 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)) + { + /* Remove all user entries in current hook. */ + + while (repl->underflow[cur_hook] > repl->hook_entry[cur_hook]) + { + ret = netlib_ipt_delete(repl, NULL, cur_hook, 1); + if (ret < 0) + { + goto errout; + } + } + } + } + + ret = netlib_ipt_commit(repl); + +errout: + free(repl); + return ret; +} + +/**************************************************************************** + * Name: netlib_ipt_append + * + * Description: + * Append an entry into config, will be put to as last config of the chain + * corresponding to hook. + * + * Input Parameters: + * repl - The config (to set into kernel later). + * entry - The entry to append. + * hook - The hook of the entry. + * + ****************************************************************************/ + +int netlib_ipt_append(FAR struct ipt_replace **repl, + FAR const struct ipt_entry *entry, + enum nf_inet_hooks hook) +{ + if (repl == NULL || *repl == NULL || entry == NULL) + { + return -EINVAL; + } + + if (((*repl)->valid_hooks & (1 << hook)) == 0) + { + fprintf(stderr, "Not valid hook %d for this table!\n", hook); + return -EINVAL; + } + + return netlib_ipt_insert_internal(repl, entry, hook, + (*repl)->underflow[hook]); +} + +/**************************************************************************** + * Name: netlib_ipt_insert + * + * Description: + * Insert an entry into config, will be put to as first config of the chain + * corresponding to hook. + * + * Input Parameters: + * repl - The config (to set into kernel later). + * entry - The entry to insert. + * hook - The hook of the entry. + * rulenum - The place to insert, 1 = first. + * + ****************************************************************************/ + +int netlib_ipt_insert(FAR struct ipt_replace **repl, + FAR const struct ipt_entry *entry, + enum nf_inet_hooks hook, int rulenum) +{ + FAR struct ipt_entry *e; + + if (repl == NULL || *repl == NULL || entry == NULL || rulenum <= 0) + { + fprintf(stderr, "Not valid param %p, %p, rulenum %d!\n", + repl, entry, rulenum); + return -EINVAL; + } + + if (((*repl)->valid_hooks & (1 << hook)) == 0) + { + fprintf(stderr, "Not valid hook %d for this table!\n", hook); + return -EINVAL; + } + + e = netlib_ipt_entry_by_rulenum(*repl, hook, rulenum, true); + if (e == NULL) + { + fprintf(stderr, "Rulenum %d too big!\n", rulenum); + return -EINVAL; + } + + return netlib_ipt_insert_internal(repl, entry, hook, + (uintptr_t)e - (uintptr_t)(*repl)->entries); +} + +/**************************************************************************** + * Name: netlib_ipt_delete + * + * Description: + * Delete an entry from config. + * + * Input Parameters: + * repl - The config (to set into kernel later). + * entry - The entry to delete, choose either entry or rulenum. + * hook - The hook of the entry. + * rulenum - The place to delete, 1 = first, set entry to NULL to use this. + * + ****************************************************************************/ + +int netlib_ipt_delete(FAR struct ipt_replace *repl, + FAR const struct ipt_entry *entry, + enum nf_inet_hooks hook, int rulenum) +{ + FAR struct ipt_entry *e; + FAR uint8_t *head; + int size; + + if (repl == NULL || (entry == NULL && rulenum <= 0)) + { + fprintf(stderr, "Not valid param %p, %p, rulenum %d!\n", + repl, entry, rulenum); + return -EINVAL; + } + + if ((repl->valid_hooks & (1 << hook)) == 0) + { + fprintf(stderr, "Not valid hook %d for this table!\n", hook); + return -EINVAL; + } + + if (entry == NULL) /* Use rulenum instead. */ + { + e = netlib_ipt_entry_by_rulenum(repl, hook, rulenum, false); + if (e == NULL) + { + fprintf(stderr, "Rulenum %d too big!\n", rulenum); + return -EINVAL; + } + + netlib_ipt_delete_internal(repl, e, hook); + return OK; + } + + head = (FAR uint8_t *)repl->entries + repl->hook_entry[hook]; + size = repl->underflow[hook] - repl->hook_entry[hook]; + ipt_entry_for_every(e, head, size) + { + if (e->next_offset == entry->next_offset && + e->target_offset == entry->target_offset && + strcmp(e->ip.outiface, entry->ip.outiface) == 0 && + strcmp(IPT_TARGET(e)->u.user.name, + IPT_TARGET(entry)->u.user.name) == 0) + { + netlib_ipt_delete_internal(repl, e, hook); + return OK; + } + } + + return -ENOENT; +} + +/**************************************************************************** + * Name: netlib_ipt_masquerade_entry + * + * Description: + * Alloc an entry with masquerade target and config to apply on ifname. + * + * Input Parameters: + * ifname - The device name to apply NAT on. + * + * Returned Value: + * The pointer to the entry, or NULL if failed. + * Caller must free it after use. + * + ****************************************************************************/ + +#ifdef CONFIG_NET_NAT +FAR struct ipt_entry *netlib_ipt_masquerade_entry(FAR const char *ifname) +{ + size_t len; + FAR struct ipt_masquerade_entry_s *entry; + + len = strlen(ifname); + if (len + 1 > IFNAMSIZ) + { + return NULL; + } + + entry = zalloc(sizeof(*entry)); + if (entry == NULL) + { + return NULL; + } + + IPT_FILL_ENTRY(entry, XT_MASQUERADE_TARGET); + + strcpy(entry->entry.ip.outiface, ifname); + memset(entry->entry.ip.outiface_mask, 0xff, len + 1); + + return &entry->entry; +} +#endif diff --git a/system/iptables/Kconfig b/system/iptables/Kconfig new file mode 100644 index 00000000000..e3f24b33358 --- /dev/null +++ b/system/iptables/Kconfig @@ -0,0 +1,32 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config SYSTEM_IPTABLES + tristate "'iptables' command" + default n + depends on NET_IPTABLES + select NETUTILS_NETLIB + select SYSTEM_ARGTABLE3 + ---help--- + Enable support for the 'iptables' command. + +if SYSTEM_IPTABLES + +config SYSTEM_IPTABLES_PROGNAME + string "iptables program name" + default "iptables" + ---help--- + This is the name of the program that will be used when the NSH ELF + program is installed. + +config SYSTEM_IPTABLES_PRIORITY + int "iptables task priority" + default 100 + +config SYSTEM_IPTABLES_STACKSIZE + int "iptables stack size" + default DEFAULT_TASK_STACKSIZE + +endif diff --git a/system/iptables/Make.defs b/system/iptables/Make.defs new file mode 100644 index 00000000000..52469d42be0 --- /dev/null +++ b/system/iptables/Make.defs @@ -0,0 +1,23 @@ +############################################################################ +# apps/system/iptables/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. +# +############################################################################ + +ifneq ($(CONFIG_SYSTEM_IPTABLES),) +CONFIGURED_APPS += $(APPDIR)/system/iptables +endif diff --git a/system/iptables/Makefile b/system/iptables/Makefile new file mode 100644 index 00000000000..5950333f86e --- /dev/null +++ b/system/iptables/Makefile @@ -0,0 +1,30 @@ +############################################################################ +# apps/system/iptables/Makefile +# +# 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. +# +############################################################################ + +include $(APPDIR)/Make.defs + +PROGNAME = $(CONFIG_SYSTEM_IPTABLES_PROGNAME) +PRIORITY = $(CONFIG_SYSTEM_IPTABLES_PRIORITY) +STACKSIZE = $(CONFIG_SYSTEM_IPTABLES_STACKSIZE) +MODULE = $(CONFIG_SYSTEM_IPTABLES) + +MAINSRC = iptables.c + +include $(APPDIR)/Application.mk diff --git a/system/iptables/iptables.c b/system/iptables/iptables.c new file mode 100644 index 00000000000..edba92daa4a --- /dev/null +++ b/system/iptables/iptables.c @@ -0,0 +1,459 @@ +/**************************************************************************** + * 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 + +#include +#include +#include +#include + +#include +#include + +#include "argtable3.h" +#include "netutils/netlib.h" + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* TODO: Our getopt(), which argtable3 depends, does not support nonoptions + * in the middle, so commands like 'iptables -I chain rulenum -j MASQUERADE' + * cannot be supported, because the 'rulenum' will stop getopt() logic and + * the -j option will never be parsed. Although we write 'rulenum' option + * here, it may not work well, but it will work when our getopt() is updated. + */ + +struct iptables_args_s +{ + FAR struct arg_str *table; + + FAR struct arg_str *append_chain; + FAR struct arg_str *insert_chain; + FAR struct arg_str *delete_chain; + FAR struct arg_str *flush_chain; + FAR struct arg_str *list_chain; + + FAR struct arg_int *rulenum; + + FAR struct arg_str *target; + FAR struct arg_str *outifname; + + FAR struct arg_end *end; +}; + +enum iptables_command_e +{ + COMMAND_APPEND, + COMMAND_INSERT, + COMMAND_DELETE, + COMMAND_FLUSH, + COMMAND_LIST, + COMMAND_NUM, +}; + +struct iptables_command_s +{ + enum iptables_command_e cmd; + enum nf_inet_hooks hook; + int rulenum; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static FAR const char *g_hooknames[] = +{ + "PREROUTING", "INPUT", "FORWARD", "OUTPUT", "POSTROUTING" +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: iptables_showusage + * + * Description: + * Show usage of the demo program + * + ****************************************************************************/ + +static void iptables_showusage(FAR const char *progname, FAR void** argtable) +{ + printf("USAGE: %s -t table -[AD] chain rule-specification\n", progname); + printf(" %s -t table -I chain [rulenum] rule-specification\n", + progname); + printf(" %s -t table -D chain rulenum\n", progname); + printf(" %s -t table -[FL] [chain]\n", progname); + printf("iptables command:\n"); + arg_print_glossary(stdout, argtable, NULL); +} + +/**************************************************************************** + * Name: iptables_parse_hook + * + * Description: + * Get hook from string + * + ****************************************************************************/ + +static enum nf_inet_hooks iptables_parse_hook(FAR const char *str) +{ + unsigned int hook; + + if (str == NULL || strlen(str) == 0) /* Might be no input (-F/-L). */ + { + return NF_INET_NUMHOOKS; + } + + for (hook = 0; hook < NF_INET_NUMHOOKS; hook++) + { + if (strcmp(str, g_hooknames[hook]) == 0) + { + return hook; + } + } + + printf("Failed to parse hook: %s, default to %d\n", str, NF_INET_NUMHOOKS); + return NF_INET_NUMHOOKS; /* Failed to parse. */ +} + +/**************************************************************************** + * Name: iptables_command + * + * Description: + * Get command from arg list + * + ****************************************************************************/ + +static struct iptables_command_s +iptables_command(FAR const struct iptables_args_s *args) +{ + struct iptables_command_s ret = + { + COMMAND_NUM, + NF_INET_NUMHOOKS, + -1 + }; + + if (args->rulenum->count > 0) + { + ret.rulenum = *args->rulenum->ival; + } + + /* Flush & list with no chain specified will result in count > 0 and empty + * string in sval[0], then we map the hook to NF_INET_NUMHOOKS. + */ + + if (args->flush_chain->count > 0) + { + ret.cmd = COMMAND_FLUSH; + ret.hook = iptables_parse_hook(args->flush_chain->sval[0]); + } + else if (args->list_chain->count > 0) + { + ret.cmd = COMMAND_LIST; + ret.hook = iptables_parse_hook(args->list_chain->sval[0]); + } + else if (args->append_chain->count > 0) + { + ret.cmd = COMMAND_APPEND; + ret.hook = iptables_parse_hook(args->append_chain->sval[0]); + } + else if (args->insert_chain->count > 0) + { + ret.cmd = COMMAND_INSERT; + ret.hook = iptables_parse_hook(args->insert_chain->sval[0]); + ret.rulenum = MAX(ret.rulenum, 1); + } + else if (args->delete_chain->count > 0) + { + ret.cmd = COMMAND_DELETE; + ret.hook = iptables_parse_hook(args->delete_chain->sval[0]); + } + + return ret; +} + +/**************************************************************************** + * 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) +{ + const char fmt[] = "%12s %4s %4s %16s %16s\n"; + char src[INET_ADDRSTRLEN]; + char dst[INET_ADDRSTRLEN]; + FAR struct ipt_entry *entry; + FAR uint8_t *head = (FAR uint8_t *)repl->entries + repl->hook_entry[hook]; + int size = repl->underflow[hook] - repl->hook_entry[hook]; + + printf("Chain %s\n", g_hooknames[hook]); + printf(fmt, "target", "idev", "odev", "source", "destination"); + + ipt_entry_for_every(entry, head, size) + { + FAR struct xt_entry_target *target = IPT_TARGET(entry); + printf(fmt, target->u.user.name, entry->ip.iniface, entry->ip.outiface, + iptables_addr2str(entry->ip.src, entry->ip.smsk, src, sizeof(src)), + iptables_addr2str(entry->ip.dst, entry->ip.dmsk, dst, sizeof(dst))); + } + + 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_nat_command + * + * Description: + * Do a NAT command + * + ****************************************************************************/ + +static int iptables_nat_command(FAR const struct iptables_command_s *cmd, + FAR const char *ifname) +{ + FAR struct ipt_replace *repl = netlib_ipt_prepare(TABLE_NAME_NAT); + FAR struct ipt_entry *entry = NULL; + int ret; + + if (repl == NULL) + { + printf("Failed to read table '" TABLE_NAME_NAT "' from kernel!\n"); + return -EIO; + } + + if (ifname && strlen(ifname) > 0) /* No ifname if we delete with rulenum. */ + { + entry = netlib_ipt_masquerade_entry(ifname); + if (entry == NULL) + { + printf("Failed to prepare entry for dev %s!\n", ifname); + ret = -ENOMEM; + goto errout_with_repl; + } + } + + switch (cmd->cmd) + { + case COMMAND_APPEND: + ret = netlib_ipt_append(&repl, entry, cmd->hook); + break; + + case COMMAND_INSERT: + ret = netlib_ipt_insert(&repl, entry, cmd->hook, cmd->rulenum); + break; + + case COMMAND_DELETE: + ret = netlib_ipt_delete(repl, entry, cmd->hook, cmd->rulenum); + break; + + default: /* Other commands should not call into this function. */ + ret = -EINVAL; + break; + } + + if (ret == OK) + { + ret = netlib_ipt_commit(repl); + } + + if (entry) + { + free(entry); + } + +errout_with_repl: + free(repl); + return ret; +} + +/**************************************************************************** + * Name: iptables_nat + * + * Description: + * Apply rules for NAT + * + ****************************************************************************/ + +static int iptables_nat(FAR const struct iptables_args_s *args) +{ + struct iptables_command_s cmd = iptables_command(args); + + switch (cmd.cmd) + { + case COMMAND_FLUSH: + return netlib_ipt_flush(TABLE_NAME_NAT, cmd.hook); + + case COMMAND_LIST: + return iptables_list(TABLE_NAME_NAT, cmd.hook); + + case COMMAND_APPEND: + case COMMAND_INSERT: + case COMMAND_DELETE: + if (args->outifname->count == 0 && + !(cmd.cmd == COMMAND_DELETE && cmd.rulenum > 0)) + { + printf("Table '" TABLE_NAME_NAT "' needs an out interface!\n"); + return -EINVAL; + } + + if (args->target->count > 0 && + strcmp(args->target->sval[0], XT_MASQUERADE_TARGET)) + { + printf("Only target '" XT_MASQUERADE_TARGET + "' is supported for table '" TABLE_NAME_NAT "'!\n"); + return -EINVAL; + } + + return iptables_nat_command(&cmd, args->outifname->sval[0]); + + default: + printf("No supported command specified!\n"); + return -EINVAL; + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int main(int argc, FAR char *argv[]) +{ + struct iptables_args_s args; + int nerrors; + + args.table = arg_str1("t", "table", "table", "table to manipulate"); + + args.append_chain = arg_str0("A", "append", "chain", + "Append a rule to chain"); + args.insert_chain = arg_str0("I", "insert", "chain", + "Insert a rule to chain at rulenum " + "(default = 1)"); + args.delete_chain = arg_str0("D", "delete", "chain", + "Delete matching rule from chain"); + args.flush_chain = arg_str0("F", "flush", "chain", + "Delete all rules in chain or all chains"); + args.list_chain = arg_str0("L", "list", "chain", + "List all rules in chain or all chains"); + + args.rulenum = arg_int0(NULL, NULL, "rulenum", "Rule num (1=first)"); + + args.target = arg_str0("j", "jump", "target", "target for rule"); + args.outifname = arg_str0("o", "out-interface", "dev", + "output network interface name"); + + args.end = arg_end(1); + + /* The chain of -F or -L is optional. */ + + args.flush_chain->hdr.flag |= ARG_HASOPTVALUE; + args.list_chain->hdr.flag |= ARG_HASOPTVALUE; + + nerrors = arg_parse(argc, argv, (FAR void**)&args); + if (nerrors != 0) + { + arg_print_errors(stderr, args.end, argv[0]); + iptables_showusage(argv[0], (FAR void**)&args); + return 0; + } + + if (strcmp(args.table->sval[0], TABLE_NAME_NAT) == 0) + { + int ret = iptables_nat(&args); + if (ret < 0) + { + printf("iptables got error on NAT: %d!\n", ret); + } + + return ret; + } + + printf("Unknown table: %s\n", args.table->sval[0]); + return 0; +}