nuttx-apps/system/iptables/iptables_utils.c
Zhe Weng f620353507 system/iptables: Support ip6tables & filter table
Signed-off-by: Zhe Weng <wengzhe@xiaomi.com>
2024-06-21 10:16:01 +08:00

849 lines
21 KiB
C

/****************************************************************************
* apps/system/iptables/iptables_utils.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 <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <nuttx/net/netfilter/ip_tables.h>
#include <nuttx/net/netfilter/ip6_tables.h>
#include "iptables.h"
#include "netutils/netlib.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define MATCH_BUFSIZE sizeof("tcp spts:!65535:65535 dpts:!65535:65535")
/****************************************************************************
* Private Data
****************************************************************************/
static FAR const char *g_hooknames[] =
{
"PREROUTING", "INPUT", "FORWARD", "OUTPUT", "POSTROUTING"
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: iptables_parse_cmd
*
* Description:
* Get command from string
*
* Return Value:
* true if success, false if failed
*
****************************************************************************/
static bool iptables_parse_cmd(FAR const char *str,
FAR enum iptables_command_e *command)
{
if (strcmp(str, "-A") == 0 || strcmp(str, "--append") == 0)
{
*command = COMMAND_APPEND;
}
else if (strcmp(str, "-D") == 0 || strcmp(str, "--delete") == 0)
{
*command = COMMAND_DELETE;
}
else if (strcmp(str, "-I") == 0 || strcmp(str, "--insert") == 0)
{
*command = COMMAND_INSERT;
}
else if (strcmp(str, "-F") == 0 || strcmp(str, "--flush") == 0)
{
*command = COMMAND_FLUSH;
}
else if (strcmp(str, "-L") == 0 || strcmp(str, "--list") == 0)
{
*command = COMMAND_LIST;
}
else if (strcmp(str, "-P") == 0 || strcmp(str, "--policy") == 0)
{
*command = COMMAND_POLICY;
}
else
{
return false;
}
return true;
}
/****************************************************************************
* 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;
}
}
return NF_INET_NUMHOOKS; /* Failed to parse. */
}
/****************************************************************************
* Name: iptables_parse_proto
*
* Description:
* Get protocol number from protocol name
*
****************************************************************************/
static uint8_t iptables_parse_proto(FAR const char *proto)
{
if (strcmp(proto, "all") == 0)
{
return 0;
}
else if (strcmp(proto, "esp") == 0)
{
return IPPROTO_ESP;
}
else if (strcmp(proto, "icmp") == 0)
{
return IPPROTO_ICMP;
}
else if (strcmp(proto, "icmp6") == 0 || strcmp(proto, "icmpv6") == 0 ||
strcmp(proto, "ipv6-icmp") == 0)
{
return IPPROTO_ICMP6;
}
else if (strcmp(proto, "tcp") == 0)
{
return IPPROTO_TCP;
}
else if (strcmp(proto, "udp") == 0)
{
return IPPROTO_UDP;
}
else
{
printf("Unknown protocol: %s\n", proto);
return 0;
}
}
/****************************************************************************
* Name: iptables_parse_target
*
* Description:
* Get target name from string
*
****************************************************************************/
static FAR const char *iptables_parse_target(FAR const char *str,
FAR int8_t *verdict)
{
if (strcmp(str, "ACCEPT") == 0)
{
*verdict = -NF_ACCEPT - 1;
return XT_STANDARD_TARGET;
}
else if (strcmp(str, "DROP") == 0)
{
*verdict = -NF_DROP - 1;
return XT_STANDARD_TARGET;
}
else
{
*verdict = 0;
return str;
}
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: iptables_parse
*
* Description:
* Parse args from arg list
*
* Returned Value:
* 0 on success, or a negative error code on failure
*
****************************************************************************/
int iptables_parse(FAR struct iptables_args_s *args,
int argc, FAR char *argv[])
{
bool inv = false;
int i;
bzero(args, sizeof(struct iptables_args_s));
args->hook = NF_INET_NUMHOOKS;
/* Parse arguments. */
for (i = 1; i < argc; i++)
{
/* Table */
if (strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "--table") == 0)
{
if (++i >= argc)
{
printf("Missing table name!\n");
return -EINVAL;
}
args->table = argv[i];
continue;
}
/* Commands */
if (iptables_parse_cmd(argv[i], &args->cmd))
{
/* The chain name is following the command */
if (i + 1 < argc)
{
args->hook = iptables_parse_hook(argv[i + 1]);
}
if (args->hook != NF_INET_NUMHOOKS)
{
i++; /* Success to parse as hook. */
}
else if (args->cmd != COMMAND_LIST && args->cmd != COMMAND_FLUSH)
{
printf("Wrong chain name %s!\n", argv[i + 1]);
return -EINVAL;
}
/* Insert or delete command may have rule number */
if (args->cmd == COMMAND_INSERT || args->cmd == COMMAND_DELETE)
{
if (i + 1 < argc)
{
args->rulenum = atoi(argv[i + 1]);
}
if (args->rulenum >= 1)
{
i++;
}
else if (args->cmd == COMMAND_INSERT)
{
/* Default insert position is 1 */
args->rulenum = 1;
}
}
/* Policy command should have target */
if (args->cmd == COMMAND_POLICY)
{
if (++i >= argc)
{
printf("Missing target name!\n");
return -EINVAL;
}
args->target = iptables_parse_target(argv[i], &args->verdict);
if (args->verdict == 0)
{
printf("Invalid target name %s!\n", argv[i]);
return -EINVAL;
}
}
continue;
}
/* Target */
if (strcmp(argv[i], "-j") == 0 || strcmp(argv[i], "--jump") == 0)
{
if (++i >= argc)
{
printf("Missing target name!\n");
return -EINVAL;
}
args->target = iptables_parse_target(argv[i], &args->verdict);
continue;
}
/* Invert */
if (strcmp(argv[i], "!") == 0)
{
inv = true;
continue;
}
/* Protocol */
if (strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--protocol") == 0)
{
if (++i >= argc)
{
printf("Missing protocol name!\n");
return -EINVAL;
}
args->protocol = iptables_parse_proto(argv[i]);
if (inv)
{
args->ipinv |= IPT_INV_PROTO;
inv = false;
}
continue;
}
/* Source address */
if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--source") == 0)
{
if (++i >= argc)
{
printf("Missing source address!\n");
return -EINVAL;
}
args->saddr = argv[i];
if (inv)
{
args->ipinv |= IPT_INV_SRCIP;
inv = false;
}
continue;
}
/* Destination address */
if (strcmp(argv[i], "-d") == 0 ||
strcmp(argv[i], "--destination") == 0)
{
if (++i >= argc)
{
printf("Missing destination address!\n");
return -EINVAL;
}
args->daddr = argv[i];
if (inv)
{
args->ipinv |= IPT_INV_DSTIP;
inv = false;
}
continue;
}
/* Source port */
if (strcmp(argv[i], "--sport") == 0 ||
strcmp(argv[i], "--source-port") == 0)
{
if (++i >= argc)
{
printf("Missing source port!\n");
return -EINVAL;
}
args->sport = argv[i];
if (inv)
{
args->tcpudpinv |= XT_TCP_INV_SRCPT;
inv = false;
}
continue;
}
/* Destination port */
if (strcmp(argv[i], "--dport") == 0 ||
strcmp(argv[i], "--destination-port") == 0)
{
if (++i >= argc)
{
printf("Missing destination port!\n");
return -EINVAL;
}
args->dport = argv[i];
if (inv)
{
args->tcpudpinv |= XT_TCP_INV_DSTPT;
inv = false;
}
continue;
}
/* In interface */
if (strcmp(argv[i], "-i") == 0 ||
strcmp(argv[i], "--in-interface") == 0)
{
if (++i >= argc)
{
printf("Missing in-interface name!\n");
return -EINVAL;
}
args->inifname = argv[i];
if (inv)
{
args->ipinv |= IPT_INV_VIA_IN;
inv = false;
}
continue;
}
/* Out interface */
if (strcmp(argv[i], "-o") == 0 ||
strcmp(argv[i], "--out-interface") == 0)
{
if (++i >= argc)
{
printf("Missing out-interface name!\n");
return -EINVAL;
}
args->outifname = argv[i];
if (inv)
{
args->ipinv |= IPT_INV_VIA_OUT;
inv = false;
}
continue;
}
/* ICMP type */
if (strcmp(argv[i], "--icmp-type") == 0 ||
strcmp(argv[i], "--icmpv6-type") == 0)
{
if (++i >= argc)
{
printf("Missing ICMP type!\n");
return -EINVAL;
}
args->icmp_type = argv[i];
if (inv)
{
args->icmpinv |= IPT_ICMP_INV;
inv = false;
}
continue;
}
}
return OK;
}
/****************************************************************************
* Name: iptables_parse_ip
*
* Description:
* Parse ip string into address and mask
*
* Returned Value:
* 0 on success, or a negative error code on failure
*
****************************************************************************/
int iptables_parse_ip(FAR const char *str, FAR void *addr, FAR void *mask,
uint8_t family)
{
FAR char *ch = strchr(str, '/');
if (family != AF_INET && family != AF_INET6)
{
return -EINVAL;
}
memset(mask, 0xff, family == AF_INET ? 4 : 16);
if (ch != NULL)
{
FAR char *endp;
int prefixlen;
*ch++ = 0;
prefixlen = strtol(ch, &endp, 10);
if (*endp != '\0')
{
return -EINVAL;
}
#ifdef CONFIG_NET_IPv4
if (family == AF_INET)
{
FAR in_addr_t *mask4 = mask;
if (prefixlen > 32 || prefixlen < 0)
{
return -EINVAL;
}
*mask4 <<= 32 - prefixlen;
}
#endif
#ifdef CONFIG_NET_IPv6
if (family == AF_INET6)
{
if (prefixlen > 128 || prefixlen < 0)
{
return -EINVAL;
}
netlib_prefix2ipv6netmask(atoi(ch), mask);
}
#endif
}
if (inet_pton(family, str, addr) != OK)
{
return -errno;
}
return OK;
}
/****************************************************************************
* Name: iptables_parse_ports
*
* Description:
* Parse port string into min/max port, NULL for ALL
*
* Returned Value:
* 0 on success, or a negative error code on failure
*
****************************************************************************/
int iptables_parse_ports(FAR const char *str, uint16_t ports[2])
{
FAR char *endp;
int port;
/* Maybe sports has value but dports is NULL, then we set NULL to ALL. */
if (str == NULL)
{
ports[0] = 0;
ports[1] = 65535;
return OK;
}
port = strtol(str, &endp, 10);
if (*endp == '\0' && port >= 0 && port <= 65535)
{
ports[0] = ports[1] = port;
return OK;
}
if (*endp == ':' && port >= 0 && port <= 65535)
{
ports[0] = port;
port = strtol(endp + 1, &endp, 10);
if (*endp == '\0' && port >= 0 && port <= 65535)
{
ports[1] = port;
return OK;
}
}
return -EINVAL;
}
/****************************************************************************
* Name: iptables_parse_icmp
*
* Description:
* Parse icmp type string into type number
*
* Returned Value:
* Type code on success, or a negative error code on failure
*
****************************************************************************/
int iptables_parse_icmp(FAR const char *str)
{
FAR char *endp;
int type;
type = strtol(str, &endp, 10);
if (*endp == '\0' && type >= 0 && type <= 255)
{
return type;
}
/* TODO: Support string description of icmp type, e.g. "echo-request" */
return -EINVAL;
}
/****************************************************************************
* Name: iptables_showusage
*
* Description:
* Show usage of the iptables program
*
****************************************************************************/
void iptables_showusage(FAR const char *progname)
{
/* Format: [!] --source -s address <description> */
const char fmt[] = "%3s %-15s %-2s %-16s %s\n";
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 -P chain target\n", progname);
printf(" %s -t table -[FL] [chain]\n", progname);
printf("\nCommands:\n");
printf(fmt, "", "--append", "-A", "chain", "Append a rule to chain");
printf(fmt, "", "--insert", "-I", "chain [rulenum]",
"Insert a rule to chain at rulenum (default = 1)");
printf(fmt, "", "--delete", "-D", "chain [rulenum]",
"Delete matching rule from chain");
printf(fmt, "", "--policy", "-P", "chain target",
"Set policy for chain to target");
printf(fmt, "", "--flush", "-F", "[chain]",
"Delete all rules in chain or all chains");
printf(fmt, "", "--list", "-L", "[chain]",
"List all rules in chain or all chains");
printf("\nOptions:\n");
printf(fmt, " ", "--table", "-t", "table",
"Table to manipulate (default: filter)");
printf(fmt, " ", "--jump", "-j", "target", "Target for rule");
printf(fmt, "[!]", "--in-interface", "-i", "dev",
"Input network interface name");
printf(fmt, "[!]", "--out-interface", "-o", "dev",
"Output network interface name");
printf(fmt, "[!]", "--source", "-s", "address[/mask]", "Source address");
printf(fmt, "[!]", "--destination", "-d", "address[/mask]",
"Destination address");
printf(fmt, "[!]", "--protocol", "-p", "proto",
"Protocol (tcp, udp, icmp, esp, all)");
printf(fmt, "[!]", "--source-port,--sport", "", "", "");
printf(fmt, " ", "", "", "port[:port]", "Source port");
printf(fmt, "[!]", "--destination-port,--dport", "", "", "");
printf(fmt, " ", "", "", "port[:port]", "Destination port");
printf(fmt, "[!]", "--icmp-type", "", "type", "ICMP type");
printf(fmt, "[!]", "--icmpv6-type", "", "type", "ICMPv6 type");
}
/****************************************************************************
* Name: iptables_hook2str
*
* Description:
* Get hook name from hook number
*
****************************************************************************/
FAR const char *iptables_hook2str(enum nf_inet_hooks hook)
{
if (hook < NF_INET_NUMHOOKS)
{
return g_hooknames[hook];
}
return "UNKNOWN";
}
/****************************************************************************
* Name: iptables_target2str
*
* Description:
* Get target name from target structure
*
****************************************************************************/
FAR const char *iptables_target2str(FAR const struct xt_entry_target *tgt)
{
if (strcmp(tgt->u.user.name, XT_STANDARD_TARGET) == 0)
{
int verdict = ((FAR const struct xt_standard_target *)tgt)->verdict;
verdict = -verdict - 1;
if (verdict == NF_ACCEPT)
{
return "ACCEPT";
}
else if (verdict == NF_DROP)
{
return "DROP";
}
else
{
return "UNKNOWN";
}
}
return tgt->u.user.name;
}
/****************************************************************************
* Name: iptables_proto2str
*
* Description:
* Get protocol name from protocol number
*
****************************************************************************/
FAR const char *iptables_proto2str(uint8_t proto)
{
switch (proto)
{
case 0:
return "all";
case IPPROTO_ICMP:
return "icmp";
case IPPROTO_ICMPV6:
return "ipv6-icmp";
case IPPROTO_TCP:
return "tcp";
case IPPROTO_UDP:
return "udp";
case IPPROTO_ESP:
return "esp";
default:
return "unknown";
}
}
/****************************************************************************
* Name: iptables_match2str
*
* Description:
* Get match details from match structure
*
****************************************************************************/
FAR const char *iptables_match2str(FAR const struct xt_entry_match *match)
{
static char s_buf[MATCH_BUFSIZE];
if (match == NULL)
{
return "";
}
if (strcmp(match->u.user.name, XT_MATCH_NAME_TCP) == 0)
{
FAR struct xt_tcp *tcp = (FAR struct xt_tcp *)(match + 1);
snprintf(s_buf, MATCH_BUFSIZE,
"tcp spts:%s%u:%u dpts:%s%u:%u",
INV_FLAG_STR(tcp->invflags & XT_TCP_INV_SRCPT),
tcp->spts[0], tcp->spts[1],
INV_FLAG_STR(tcp->invflags & XT_TCP_INV_DSTPT),
tcp->dpts[0], tcp->dpts[1]);
}
else if (strcmp(match->u.user.name, XT_MATCH_NAME_UDP) == 0)
{
FAR struct xt_udp *udp = (FAR struct xt_udp *)(match + 1);
snprintf(s_buf, MATCH_BUFSIZE,
"udp spts:%s%u:%u dpts:%s%u:%u",
INV_FLAG_STR(udp->invflags & XT_UDP_INV_SRCPT),
udp->spts[0], udp->spts[1],
INV_FLAG_STR(udp->invflags & XT_UDP_INV_DSTPT),
udp->dpts[0], udp->dpts[1]);
}
else if (strcmp(match->u.user.name, XT_MATCH_NAME_ICMP) == 0)
{
FAR struct ipt_icmp *icmp = (FAR struct ipt_icmp *)(match + 1);
snprintf(s_buf, MATCH_BUFSIZE,
"icmp %stype %u",
INV_FLAG_STR(icmp->invflags & IPT_ICMP_INV),
icmp->type);
}
else if (strcmp(match->u.user.name, XT_MATCH_NAME_ICMP6) == 0)
{
FAR struct ip6t_icmp *icmp6 = (FAR struct ip6t_icmp *)(match + 1);
snprintf(s_buf, MATCH_BUFSIZE,
"ipv6-icmp %stype %u",
INV_FLAG_STR(icmp6->invflags & IP6T_ICMP_INV),
icmp6->type);
}
else
{
return match->u.user.name;
}
return s_buf;
}