open5gs/lib/ipfw/ogs-ipfw.c

607 lines
20 KiB
C

/*
* Copyright (C) 2019-2024 by Sukchan Lee <acetcom@gmail.com>
*
* This file is part of Open5GS.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef IF_NAMESIZE
#define IF_NAMESIZE 16
#ifndef IFNAMSIZ
#define IFNAMSIZ IF_NAMESIZE
#endif
#endif
#include "ipfw2.h"
#include "objs/include_e/netinet/ip_fw.h"
#define MAX_NUM_OF_TOKEN 32
#define MAX_NUM_OF_RULE_BUFFER 1024
void compile_rule(char *av[], uint32_t *rbuf, int *rbufsize, void *tstate);
int ogs_ipfw_compile_rule(ogs_ipfw_rule_t *ipfw_rule, char *flow_description)
{
char *token, *dir;
char *saveptr;
int i;
char *av[MAX_NUM_OF_TOKEN];
uint32_t rulebuf[MAX_NUM_OF_RULE_BUFFER];
int rbufsize;
struct ip_fw_rule *rule = (struct ip_fw_rule *)rulebuf;
int x, l;
ipfw_insn *cmd;
char *description = NULL;
ogs_assert(ipfw_rule);
ogs_assert(flow_description);
rbufsize = sizeof(rulebuf);
memset(rulebuf, 0, rbufsize);
av[0] = NULL;
/* ACTION */
description = ogs_strdup(flow_description);
ogs_assert(description);
token = ogs_strtok_r(description, " ", &saveptr);
if (strcmp(token, "permit") != 0) {
ogs_error("Not begins with reserved keyword : 'permit'");
ogs_free(description);
return OGS_ERROR;
}
av[1] = token;
/* Save DIRECTION */
dir = token = ogs_strtok_r(NULL, " ", &saveptr);
if (strcmp(token, "out") != 0) {
ogs_error("Not begins with reserved keyword : 'permit out'");
ogs_free(description);
return OGS_ERROR;
}
/* ADDR */
i = 2;
token = ogs_strtok_r(NULL, " ", &saveptr);
while (token != NULL) {
av[i++] = token;
token = ogs_strtok_r(NULL, " ", &saveptr);
}
/* Add DIRECTION */
av[i++] = dir;
av[i] = NULL;
/* "to assigned" --> "to any" */
for (x = 2; av[x] != NULL; x++) {
if (strcmp(av[x], "assigned") == 0 && strcmp(av[x-1], "to") == 0) {
av[x] = "any";
break;
}
}
compile_rule(av, (uint32_t *)rule, &rbufsize, NULL);
memset(ipfw_rule, 0, sizeof(ogs_ipfw_rule_t));
for (l = rule->act_ofs, cmd = rule->cmd;
l > 0 ; l -= F_LEN(cmd) , cmd += F_LEN(cmd)) {
uint32_t *a = NULL;
uint16_t *p = NULL;
switch (cmd->opcode) {
case O_PROTO:
ipfw_rule->proto = cmd->arg1;
break;
case O_IP_SRC:
case O_IP_SRC_MASK:
a = ((ipfw_insn_u32 *)cmd)->d;
ipfw_rule->ipv4_src = 1;
ipfw_rule->ip.src.addr[0] = a[0];
if (cmd->opcode == O_IP_SRC_MASK)
ipfw_rule->ip.src.mask[0] = a[1];
else
ipfw_rule->ip.src.mask[0] = 0xffffffff;
break;
case O_IP_DST:
case O_IP_DST_MASK:
a = ((ipfw_insn_u32 *)cmd)->d;
ipfw_rule->ipv4_dst = 1;
ipfw_rule->ip.dst.addr[0] = a[0];
if (cmd->opcode == O_IP_DST_MASK)
ipfw_rule->ip.dst.mask[0] = a[1];
else
ipfw_rule->ip.dst.mask[0] = 0xffffffff;
break;
case O_IP6_SRC:
case O_IP6_SRC_MASK:
a = ((ipfw_insn_u32 *)cmd)->d;
ipfw_rule->ipv6_src = 1;
memcpy(ipfw_rule->ip.src.addr, a, OGS_IPV6_LEN);
if (cmd->opcode == O_IP6_SRC_MASK)
memcpy(ipfw_rule->ip.src.mask, a+4, OGS_IPV6_LEN);
else
n2mask((struct in6_addr *)ipfw_rule->ip.src.mask, 128);
break;
case O_IP6_DST:
case O_IP6_DST_MASK:
a = ((ipfw_insn_u32 *)cmd)->d;
ipfw_rule->ipv6_dst = 1;
memcpy(ipfw_rule->ip.dst.addr, a, OGS_IPV6_LEN);
if (cmd->opcode == O_IP6_DST_MASK)
memcpy(ipfw_rule->ip.dst.mask, a+4, OGS_IPV6_LEN);
else
n2mask((struct in6_addr *)ipfw_rule->ip.dst.mask, 128);
break;
case O_IP_SRCPORT:
p = ((ipfw_insn_u16 *)cmd)->ports;
ipfw_rule->port.src.low = p[0];
ipfw_rule->port.src.high = p[1];
break;
case O_IP_DSTPORT:
p = ((ipfw_insn_u16 *)cmd)->ports;
ipfw_rule->port.dst.low = p[0];
ipfw_rule->port.dst.high = p[1];
break;
}
}
ogs_free(description);
return OGS_OK;
}
char *ogs_ipfw_encode_flow_description(ogs_ipfw_rule_t *ipfw_rule)
{
char flow_description[OGS_HUGE_LEN];
char *p, *last;
char buf[OGS_ADDRSTRLEN];
ogs_sockaddr_t sa;
int prefixlen = 0;
p = flow_description;
last = flow_description + OGS_HUGE_LEN;
ogs_assert(ipfw_rule);
p = ogs_slprintf(p, last, "permit out");
if (ipfw_rule->proto) {
p = ogs_slprintf(p, last, " %d", ipfw_rule->proto);
} else {
p = ogs_slprintf(p, last, " ip");
}
#define IPV4_BITLEN (OGS_IPV4_LEN * 8)
#define IPV6_BITLEN (OGS_IPV6_LEN * 8)
p = ogs_slprintf(p, last, " from");
memset(&sa, 0, sizeof(sa));
if (ipfw_rule->ipv4_src) {
sa.ogs_sa_family = AF_INET;
memcpy(&sa.sin.sin_addr,
ipfw_rule->ip.src.addr, sizeof(struct in_addr));
OGS_ADDR(&sa, buf);
prefixlen = contigmask(
(uint8_t *)ipfw_rule->ip.src.mask, IPV4_BITLEN);
if (prefixlen < 0) {
ogs_error("Invalid mask[%x:%x:%x:%x]",
ipfw_rule->ip.src.mask[0],
ipfw_rule->ip.src.mask[1],
ipfw_rule->ip.src.mask[2],
ipfw_rule->ip.src.mask[3]);
return NULL;
} else if (prefixlen == 0) {
p = ogs_slprintf(p, last, " any");
} else if (prefixlen > 0 && prefixlen < IPV4_BITLEN) {
p = ogs_slprintf(p, last, " %s/%d", buf, prefixlen);
} else if (prefixlen == IPV4_BITLEN) {
p = ogs_slprintf(p, last, " %s", buf);
} else {
ogs_fatal("Invalid prefixlen[%d]", prefixlen);
ogs_assert_if_reached();
}
} else if (ipfw_rule->ipv6_src) {
sa.ogs_sa_family = AF_INET6;
memcpy(&sa.sin6.sin6_addr,
ipfw_rule->ip.src.addr, sizeof(struct in6_addr));
OGS_ADDR(&sa, buf);
prefixlen = contigmask(
(uint8_t *)ipfw_rule->ip.src.mask, IPV6_BITLEN);
if (prefixlen < 0) {
ogs_error("Invalid mask[%x:%x:%x:%x]",
ipfw_rule->ip.src.mask[0],
ipfw_rule->ip.src.mask[1],
ipfw_rule->ip.src.mask[2],
ipfw_rule->ip.src.mask[3]);
return NULL;
} else if (prefixlen == 0) {
p = ogs_slprintf(p, last, " any");
} else if (prefixlen > 0 && prefixlen < IPV6_BITLEN) {
p = ogs_slprintf(p, last, " %s/%d", buf, prefixlen);
} else if (prefixlen == IPV6_BITLEN) {
p = ogs_slprintf(p, last, " %s", buf);
} else {
ogs_fatal("Invalid prefixlen[%d]", prefixlen);
ogs_assert_if_reached();
}
} else
p = ogs_slprintf(p, last, " any");
if (ipfw_rule->port.src.low == ipfw_rule->port.src.high) {
if (ipfw_rule->port.src.low == 0) {
/* Nothing */
} else {
p = ogs_slprintf(p, last, " %d", ipfw_rule->port.src.low);
}
} else {
p = ogs_slprintf(p, last, " %d-%d",
ipfw_rule->port.src.low, ipfw_rule->port.src.high);
}
p = ogs_slprintf(p, last, " to");
memset(&sa, 0, sizeof(sa));
if (ipfw_rule->ipv4_dst) {
sa.ogs_sa_family = AF_INET;
memcpy(&sa.sin.sin_addr,
ipfw_rule->ip.dst.addr, sizeof(struct in_addr));
OGS_ADDR(&sa, buf);
prefixlen = contigmask(
(uint8_t *)ipfw_rule->ip.dst.mask, IPV4_BITLEN);
if (prefixlen < 0) {
ogs_error("Invalid mask[%x:%x:%x:%x]",
ipfw_rule->ip.dst.mask[0],
ipfw_rule->ip.dst.mask[1],
ipfw_rule->ip.dst.mask[2],
ipfw_rule->ip.dst.mask[3]);
return NULL;
} else if (prefixlen == 0) {
p = ogs_slprintf(p, last, " assigned");
} else if (prefixlen > 0 && prefixlen < IPV4_BITLEN) {
p = ogs_slprintf(p, last, " %s/%d", buf, prefixlen);
} else if (prefixlen == IPV4_BITLEN) {
p = ogs_slprintf(p, last, " %s", buf);
} else {
ogs_fatal("Invalid prefixlen[%d]", prefixlen);
ogs_assert_if_reached();
}
} else if (ipfw_rule->ipv6_dst) {
sa.ogs_sa_family = AF_INET6;
memcpy(&sa.sin6.sin6_addr,
ipfw_rule->ip.dst.addr, sizeof(struct in6_addr));
OGS_ADDR(&sa, buf);
prefixlen = contigmask(
(uint8_t *)ipfw_rule->ip.dst.mask, IPV6_BITLEN);
if (prefixlen < 0) {
ogs_error("Invalid mask[%x:%x:%x:%x]",
ipfw_rule->ip.dst.mask[0],
ipfw_rule->ip.dst.mask[1],
ipfw_rule->ip.dst.mask[2],
ipfw_rule->ip.dst.mask[3]);
return NULL;
} else if (prefixlen == 0) {
p = ogs_slprintf(p, last, " assigned");
} else if (prefixlen > 0 && prefixlen < IPV6_BITLEN) {
p = ogs_slprintf(p, last, " %s/%d", buf, prefixlen);
} else if (prefixlen == IPV6_BITLEN) {
p = ogs_slprintf(p, last, " %s", buf);
} else {
ogs_fatal("Invalid prefixlen[%d]", prefixlen);
ogs_assert_if_reached();
}
} else
p = ogs_slprintf(p, last, " assigned");
if (ipfw_rule->port.dst.low == ipfw_rule->port.dst.high) {
if (ipfw_rule->port.dst.low == 0) {
/* Nothing */
} else {
p = ogs_slprintf(p, last, " %d", ipfw_rule->port.dst.low);
}
} else {
p = ogs_slprintf(p, last, " %d-%d",
ipfw_rule->port.dst.low, ipfw_rule->port.dst.high);
}
return ogs_strdup(flow_description);
}
ogs_ipfw_rule_t *ogs_ipfw_copy_and_swap(
ogs_ipfw_rule_t *dst, ogs_ipfw_rule_t *src)
{
ogs_assert(src);
ogs_assert(dst);
ogs_assert(src != dst);
memcpy(dst, src, sizeof(ogs_ipfw_rule_t));
dst->ipv4_src = src->ipv4_dst;
dst->ipv4_dst = src->ipv4_src;
dst->ipv6_src = src->ipv6_dst;
dst->ipv6_dst = src->ipv6_src;
memcpy(&dst->ip.src, &src->ip.dst, sizeof(dst->ip.src));
memcpy(&dst->ip.dst, &src->ip.src, sizeof(dst->ip.dst));
memcpy(&dst->port.src, &src->port.dst, sizeof(dst->port.src));
memcpy(&dst->port.dst, &src->port.src, sizeof(dst->port.dst));
return dst;
}
void ogs_ipfw_rule_swap(ogs_ipfw_rule_t *ipfw_rule)
{
ogs_ipfw_rule_t dst;
ogs_assert(ipfw_rule);
ogs_ipfw_copy_and_swap(&dst, ipfw_rule);
memcpy(ipfw_rule, &dst, sizeof(ogs_ipfw_rule_t));
}
void ogs_pf_content_from_ipfw_rule(
uint8_t direction, ogs_pf_content_t *content, ogs_ipfw_rule_t *rule,
bool no_ipv4v6_local_addr_in_packet_filter)
{
int j, len;
ogs_assert(content);
ogs_assert(rule);
j = 0, len = 0;
if (rule->proto) {
content->component[j].type =
OGS_PACKET_FILTER_PROTOCOL_IDENTIFIER_NEXT_HEADER_TYPE;
content->component[j].proto = rule->proto;
j++; len += 2;
}
/*
* As per 3GPP TS 24.008, following Packet filter component type identifier
* are not supported on the LTE pre release-11 UEs:
*
* IPv4 local address type
* IPv6 remote address/prefix length type
* IPv6 local address/prefix length type
*
* And,
* IPv6 remote address/prefix length type and
* IPv6 local address/prefix length type shall be used when both MS and
* Network support Local Address in TFTs.
*/
if (rule->ipv4_src) {
switch (direction) {
case OGS_FLOW_DOWNLINK_ONLY:
case OGS_FLOW_BIDIRECTIONAL:
content->component[j].type =
OGS_PACKET_FILTER_IPV4_REMOTE_ADDRESS_TYPE;
content->component[j].ipv4.addr = rule->ip.src.addr[0];
content->component[j].ipv4.mask = rule->ip.src.mask[0];
j++; len += 9;
break;
case OGS_FLOW_UPLINK_ONLY:
if (!no_ipv4v6_local_addr_in_packet_filter) {
content->component[j].type =
OGS_PACKET_FILTER_IPV4_LOCAL_ADDRESS_TYPE;
content->component[j].ipv4.addr = rule->ip.src.addr[0];
content->component[j].ipv4.mask = rule->ip.src.mask[0];
j++; len += 9;
}
break;
default:
ogs_fatal("Unsupported direction [%d]", direction);
ogs_assert_if_reached();
}
}
if (rule->ipv4_dst) {
switch (direction) {
case OGS_FLOW_DOWNLINK_ONLY:
case OGS_FLOW_BIDIRECTIONAL:
if (!no_ipv4v6_local_addr_in_packet_filter) {
content->component[j].type =
OGS_PACKET_FILTER_IPV4_LOCAL_ADDRESS_TYPE;
content->component[j].ipv4.addr = rule->ip.dst.addr[0];
content->component[j].ipv4.mask = rule->ip.dst.mask[0];
j++; len += 9;
}
break;
case OGS_FLOW_UPLINK_ONLY:
content->component[j].type =
OGS_PACKET_FILTER_IPV4_REMOTE_ADDRESS_TYPE;
content->component[j].ipv4.addr = rule->ip.dst.addr[0];
content->component[j].ipv4.mask = rule->ip.dst.mask[0];
j++; len += 9;
break;
default:
ogs_fatal("Unsupported direction [%d]", direction);
ogs_assert_if_reached();
}
}
if (rule->ipv6_src) {
switch (direction) {
case OGS_FLOW_DOWNLINK_ONLY:
case OGS_FLOW_BIDIRECTIONAL:
if (no_ipv4v6_local_addr_in_packet_filter) {
content->component[j].type =
OGS_PACKET_FILTER_IPV6_REMOTE_ADDRESS_TYPE;
memcpy(content->component[j].ipv6_mask.addr,
rule->ip.src.addr, sizeof rule->ip.src.addr);
memcpy(content->component[j].ipv6_mask.mask,
rule->ip.src.mask, sizeof rule->ip.src.mask);
j++; len += 33;
} else {
content->component[j].type =
OGS_PACKET_FILTER_IPV6_REMOTE_ADDRESS_PREFIX_LENGTH_TYPE;
memcpy(content->component[j].ipv6.addr,
rule->ip.src.addr, sizeof rule->ip.src.addr);
content->component[j].ipv6.prefixlen =
contigmask((uint8_t *)rule->ip.src.mask, 128);
j++; len += 18;
}
break;
case OGS_FLOW_UPLINK_ONLY:
if (!no_ipv4v6_local_addr_in_packet_filter) {
content->component[j].type =
OGS_PACKET_FILTER_IPV6_LOCAL_ADDRESS_PREFIX_LENGTH_TYPE;
memcpy(content->component[j].ipv6.addr,
rule->ip.src.addr, sizeof rule->ip.src.addr);
content->component[j].ipv6.prefixlen =
contigmask((uint8_t *)rule->ip.src.mask, 128);
j++; len += 18;
}
break;
default:
ogs_fatal("Unsupported direction [%d]", direction);
ogs_assert_if_reached();
}
}
if (rule->ipv6_dst) {
switch (direction) {
case OGS_FLOW_DOWNLINK_ONLY:
case OGS_FLOW_BIDIRECTIONAL:
if (!no_ipv4v6_local_addr_in_packet_filter) {
content->component[j].type =
OGS_PACKET_FILTER_IPV6_LOCAL_ADDRESS_PREFIX_LENGTH_TYPE;
memcpy(content->component[j].ipv6.addr,
rule->ip.dst.addr, sizeof rule->ip.dst.addr);
content->component[j].ipv6.prefixlen =
contigmask((uint8_t *)rule->ip.dst.mask, 128);
j++; len += 18;
}
break;
case OGS_FLOW_UPLINK_ONLY:
if (no_ipv4v6_local_addr_in_packet_filter) {
content->component[j].type =
OGS_PACKET_FILTER_IPV6_REMOTE_ADDRESS_TYPE;
memcpy(content->component[j].ipv6_mask.addr,
rule->ip.dst.addr, sizeof rule->ip.dst.addr);
memcpy(content->component[j].ipv6_mask.mask,
rule->ip.dst.mask, sizeof rule->ip.dst.mask);
j++; len += 33;
} else {
content->component[j].type =
OGS_PACKET_FILTER_IPV6_REMOTE_ADDRESS_PREFIX_LENGTH_TYPE;
memcpy(content->component[j].ipv6.addr,
rule->ip.dst.addr, sizeof rule->ip.dst.addr);
content->component[j].ipv6.prefixlen =
contigmask((uint8_t *)rule->ip.dst.mask, 128);
j++; len += 18;
}
break;
default:
ogs_fatal("Unsupported direction [%d]", direction);
ogs_assert_if_reached();
}
}
if (rule->port.src.low) {
if (rule->port.src.low == rule->port.src.high) {
switch (direction) {
case OGS_FLOW_DOWNLINK_ONLY:
case OGS_FLOW_BIDIRECTIONAL:
content->component[j].type =
OGS_PACKET_FILTER_SINGLE_REMOTE_PORT_TYPE;
break;
case OGS_FLOW_UPLINK_ONLY:
content->component[j].type =
OGS_PACKET_FILTER_SINGLE_LOCAL_PORT_TYPE;
break;
default:
ogs_fatal("Unsupported direction [%d]", direction);
ogs_assert_if_reached();
}
content->component[j].port.low = rule->port.src.low;
j++; len += 3;
} else {
switch (direction) {
case OGS_FLOW_DOWNLINK_ONLY:
case OGS_FLOW_BIDIRECTIONAL:
content->component[j].type =
OGS_PACKET_FILTER_REMOTE_PORT_RANGE_TYPE;
break;
case OGS_FLOW_UPLINK_ONLY:
content->component[j].type =
OGS_PACKET_FILTER_LOCAL_PORT_RANGE_TYPE;
break;
default:
ogs_fatal("Unsupported direction [%d]", direction);
ogs_assert_if_reached();
}
content->component[j].port.low = rule->port.src.low;
content->component[j].port.high = rule->port.src.high;
j++; len += 5;
}
}
if (rule->port.dst.low) {
if (rule->port.dst.low == rule->port.dst.high) {
switch (direction) {
case OGS_FLOW_DOWNLINK_ONLY:
case OGS_FLOW_BIDIRECTIONAL:
content->component[j].type =
OGS_PACKET_FILTER_SINGLE_LOCAL_PORT_TYPE;
break;
case OGS_FLOW_UPLINK_ONLY:
content->component[j].type =
OGS_PACKET_FILTER_SINGLE_REMOTE_PORT_TYPE;
break;
default:
ogs_fatal("Unsupported direction [%d]", direction);
ogs_assert_if_reached();
}
content->component[j].port.low = rule->port.dst.low;
j++; len += 3;
} else {
switch (direction) {
case OGS_FLOW_DOWNLINK_ONLY:
case OGS_FLOW_BIDIRECTIONAL:
content->component[j].type =
OGS_PACKET_FILTER_LOCAL_PORT_RANGE_TYPE;
break;
case OGS_FLOW_UPLINK_ONLY:
content->component[j].type =
OGS_PACKET_FILTER_REMOTE_PORT_RANGE_TYPE;
break;
default:
ogs_fatal("Unsupported direction [%d]", direction);
ogs_assert_if_reached();
}
content->component[j].port.low = rule->port.dst.low;
content->component[j].port.high = rule->port.dst.high;
j++; len += 5;
}
}
content->num_of_component = j;
content->length = len;
}