
8 changed files with 552 additions and 7 deletions
@ -0,0 +1,344 @@ |
|||
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
|||
|
|||
/***
|
|||
This file is part of systemd. |
|||
|
|||
Copyright 2015 Lennart Poettering |
|||
|
|||
systemd is free software; you can redistribute it and/or modify it |
|||
under the terms of the GNU Lesser General Public License as published by |
|||
the Free Software Foundation; either version 2.1 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
systemd 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 |
|||
Lesser General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU Lesser General Public License |
|||
along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
|||
***/ |
|||
|
|||
#include <sys/types.h> |
|||
#include <arpa/inet.h> |
|||
#include <net/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 "util.h" |
|||
#include "fw-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 && strlen(out_interface) >= IFNAMSIZ) |
|||
return -EINVAL; |
|||
|
|||
if (in_interface && strlen(in_interface) >= IFNAMSIZ) |
|||
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; |
|||
in_addr_prefixlen_to_netmask(&entry->ip.smsk, source_prefixlen); |
|||
} |
|||
|
|||
if (out_interface) { |
|||
strcpy(entry->ip.outiface, out_interface); |
|||
memset(entry->ip.outiface_mask, 0xFF, strlen(out_interface)+1); |
|||
} |
|||
if (destination) { |
|||
entry->ip.dst = destination->in; |
|||
in_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 -ENOTSUP; |
|||
|
|||
if (protocol != 0 && protocol != IPPROTO_TCP && protocol != IPPROTO_UDP) |
|||
return -ENOTSUP; |
|||
|
|||
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 -ENOTSUP; |
|||
|
|||
if (protocol != IPPROTO_TCP && protocol != IPPROTO_UDP) |
|||
return -ENOTSUP; |
|||
|
|||
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; |
|||
} |
@ -0,0 +1,82 @@ |
|||
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
|||
|
|||
#pragma once |
|||
|
|||
/***
|
|||
This file is part of systemd. |
|||
|
|||
Copyright 2015 Lennart Poettering |
|||
|
|||
systemd is free software; you can redistribute it and/or modify it |
|||
under the terms of the GNU Lesser General Public License as published by |
|||
the Free Software Foundation; either version 2.1 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
systemd 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 |
|||
Lesser General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU Lesser General Public License |
|||
along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
|||
***/ |
|||
|
|||
#include "in-addr-util.h" |
|||
|
|||
#ifdef HAVE_LIBIPTC |
|||
|
|||
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); |
|||
|
|||
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); |
|||
|
|||
#else |
|||
|
|||
static inline 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) { |
|||
return -ENOTSUP; |
|||
} |
|||
|
|||
static inline 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) { |
|||
return -ENOSTUP; |
|||
} |
|||
|
|||
#endif |
@ -0,0 +1,60 @@ |
|||
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
|||
|
|||
/***
|
|||
This file is part of systemd. |
|||
|
|||
Copyright 2015 Lennart Poettering |
|||
|
|||
systemd is free software; you can redistribute it and/or modify it |
|||
under the terms of the GNU Lesser General Public License as published by |
|||
the Free Software Foundation; either version 2.1 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
systemd 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 |
|||
Lesser General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU Lesser General Public License |
|||
along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
|||
***/ |
|||
|
|||
#include "log.h" |
|||
#include "fw-util.h" |
|||
|
|||
#define MAKE_IN_ADDR_UNION(a,b,c,d) (union in_addr_union) { .in.s_addr = htobe32((uint32_t) (a) << 24 | (uint32_t) (b) << 16 | (uint32_t) (c) << 8 | (uint32_t) (d))} |
|||
|
|||
int main(int argc, char *argv[]) { |
|||
int r; |
|||
log_set_max_level(LOG_DEBUG); |
|||
|
|||
r = fw_add_masquerade(true, AF_INET, 0, NULL, 0, "foobar", NULL, 0); |
|||
if (r < 0) |
|||
log_error_errno(r, "Failed to modify firewall: %m"); |
|||
|
|||
r = fw_add_masquerade(true, AF_INET, 0, NULL, 0, "foobar", NULL, 0); |
|||
if (r < 0) |
|||
log_error_errno(r, "Failed to modify firewall: %m"); |
|||
|
|||
r = fw_add_masquerade(false, AF_INET, 0, NULL, 0, "foobar", NULL, 0); |
|||
if (r < 0) |
|||
log_error_errno(r, "Failed to modify firewall: %m"); |
|||
|
|||
r = fw_add_local_dnat(true, AF_INET, IPPROTO_TCP, NULL, NULL, 0, NULL, 0, 4711, &MAKE_IN_ADDR_UNION(1, 2, 3, 4), 815, NULL); |
|||
if (r < 0) |
|||
log_error_errno(r, "Failed to modify firewall: %m"); |
|||
|
|||
r = fw_add_local_dnat(true, AF_INET, IPPROTO_TCP, NULL, NULL, 0, NULL, 0, 4711, &MAKE_IN_ADDR_UNION(1, 2, 3, 4), 815, NULL); |
|||
if (r < 0) |
|||
log_error_errno(r, "Failed to modify firewall: %m"); |
|||
|
|||
r = fw_add_local_dnat(true, AF_INET, IPPROTO_TCP, NULL, NULL, 0, NULL, 0, 4711, &MAKE_IN_ADDR_UNION(1, 2, 3, 5), 815, &MAKE_IN_ADDR_UNION(1, 2, 3, 4)); |
|||
if (r < 0) |
|||
log_error_errno(r, "Failed to modify firewall: %m"); |
|||
|
|||
r = fw_add_local_dnat(false, AF_INET, IPPROTO_TCP, NULL, NULL, 0, NULL, 0, 4711, &MAKE_IN_ADDR_UNION(1, 2, 3, 5), 815, NULL); |
|||
if (r < 0) |
|||
log_error_errno(r, "Failed to modify firewall: %m"); |
|||
|
|||
return 0; |
|||
} |
Loading…
Reference in new issue