Systemd/src/shared/firewall-util.c
Lennart Poettering 0c69794138 tree-wide: remove Lennart's copyright lines
These lines are generally out-of-date, incomplete and unnecessary. With
SPDX and git repository much more accurate and fine grained information
about licensing and authorship is available, hence let's drop the
per-file copyright notice. Of course, removing copyright lines of others
is problematic, hence this commit only removes my own lines and leaves
all others untouched. It might be nicer if sooner or later those could
go away too, making git the only and accurate source of authorship
information.
2018-06-14 10:20:20 +02:00

345 lines
12 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
/* Temporary work-around for broken glibc vs. linux kernel header definitions
* This is already fixed upstream, remove this when distributions have updated.
*/
#define _NET_IF_H 1
#include <alloca.h>
#include <arpa/inet.h>
#include <endian.h>
#include <errno.h>
#include <stddef.h>
#include <string.h>
#include <sys/socket.h>
#include <net/if.h>
#ifndef IFNAMSIZ
#define IFNAMSIZ 16
#endif
#include <linux/if.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter/nf_nat.h>
#include <linux/netfilter/xt_addrtype.h>
#include <libiptc/libiptc.h>
#include "alloc-util.h"
#include "firewall-util.h"
#include "in-addr-util.h"
#include "macro.h"
#include "socket-util.h"
DEFINE_TRIVIAL_CLEANUP_FUNC(struct xtc_handle*, iptc_free);
static int entry_fill_basics(
struct ipt_entry *entry,
int protocol,
const char *in_interface,
const union in_addr_union *source,
unsigned source_prefixlen,
const char *out_interface,
const union in_addr_union *destination,
unsigned destination_prefixlen) {
assert(entry);
if (out_interface && !ifname_valid(out_interface))
return -EINVAL;
if (in_interface && !ifname_valid(in_interface))
return -EINVAL;
entry->ip.proto = protocol;
if (in_interface) {
strcpy(entry->ip.iniface, in_interface);
memset(entry->ip.iniface_mask, 0xFF, strlen(in_interface)+1);
}
if (source) {
entry->ip.src = source->in;
in4_addr_prefixlen_to_netmask(&entry->ip.smsk, source_prefixlen);
}
if (out_interface) {
size_t l = strlen(out_interface);
assert(l < sizeof entry->ip.outiface);
assert(l < sizeof entry->ip.outiface_mask);
strcpy(entry->ip.outiface, out_interface);
memset(entry->ip.outiface_mask, 0xFF, l + 1);
}
if (destination) {
entry->ip.dst = destination->in;
in4_addr_prefixlen_to_netmask(&entry->ip.dmsk, destination_prefixlen);
}
return 0;
}
int fw_add_masquerade(
bool add,
int af,
int protocol,
const union in_addr_union *source,
unsigned source_prefixlen,
const char *out_interface,
const union in_addr_union *destination,
unsigned destination_prefixlen) {
_cleanup_(iptc_freep) struct xtc_handle *h = NULL;
struct ipt_entry *entry, *mask;
struct ipt_entry_target *t;
size_t sz;
struct nf_nat_ipv4_multi_range_compat *mr;
int r;
if (af != AF_INET)
return -EOPNOTSUPP;
if (!IN_SET(protocol, 0, IPPROTO_TCP, IPPROTO_UDP))
return -EOPNOTSUPP;
h = iptc_init("nat");
if (!h)
return -errno;
sz = XT_ALIGN(sizeof(struct ipt_entry)) +
XT_ALIGN(sizeof(struct ipt_entry_target)) +
XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat));
/* Put together the entry we want to add or remove */
entry = alloca0(sz);
entry->next_offset = sz;
entry->target_offset = XT_ALIGN(sizeof(struct ipt_entry));
r = entry_fill_basics(entry, protocol, NULL, source, source_prefixlen, out_interface, destination, destination_prefixlen);
if (r < 0)
return r;
/* Fill in target part */
t = ipt_get_target(entry);
t->u.target_size =
XT_ALIGN(sizeof(struct ipt_entry_target)) +
XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat));
strncpy(t->u.user.name, "MASQUERADE", sizeof(t->u.user.name));
mr = (struct nf_nat_ipv4_multi_range_compat*) t->data;
mr->rangesize = 1;
/* Create a search mask entry */
mask = alloca(sz);
memset(mask, 0xFF, sz);
if (add) {
if (iptc_check_entry("POSTROUTING", entry, (unsigned char*) mask, h))
return 0;
if (errno != ENOENT) /* if other error than not existing yet, fail */
return -errno;
if (!iptc_insert_entry("POSTROUTING", entry, 0, h))
return -errno;
} else {
if (!iptc_delete_entry("POSTROUTING", entry, (unsigned char*) mask, h)) {
if (errno == ENOENT) /* if it's already gone, all is good! */
return 0;
return -errno;
}
}
if (!iptc_commit(h))
return -errno;
return 0;
}
int fw_add_local_dnat(
bool add,
int af,
int protocol,
const char *in_interface,
const union in_addr_union *source,
unsigned source_prefixlen,
const union in_addr_union *destination,
unsigned destination_prefixlen,
uint16_t local_port,
const union in_addr_union *remote,
uint16_t remote_port,
const union in_addr_union *previous_remote) {
_cleanup_(iptc_freep) struct xtc_handle *h = NULL;
struct ipt_entry *entry, *mask;
struct ipt_entry_target *t;
struct ipt_entry_match *m;
struct xt_addrtype_info_v1 *at;
struct nf_nat_ipv4_multi_range_compat *mr;
size_t sz, msz;
int r;
assert(add || !previous_remote);
if (af != AF_INET)
return -EOPNOTSUPP;
if (!IN_SET(protocol, IPPROTO_TCP, IPPROTO_UDP))
return -EOPNOTSUPP;
if (local_port <= 0)
return -EINVAL;
if (remote_port <= 0)
return -EINVAL;
h = iptc_init("nat");
if (!h)
return -errno;
sz = XT_ALIGN(sizeof(struct ipt_entry)) +
XT_ALIGN(sizeof(struct ipt_entry_match)) +
XT_ALIGN(sizeof(struct xt_addrtype_info_v1)) +
XT_ALIGN(sizeof(struct ipt_entry_target)) +
XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat));
if (protocol == IPPROTO_TCP)
msz = XT_ALIGN(sizeof(struct ipt_entry_match)) +
XT_ALIGN(sizeof(struct xt_tcp));
else
msz = XT_ALIGN(sizeof(struct ipt_entry_match)) +
XT_ALIGN(sizeof(struct xt_udp));
sz += msz;
/* Fill in basic part */
entry = alloca0(sz);
entry->next_offset = sz;
entry->target_offset =
XT_ALIGN(sizeof(struct ipt_entry)) +
XT_ALIGN(sizeof(struct ipt_entry_match)) +
XT_ALIGN(sizeof(struct xt_addrtype_info_v1)) +
msz;
r = entry_fill_basics(entry, protocol, in_interface, source, source_prefixlen, NULL, destination, destination_prefixlen);
if (r < 0)
return r;
/* Fill in first match */
m = (struct ipt_entry_match*) ((uint8_t*) entry + XT_ALIGN(sizeof(struct ipt_entry)));
m->u.match_size = msz;
if (protocol == IPPROTO_TCP) {
struct xt_tcp *tcp;
strncpy(m->u.user.name, "tcp", sizeof(m->u.user.name));
tcp = (struct xt_tcp*) m->data;
tcp->dpts[0] = tcp->dpts[1] = local_port;
tcp->spts[0] = 0;
tcp->spts[1] = 0xFFFF;
} else {
struct xt_udp *udp;
strncpy(m->u.user.name, "udp", sizeof(m->u.user.name));
udp = (struct xt_udp*) m->data;
udp->dpts[0] = udp->dpts[1] = local_port;
udp->spts[0] = 0;
udp->spts[1] = 0xFFFF;
}
/* Fill in second match */
m = (struct ipt_entry_match*) ((uint8_t*) entry + XT_ALIGN(sizeof(struct ipt_entry)) + msz);
m->u.match_size =
XT_ALIGN(sizeof(struct ipt_entry_match)) +
XT_ALIGN(sizeof(struct xt_addrtype_info_v1));
strncpy(m->u.user.name, "addrtype", sizeof(m->u.user.name));
m->u.user.revision = 1;
at = (struct xt_addrtype_info_v1*) m->data;
at->dest = XT_ADDRTYPE_LOCAL;
/* Fill in target part */
t = ipt_get_target(entry);
t->u.target_size =
XT_ALIGN(sizeof(struct ipt_entry_target)) +
XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat));
strncpy(t->u.user.name, "DNAT", sizeof(t->u.user.name));
mr = (struct nf_nat_ipv4_multi_range_compat*) t->data;
mr->rangesize = 1;
mr->range[0].flags = NF_NAT_RANGE_PROTO_SPECIFIED|NF_NAT_RANGE_MAP_IPS;
mr->range[0].min_ip = mr->range[0].max_ip = remote->in.s_addr;
if (protocol == IPPROTO_TCP)
mr->range[0].min.tcp.port = mr->range[0].max.tcp.port = htons(remote_port);
else
mr->range[0].min.udp.port = mr->range[0].max.udp.port = htons(remote_port);
mask = alloca0(sz);
memset(mask, 0xFF, sz);
if (add) {
/* Add the PREROUTING rule, if it is missing so far */
if (!iptc_check_entry("PREROUTING", entry, (unsigned char*) mask, h)) {
if (errno != ENOENT)
return -EINVAL;
if (!iptc_insert_entry("PREROUTING", entry, 0, h))
return -errno;
}
/* If a previous remote is set, remove its entry */
if (previous_remote && previous_remote->in.s_addr != remote->in.s_addr) {
mr->range[0].min_ip = mr->range[0].max_ip = previous_remote->in.s_addr;
if (!iptc_delete_entry("PREROUTING", entry, (unsigned char*) mask, h)) {
if (errno != ENOENT)
return -errno;
}
mr->range[0].min_ip = mr->range[0].max_ip = remote->in.s_addr;
}
/* Add the OUTPUT rule, if it is missing so far */
if (!in_interface) {
/* Don't apply onto loopback addresses */
if (!destination) {
entry->ip.dst.s_addr = htobe32(0x7F000000);
entry->ip.dmsk.s_addr = htobe32(0xFF000000);
entry->ip.invflags = IPT_INV_DSTIP;
}
if (!iptc_check_entry("OUTPUT", entry, (unsigned char*) mask, h)) {
if (errno != ENOENT)
return -errno;
if (!iptc_insert_entry("OUTPUT", entry, 0, h))
return -errno;
}
/* If a previous remote is set, remove its entry */
if (previous_remote && previous_remote->in.s_addr != remote->in.s_addr) {
mr->range[0].min_ip = mr->range[0].max_ip = previous_remote->in.s_addr;
if (!iptc_delete_entry("OUTPUT", entry, (unsigned char*) mask, h)) {
if (errno != ENOENT)
return -errno;
}
}
}
} else {
if (!iptc_delete_entry("PREROUTING", entry, (unsigned char*) mask, h)) {
if (errno != ENOENT)
return -errno;
}
if (!in_interface) {
if (!destination) {
entry->ip.dst.s_addr = htobe32(0x7F000000);
entry->ip.dmsk.s_addr = htobe32(0xFF000000);
entry->ip.invflags = IPT_INV_DSTIP;
}
if (!iptc_delete_entry("OUTPUT", entry, (unsigned char*) mask, h)) {
if (errno != ENOENT)
return -errno;
}
}
}
if (!iptc_commit(h))
return -errno;
return 0;
}