/**************************************************************************** * 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 #include #include #include #include #include #include #include #include #include #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 */ 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; }