1706 lines
44 KiB
C
1706 lines
44 KiB
C
/* $Id: iptcrdr.c,v 1.53 2015/02/08 09:10:00 nanard Exp $ */
|
|
/* MiniUPnP project
|
|
* http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
|
|
* (c) 2006-2016 Thomas Bernard
|
|
* This software is subject to the conditions detailed
|
|
* in the LICENCE file provided within the distribution */
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <syslog.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <dlfcn.h>
|
|
#include <xtables.h>
|
|
#include <linux/netfilter/xt_DSCP.h>
|
|
#include <libiptc/libiptc.h>
|
|
|
|
#include <linux/version.h>
|
|
|
|
#if IPTABLES_143
|
|
/* IPTABLES API version >= 1.4.3 */
|
|
|
|
/* added in order to compile on gentoo :
|
|
* http://miniupnp.tuxfamily.org/forum/viewtopic.php?p=2183 */
|
|
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
|
|
#define __must_be_array(a) \
|
|
BUILD_BUG_ON_ZERO(__builtin_types_compatible_p(typeof(a), typeof(&a[0])))
|
|
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))
|
|
#define LIST_POISON2 ((void *) 0x00200200 )
|
|
|
|
#if 0
|
|
#include <linux/netfilter/nf_nat.h>
|
|
#else
|
|
#include "tiny_nf_nat.h"
|
|
#endif
|
|
#define ip_nat_multi_range nf_nat_multi_range
|
|
#define ip_nat_range nf_nat_range
|
|
#define IPTC_HANDLE struct iptc_handle *
|
|
#else
|
|
/* IPTABLES API version < 1.4.3 */
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22)
|
|
#include <linux/netfilter_ipv4/ip_nat.h>
|
|
#else
|
|
#if 0
|
|
#include <linux/netfilter/nf_nat.h>
|
|
#else
|
|
#include "tiny_nf_nat.h"
|
|
#endif
|
|
#endif
|
|
#define IPTC_HANDLE iptc_handle_t
|
|
#endif
|
|
|
|
/* IPT_ALIGN was renamed XT_ALIGN in iptables-1.4.11 */
|
|
#ifndef IPT_ALIGN
|
|
#define IPT_ALIGN XT_ALIGN
|
|
#endif
|
|
|
|
#include "../macros.h"
|
|
#include "../config.h"
|
|
#include "iptcrdr.h"
|
|
#include "../upnpglobalvars.h"
|
|
|
|
/* local functions declarations */
|
|
static int
|
|
addnatrule(int proto, unsigned short eport,
|
|
const char * iaddr, unsigned short iport,
|
|
const char * rhost);
|
|
|
|
static int
|
|
add_filter_rule(int proto, const char * rhost,
|
|
const char * iaddr, unsigned short iport);
|
|
|
|
static int
|
|
addmasqueraderule(int proto,
|
|
unsigned short eport,
|
|
const char * iaddr, unsigned short iport,
|
|
const char * rhost, const char * extif);
|
|
|
|
static int
|
|
addpeernatrule(int proto,
|
|
const char * eaddr, unsigned short eport,
|
|
const char * iaddr, unsigned short iport,
|
|
const char * rhost, unsigned short rport);
|
|
|
|
static int
|
|
addpeerdscprule(int proto, unsigned char dscp,
|
|
const char * iaddr, unsigned short iport,
|
|
const char * rhost, unsigned short rport);
|
|
|
|
/* dummy init and shutdown functions */
|
|
int init_redirect(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void shutdown_redirect(void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* convert an ip address to string */
|
|
static int snprintip(char * dst, size_t size, uint32_t ip)
|
|
{
|
|
return snprintf(dst, size,
|
|
"%u.%u.%u.%u", ip >> 24, (ip >> 16) & 0xff,
|
|
(ip >> 8) & 0xff, ip & 0xff);
|
|
}
|
|
|
|
/* netfilter cannot store redirection descriptions, so we use our
|
|
* own structure to store them */
|
|
struct rdr_desc {
|
|
struct rdr_desc * next;
|
|
unsigned int timestamp;
|
|
unsigned short eport;
|
|
short proto;
|
|
char str[];
|
|
};
|
|
|
|
/* pointer to the chained list where descriptions are stored */
|
|
static struct rdr_desc * rdr_desc_list = 0;
|
|
|
|
/* add a description to the list of redirection descriptions */
|
|
static void
|
|
add_redirect_desc(unsigned short eport, int proto,
|
|
const char * desc, unsigned int timestamp)
|
|
{
|
|
struct rdr_desc * p;
|
|
size_t l;
|
|
/* set a default description if none given */
|
|
if(!desc)
|
|
desc = "miniupnpd";
|
|
l = strlen(desc) + 1;
|
|
p = malloc(sizeof(struct rdr_desc) + l);
|
|
if(p)
|
|
{
|
|
p->next = rdr_desc_list;
|
|
p->timestamp = timestamp;
|
|
p->eport = eport;
|
|
p->proto = (short)proto;
|
|
memcpy(p->str, desc, l);
|
|
rdr_desc_list = p;
|
|
}
|
|
}
|
|
|
|
/* delete a description from the list */
|
|
static void
|
|
del_redirect_desc(unsigned short eport, int proto)
|
|
{
|
|
struct rdr_desc * p, * last;
|
|
p = rdr_desc_list;
|
|
last = 0;
|
|
while(p)
|
|
{
|
|
if(p->eport == eport && p->proto == proto)
|
|
{
|
|
if(!last)
|
|
rdr_desc_list = p->next;
|
|
else
|
|
last->next = p->next;
|
|
free(p);
|
|
return;
|
|
}
|
|
last = p;
|
|
p = p->next;
|
|
}
|
|
}
|
|
|
|
/* go through the list to find the description */
|
|
static void
|
|
get_redirect_desc(unsigned short eport, int proto,
|
|
char * desc, int desclen,
|
|
unsigned int * timestamp)
|
|
{
|
|
struct rdr_desc * p;
|
|
for(p = rdr_desc_list; p; p = p->next)
|
|
{
|
|
if(p->eport == eport && p->proto == (short)proto)
|
|
{
|
|
if(desc)
|
|
strncpy(desc, p->str, desclen);
|
|
if(timestamp)
|
|
*timestamp = p->timestamp;
|
|
return;
|
|
}
|
|
}
|
|
/* if no description was found, return miniupnpd as default */
|
|
if(desc)
|
|
strncpy(desc, "miniupnpd", desclen);
|
|
if(timestamp)
|
|
*timestamp = 0;
|
|
}
|
|
|
|
#if USE_INDEX_FROM_DESC_LIST
|
|
static int
|
|
get_redirect_desc_by_index(int index, unsigned short * eport, int * proto,
|
|
char * desc, int desclen, unsigned int * timestamp)
|
|
{
|
|
int i = 0;
|
|
struct rdr_desc * p;
|
|
if(!desc || (desclen == 0))
|
|
return -1;
|
|
for(p = rdr_desc_list; p; p = p->next, i++)
|
|
{
|
|
if(i == index)
|
|
{
|
|
*eport = p->eport;
|
|
*proto = (int)p->proto;
|
|
strncpy(desc, p->str, desclen);
|
|
if(timestamp)
|
|
*timestamp = p->timestamp;
|
|
return 0;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
/* add_redirect_rule2() */
|
|
int
|
|
add_redirect_rule2(const char * ifname,
|
|
const char * rhost, unsigned short eport,
|
|
const char * iaddr, unsigned short iport, int proto,
|
|
const char * desc, unsigned int timestamp)
|
|
{
|
|
int r;
|
|
UNUSED(ifname);
|
|
|
|
r = addnatrule(proto, eport, iaddr, iport, rhost);
|
|
if(r >= 0) {
|
|
add_redirect_desc(eport, proto, desc, timestamp);
|
|
r = addmasqueraderule(proto, eport, iaddr, iport, rhost, ifname);
|
|
if(r <= 0) {
|
|
syslog(LOG_NOTICE, "add_redirect_rule2(): addmasqueraderule returned %d", r);
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
/* add_redirect_rule2() */
|
|
int
|
|
add_peer_redirect_rule2(const char * ifname,
|
|
const char * rhost, unsigned short rport,
|
|
const char * eaddr, unsigned short eport,
|
|
const char * iaddr, unsigned short iport, int proto,
|
|
const char * desc, unsigned int timestamp)
|
|
{
|
|
int r;
|
|
UNUSED(ifname);
|
|
|
|
r = addpeernatrule(proto, eaddr, eport, iaddr, iport, rhost, rport);
|
|
if(r >= 0)
|
|
add_redirect_desc(eport, proto, desc, timestamp);
|
|
return r;
|
|
}
|
|
|
|
int
|
|
add_peer_dscp_rule2(const char * ifname,
|
|
const char * rhost, unsigned short rport,
|
|
unsigned char dscp,
|
|
const char * iaddr, unsigned short iport, int proto,
|
|
const char * desc, unsigned int timestamp)
|
|
{
|
|
int r;
|
|
UNUSED(ifname);
|
|
UNUSED(desc);
|
|
UNUSED(timestamp);
|
|
|
|
r = addpeerdscprule(proto, dscp, iaddr, iport, rhost, rport);
|
|
/* if(r >= 0)
|
|
add_redirect_desc(dscp, proto, desc, timestamp); */
|
|
return r;
|
|
}
|
|
|
|
int
|
|
add_filter_rule2(const char * ifname,
|
|
const char * rhost, const char * iaddr,
|
|
unsigned short eport, unsigned short iport,
|
|
int proto, const char * desc)
|
|
{
|
|
UNUSED(ifname);
|
|
UNUSED(eport);
|
|
UNUSED(desc);
|
|
|
|
return add_filter_rule(proto, rhost, iaddr, iport);
|
|
}
|
|
|
|
/* get_redirect_rule()
|
|
* returns -1 if the rule is not found */
|
|
int
|
|
get_redirect_rule(const char * ifname, unsigned short eport, int proto,
|
|
char * iaddr, int iaddrlen, unsigned short * iport,
|
|
char * desc, int desclen,
|
|
char * rhost, int rhostlen,
|
|
unsigned int * timestamp,
|
|
u_int64_t * packets, u_int64_t * bytes)
|
|
{
|
|
return get_nat_redirect_rule(miniupnpd_nat_chain,
|
|
ifname, eport, proto,
|
|
iaddr, iaddrlen, iport,
|
|
desc, desclen,
|
|
rhost, rhostlen,
|
|
timestamp, packets, bytes);
|
|
}
|
|
|
|
int
|
|
get_nat_redirect_rule(const char * nat_chain_name, const char * ifname, unsigned short eport, int proto,
|
|
char * iaddr, int iaddrlen, unsigned short * iport,
|
|
char * desc, int desclen,
|
|
char * rhost, int rhostlen,
|
|
unsigned int * timestamp,
|
|
u_int64_t * packets, u_int64_t * bytes)
|
|
{
|
|
int r = -1;
|
|
IPTC_HANDLE h;
|
|
const struct ipt_entry * e;
|
|
const struct ipt_entry_target * target;
|
|
const struct ip_nat_multi_range * mr;
|
|
const struct ipt_entry_match *match;
|
|
UNUSED(ifname);
|
|
|
|
h = iptc_init("nat");
|
|
if(!h)
|
|
{
|
|
syslog(LOG_ERR, "get_redirect_rule() : "
|
|
"iptc_init() failed : %s",
|
|
iptc_strerror(errno));
|
|
return -1;
|
|
}
|
|
if(!iptc_is_chain(nat_chain_name, h))
|
|
{
|
|
syslog(LOG_ERR, "chain %s not found", nat_chain_name);
|
|
}
|
|
else
|
|
{
|
|
#ifdef IPTABLES_143
|
|
for(e = iptc_first_rule(nat_chain_name, h);
|
|
e;
|
|
e = iptc_next_rule(e, h))
|
|
#else
|
|
for(e = iptc_first_rule(nat_chain_name, &h);
|
|
e;
|
|
e = iptc_next_rule(e, &h))
|
|
#endif
|
|
{
|
|
if(proto==e->ip.proto)
|
|
{
|
|
match = (const struct ipt_entry_match *)&e->elems;
|
|
if(0 == strncmp(match->u.user.name, "tcp", IPT_FUNCTION_MAXNAMELEN))
|
|
{
|
|
const struct ipt_tcp * info;
|
|
info = (const struct ipt_tcp *)match->data;
|
|
if(eport != info->dpts[0])
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
const struct ipt_udp * info;
|
|
info = (const struct ipt_udp *)match->data;
|
|
if(eport != info->dpts[0])
|
|
continue;
|
|
}
|
|
target = (void *)e + e->target_offset;
|
|
/* target = ipt_get_target(e); */
|
|
mr = (const struct ip_nat_multi_range *)&target->data[0];
|
|
snprintip(iaddr, iaddrlen, ntohl(mr->range[0].min_ip));
|
|
*iport = ntohs(mr->range[0].min.all);
|
|
get_redirect_desc(eport, proto, desc, desclen, timestamp);
|
|
if(packets)
|
|
*packets = e->counters.pcnt;
|
|
if(bytes)
|
|
*bytes = e->counters.bcnt;
|
|
/* rhost */
|
|
if(e->ip.src.s_addr && rhost) {
|
|
snprintip(rhost, rhostlen, ntohl(e->ip.src.s_addr));
|
|
}
|
|
r = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(h)
|
|
#ifdef IPTABLES_143
|
|
iptc_free(h);
|
|
#else
|
|
iptc_free(&h);
|
|
#endif
|
|
return r;
|
|
}
|
|
|
|
/* get_redirect_rule_by_index()
|
|
* return -1 when the rule was not found */
|
|
int
|
|
get_redirect_rule_by_index(int index,
|
|
char * ifname, unsigned short * eport,
|
|
char * iaddr, int iaddrlen, unsigned short * iport,
|
|
int * proto, char * desc, int desclen,
|
|
char * rhost, int rhostlen,
|
|
unsigned int * timestamp,
|
|
u_int64_t * packets, u_int64_t * bytes)
|
|
{
|
|
int r = -1;
|
|
#if USE_INDEX_FROM_DESC_LIST
|
|
r = get_redirect_desc_by_index(index, eport, proto,
|
|
desc, desclen, timestamp);
|
|
if (r==0)
|
|
{
|
|
r = get_redirect_rule(ifname, *eport, *proto, iaddr, iaddrlen, iport,
|
|
0, 0, packets, bytes);
|
|
}
|
|
#else
|
|
int i = 0;
|
|
IPTC_HANDLE h;
|
|
const struct ipt_entry * e;
|
|
const struct ipt_entry_target * target;
|
|
const struct ip_nat_multi_range * mr;
|
|
const struct ipt_entry_match *match;
|
|
UNUSED(ifname);
|
|
|
|
h = iptc_init("nat");
|
|
if(!h)
|
|
{
|
|
syslog(LOG_ERR, "get_redirect_rule_by_index() : "
|
|
"iptc_init() failed : %s",
|
|
iptc_strerror(errno));
|
|
return -1;
|
|
}
|
|
if(!iptc_is_chain(miniupnpd_nat_chain, h))
|
|
{
|
|
syslog(LOG_ERR, "chain %s not found", miniupnpd_nat_chain);
|
|
}
|
|
else
|
|
{
|
|
#ifdef IPTABLES_143
|
|
for(e = iptc_first_rule(miniupnpd_nat_chain, h);
|
|
e;
|
|
e = iptc_next_rule(e, h))
|
|
#else
|
|
for(e = iptc_first_rule(miniupnpd_nat_chain, &h);
|
|
e;
|
|
e = iptc_next_rule(e, &h))
|
|
#endif
|
|
{
|
|
if(i==index)
|
|
{
|
|
*proto = e->ip.proto;
|
|
match = (const struct ipt_entry_match *)&e->elems;
|
|
if(0 == strncmp(match->u.user.name, "tcp", IPT_FUNCTION_MAXNAMELEN))
|
|
{
|
|
const struct ipt_tcp * info;
|
|
info = (const struct ipt_tcp *)match->data;
|
|
*eport = info->dpts[0];
|
|
}
|
|
else
|
|
{
|
|
const struct ipt_udp * info;
|
|
info = (const struct ipt_udp *)match->data;
|
|
*eport = info->dpts[0];
|
|
}
|
|
target = (void *)e + e->target_offset;
|
|
mr = (const struct ip_nat_multi_range *)&target->data[0];
|
|
snprintip(iaddr, iaddrlen, ntohl(mr->range[0].min_ip));
|
|
*iport = ntohs(mr->range[0].min.all);
|
|
get_redirect_desc(*eport, *proto, desc, desclen, timestamp);
|
|
if(packets)
|
|
*packets = e->counters.pcnt;
|
|
if(bytes)
|
|
*bytes = e->counters.bcnt;
|
|
/* rhost */
|
|
if(rhost && rhostlen > 0) {
|
|
if(e->ip.src.s_addr) {
|
|
snprintip(rhost, rhostlen, ntohl(e->ip.src.s_addr));
|
|
} else {
|
|
rhost[0] = '\0';
|
|
}
|
|
}
|
|
r = 0;
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
if(h)
|
|
#ifdef IPTABLES_143
|
|
iptc_free(h);
|
|
#else
|
|
iptc_free(&h);
|
|
#endif
|
|
#endif
|
|
return r;
|
|
}
|
|
|
|
/* get_peer_rule_by_index()
|
|
* return -1 when the rule was not found */
|
|
int
|
|
get_peer_rule_by_index(int index,
|
|
char * ifname, unsigned short * eport,
|
|
char * iaddr, int iaddrlen, unsigned short * iport,
|
|
int * proto, char * desc, int desclen,
|
|
char * rhost, int rhostlen, unsigned short * rport,
|
|
unsigned int * timestamp,
|
|
u_int64_t * packets, u_int64_t * bytes)
|
|
{
|
|
int r = -1;
|
|
#if USE_INDEX_FROM_DESC_LIST && 0
|
|
r = get_redirect_desc_by_index(index, eport, proto,
|
|
desc, desclen, timestamp);
|
|
if (r==0)
|
|
{
|
|
r = get_redirect_rule(ifname, *eport, *proto, iaddr, iaddrlen, iport,
|
|
0, 0, packets, bytes);
|
|
}
|
|
#else
|
|
int i = 0;
|
|
IPTC_HANDLE h;
|
|
const struct ipt_entry * e;
|
|
const struct ipt_entry_target * target;
|
|
const struct ip_nat_multi_range * mr;
|
|
const struct ipt_entry_match *match;
|
|
UNUSED(ifname);
|
|
|
|
h = iptc_init("nat");
|
|
if(!h)
|
|
{
|
|
syslog(LOG_ERR, "get_peer_rule_by_index() : "
|
|
"iptc_init() failed : %s",
|
|
iptc_strerror(errno));
|
|
return -1;
|
|
}
|
|
if(!iptc_is_chain(miniupnpd_nat_postrouting_chain, h))
|
|
{
|
|
syslog(LOG_ERR, "chain %s not found", miniupnpd_nat_postrouting_chain);
|
|
}
|
|
else
|
|
{
|
|
#ifdef IPTABLES_143
|
|
for(e = iptc_first_rule(miniupnpd_nat_postrouting_chain, h);
|
|
e;
|
|
e = iptc_next_rule(e, h))
|
|
#else
|
|
for(e = iptc_first_rule(miniupnpd_nat_postrouting_chain, &h);
|
|
e;
|
|
e = iptc_next_rule(e, &h))
|
|
#endif
|
|
{
|
|
if(i==index)
|
|
{
|
|
*proto = e->ip.proto;
|
|
match = (const struct ipt_entry_match *)&e->elems;
|
|
if(0 == strncmp(match->u.user.name, "tcp", IPT_FUNCTION_MAXNAMELEN))
|
|
{
|
|
const struct ipt_tcp * info;
|
|
info = (const struct ipt_tcp *)match->data;
|
|
if (rport)
|
|
*rport = info->dpts[0];
|
|
if (iport)
|
|
*iport = info->spts[0];
|
|
}
|
|
else
|
|
{
|
|
const struct ipt_udp * info;
|
|
info = (const struct ipt_udp *)match->data;
|
|
if (rport)
|
|
*rport = info->dpts[0];
|
|
if (iport)
|
|
*iport = info->spts[0];
|
|
}
|
|
target = (void *)e + e->target_offset;
|
|
mr = (const struct ip_nat_multi_range *)&target->data[0];
|
|
*eport = ntohs(mr->range[0].min.all);
|
|
get_redirect_desc(*eport, *proto, desc, desclen, timestamp);
|
|
if(packets)
|
|
*packets = e->counters.pcnt;
|
|
if(bytes)
|
|
*bytes = e->counters.bcnt;
|
|
/* rhost */
|
|
if(rhost && rhostlen > 0) {
|
|
if(e->ip.dst.s_addr) {
|
|
snprintip(rhost, rhostlen, ntohl(e->ip.dst.s_addr));
|
|
} else {
|
|
rhost[0] = '\0';
|
|
}
|
|
}
|
|
if(iaddr && iaddrlen > 0) {
|
|
if(e->ip.src.s_addr) {
|
|
snprintip(iaddr, iaddrlen, ntohl(e->ip.src.s_addr));
|
|
} else {
|
|
rhost[0] = '\0';
|
|
}
|
|
}
|
|
r = 0;
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
if(h)
|
|
#ifdef IPTABLES_143
|
|
iptc_free(h);
|
|
#else
|
|
iptc_free(&h);
|
|
#endif
|
|
#endif
|
|
return r;
|
|
}
|
|
|
|
/* delete_rule_and_commit() :
|
|
* subfunction used in delete_redirect_and_filter_rules() */
|
|
static int
|
|
delete_rule_and_commit(unsigned int index, IPTC_HANDLE h,
|
|
const char * miniupnpd_chain,
|
|
const char * logcaller)
|
|
{
|
|
int r = 0;
|
|
#ifdef IPTABLES_143
|
|
if(!iptc_delete_num_entry(miniupnpd_chain, index, h))
|
|
#else
|
|
if(!iptc_delete_num_entry(miniupnpd_chain, index, &h))
|
|
#endif
|
|
{
|
|
syslog(LOG_ERR, "%s() : iptc_delete_num_entry(): %s\n",
|
|
logcaller, iptc_strerror(errno));
|
|
r = -1;
|
|
}
|
|
#ifdef IPTABLES_143
|
|
else if(!iptc_commit(h))
|
|
#else
|
|
else if(!iptc_commit(&h))
|
|
#endif
|
|
{
|
|
syslog(LOG_ERR, "%s() : iptc_commit(): %s\n",
|
|
logcaller, iptc_strerror(errno));
|
|
r = -1;
|
|
}
|
|
if(h)
|
|
#ifdef IPTABLES_143
|
|
iptc_free(h);
|
|
#else
|
|
iptc_free(&h);
|
|
#endif
|
|
return r;
|
|
}
|
|
|
|
/* delete_redirect_and_filter_rules()
|
|
*/
|
|
int
|
|
delete_redirect_and_filter_rules(unsigned short eport, int proto)
|
|
{
|
|
int r = -1, r2 = -1;
|
|
unsigned index = 0;
|
|
unsigned i = 0;
|
|
IPTC_HANDLE h;
|
|
const struct ipt_entry * e;
|
|
const struct ipt_entry_target * target;
|
|
const struct ip_nat_multi_range * mr;
|
|
const struct ipt_entry_match *match;
|
|
unsigned short iport = 0;
|
|
uint32_t iaddr = 0;
|
|
|
|
h = iptc_init("nat");
|
|
if(!h)
|
|
{
|
|
syslog(LOG_ERR, "delete_redirect_and_filter_rules() : "
|
|
"iptc_init() failed : %s",
|
|
iptc_strerror(errno));
|
|
return -1;
|
|
}
|
|
/* First step : find the right nat rule */
|
|
if(!iptc_is_chain(miniupnpd_nat_chain, h))
|
|
{
|
|
syslog(LOG_ERR, "chain %s not found", miniupnpd_nat_chain);
|
|
}
|
|
else
|
|
{
|
|
#ifdef IPTABLES_143
|
|
for(e = iptc_first_rule(miniupnpd_nat_chain, h);
|
|
e;
|
|
e = iptc_next_rule(e, h), i++)
|
|
#else
|
|
for(e = iptc_first_rule(miniupnpd_nat_chain, &h);
|
|
e;
|
|
e = iptc_next_rule(e, &h), i++)
|
|
#endif
|
|
{
|
|
if(proto==e->ip.proto)
|
|
{
|
|
match = (const struct ipt_entry_match *)&e->elems;
|
|
if(0 == strncmp(match->u.user.name, "tcp", IPT_FUNCTION_MAXNAMELEN))
|
|
{
|
|
const struct ipt_tcp * info;
|
|
info = (const struct ipt_tcp *)match->data;
|
|
if(eport != info->dpts[0])
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
const struct ipt_udp * info;
|
|
info = (const struct ipt_udp *)match->data;
|
|
if(eport != info->dpts[0])
|
|
continue;
|
|
}
|
|
/* get the index, the internal address and the internal port
|
|
* of the rule */
|
|
index = i;
|
|
target = (void *)e + e->target_offset;
|
|
mr = (const struct ip_nat_multi_range *)&target->data[0];
|
|
iaddr = mr->range[0].min_ip;
|
|
iport = ntohs(mr->range[0].min.all);
|
|
r = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(h)
|
|
#ifdef IPTABLES_143
|
|
iptc_free(h);
|
|
#else
|
|
iptc_free(&h);
|
|
#endif
|
|
if(r == 0)
|
|
{
|
|
syslog(LOG_INFO, "Trying to delete nat rule at index %u", index);
|
|
/* Now delete both rules */
|
|
/* first delete the nat rule */
|
|
h = iptc_init("nat");
|
|
if(h)
|
|
{
|
|
r = delete_rule_and_commit(index, h, miniupnpd_nat_chain, "delete_redirect_rule");
|
|
}
|
|
if((r == 0) && (h = iptc_init("filter")))
|
|
{
|
|
i = 0;
|
|
/* we must find the right index for the filter rule */
|
|
#ifdef IPTABLES_143
|
|
for(e = iptc_first_rule(miniupnpd_forward_chain, h);
|
|
e;
|
|
e = iptc_next_rule(e, h), i++)
|
|
#else
|
|
for(e = iptc_first_rule(miniupnpd_forward_chain, &h);
|
|
e;
|
|
e = iptc_next_rule(e, &h), i++)
|
|
#endif
|
|
{
|
|
if(proto==e->ip.proto)
|
|
{
|
|
match = (const struct ipt_entry_match *)&e->elems;
|
|
/*syslog(LOG_DEBUG, "filter rule #%u: %s %s",
|
|
i, match->u.user.name, inet_ntoa(e->ip.dst));*/
|
|
if(0 == strncmp(match->u.user.name, "tcp", IPT_FUNCTION_MAXNAMELEN))
|
|
{
|
|
const struct ipt_tcp * info;
|
|
info = (const struct ipt_tcp *)match->data;
|
|
if(iport != info->dpts[0])
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
const struct ipt_udp * info;
|
|
info = (const struct ipt_udp *)match->data;
|
|
if(iport != info->dpts[0])
|
|
continue;
|
|
}
|
|
if(iaddr != e->ip.dst.s_addr)
|
|
continue;
|
|
index = i;
|
|
syslog(LOG_INFO, "Trying to delete filter rule at index %u", index);
|
|
r = delete_rule_and_commit(index, h, miniupnpd_forward_chain, "delete_filter_rule");
|
|
h = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(h)
|
|
#ifdef IPTABLES_143
|
|
iptc_free(h);
|
|
#else
|
|
iptc_free(&h);
|
|
#endif
|
|
}
|
|
|
|
/*delete PEER rule*/
|
|
if((h = iptc_init("nat")))
|
|
{
|
|
i = 0;
|
|
/* we must find the right index for the filter rule */
|
|
#ifdef IPTABLES_143
|
|
for(e = iptc_first_rule(miniupnpd_nat_postrouting_chain, h);
|
|
e;
|
|
e = iptc_next_rule(e, h), i++)
|
|
#else
|
|
for(e = iptc_first_rule(miniupnpd_nat_postrouting_chain, &h);
|
|
e;
|
|
e = iptc_next_rule(e, &h), i++)
|
|
#endif
|
|
{
|
|
if(proto==e->ip.proto)
|
|
{
|
|
target = (void *)e + e->target_offset;
|
|
mr = (const struct ip_nat_multi_range *)&target->data[0];
|
|
if (eport != ntohs(mr->range[0].min.all)) {
|
|
continue;
|
|
}
|
|
iaddr = e->ip.src.s_addr;
|
|
match = (const struct ipt_entry_match *)&e->elems;
|
|
if(0 == strncmp(match->u.user.name, "tcp", IPT_FUNCTION_MAXNAMELEN))
|
|
{
|
|
const struct ipt_tcp * info;
|
|
info = (const struct ipt_tcp *)match->data;
|
|
iport = info->spts[0];
|
|
}
|
|
else
|
|
{
|
|
const struct ipt_udp * info;
|
|
info = (const struct ipt_udp *)match->data;
|
|
iport = info->dpts[0];
|
|
}
|
|
|
|
index = i;
|
|
syslog(LOG_INFO, "Trying to delete peer rule at index %u", index);
|
|
r2 = delete_rule_and_commit(index, h, miniupnpd_nat_postrouting_chain, "delete_peer_rule");
|
|
h = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(h)
|
|
#ifdef IPTABLES_143
|
|
iptc_free(h);
|
|
#else
|
|
iptc_free(&h);
|
|
#endif
|
|
/*delete DSCP rule*/
|
|
if((r2==0)&&(h = iptc_init("mangle")))
|
|
{
|
|
i = 0;
|
|
index = -1;
|
|
/* we must find the right index for the filter rule */
|
|
#ifdef IPTABLES_143
|
|
for(e = iptc_first_rule(miniupnpd_nat_chain, h);
|
|
e;
|
|
e = iptc_next_rule(e, h), i++)
|
|
#else
|
|
for(e = iptc_first_rule(miniupnpd_nat_chain, &h);
|
|
e;
|
|
e = iptc_next_rule(e, &h), i++)
|
|
#endif
|
|
{
|
|
if(proto==e->ip.proto)
|
|
{
|
|
match = (const struct ipt_entry_match *)&e->elems;
|
|
/*syslog(LOG_DEBUG, "filter rule #%u: %s %s",
|
|
i, match->u.user.name, inet_ntoa(e->ip.dst));*/
|
|
if(0 == strncmp(match->u.user.name, "tcp", IPT_FUNCTION_MAXNAMELEN))
|
|
{
|
|
const struct ipt_tcp * info;
|
|
info = (const struct ipt_tcp *)match->data;
|
|
if(iport != info->spts[0])
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
const struct ipt_udp * info;
|
|
info = (const struct ipt_udp *)match->data;
|
|
if(iport != info->spts[0])
|
|
continue;
|
|
}
|
|
if(iaddr != e->ip.src.s_addr)
|
|
continue;
|
|
index = i;
|
|
syslog(LOG_INFO, "Trying to delete dscp rule at index %u", index);
|
|
r2 = delete_rule_and_commit(index, h, miniupnpd_nat_chain, "delete_dscp_rule");
|
|
h = NULL;
|
|
break;
|
|
}
|
|
}
|
|
if (h)
|
|
#ifdef IPTABLES_143
|
|
iptc_free(h);
|
|
#else
|
|
iptc_free(&h);
|
|
#endif
|
|
}
|
|
|
|
del_redirect_desc(eport, proto);
|
|
return r*r2;
|
|
}
|
|
|
|
/* ==================================== */
|
|
/* TODO : add the -m state --state NEW,ESTABLISHED,RELATED
|
|
* only for the filter rule */
|
|
static struct ipt_entry_match *
|
|
get_tcp_match(unsigned short dport, unsigned short sport)
|
|
{
|
|
struct ipt_entry_match *match;
|
|
struct ipt_tcp * tcpinfo;
|
|
size_t size;
|
|
size = IPT_ALIGN(sizeof(struct ipt_entry_match))
|
|
+ IPT_ALIGN(sizeof(struct ipt_tcp));
|
|
match = calloc(1, size);
|
|
match->u.match_size = size;
|
|
strncpy(match->u.user.name, "tcp", sizeof(match->u.user.name));
|
|
tcpinfo = (struct ipt_tcp *)match->data;
|
|
if (sport == 0) {
|
|
tcpinfo->spts[0] = 0; /* all source ports */
|
|
tcpinfo->spts[1] = 0xFFFF;
|
|
} else {
|
|
tcpinfo->spts[0] = sport; /* specified source port */
|
|
tcpinfo->spts[1] = sport;
|
|
}
|
|
if (dport == 0) {
|
|
tcpinfo->dpts[0] = 0; /* all destination ports */
|
|
tcpinfo->dpts[1] = 0xFFFF;
|
|
} else {
|
|
tcpinfo->dpts[0] = dport; /* specified destination port */
|
|
tcpinfo->dpts[1] = dport;
|
|
}
|
|
return match;
|
|
}
|
|
|
|
static struct ipt_entry_match *
|
|
get_udp_match(unsigned short dport, unsigned short sport)
|
|
{
|
|
struct ipt_entry_match *match;
|
|
struct ipt_udp * udpinfo;
|
|
size_t size;
|
|
size = IPT_ALIGN(sizeof(struct ipt_entry_match))
|
|
+ IPT_ALIGN(sizeof(struct ipt_udp));
|
|
match = calloc(1, size);
|
|
match->u.match_size = size;
|
|
strncpy(match->u.user.name, "udp", sizeof(match->u.user.name));
|
|
udpinfo = (struct ipt_udp *)match->data;
|
|
if (sport == 0) {
|
|
udpinfo->spts[0] = 0; /* all source ports */
|
|
udpinfo->spts[1] = 0xFFFF;
|
|
} else {
|
|
udpinfo->spts[0] = sport; /* specified source port */
|
|
udpinfo->spts[1] = sport;
|
|
}
|
|
if (dport == 0) {
|
|
udpinfo->dpts[0] = 0; /* all destination ports */
|
|
udpinfo->dpts[1] = 0xFFFF;
|
|
} else {
|
|
udpinfo->dpts[0] = dport; /* specified destination port */
|
|
udpinfo->dpts[1] = dport;
|
|
}
|
|
return match;
|
|
}
|
|
|
|
static struct ipt_entry_target *
|
|
get_dnat_target(const char * daddr, unsigned short dport)
|
|
{
|
|
struct ipt_entry_target * target;
|
|
struct ip_nat_multi_range * mr;
|
|
struct ip_nat_range * range;
|
|
size_t size;
|
|
|
|
size = IPT_ALIGN(sizeof(struct ipt_entry_target))
|
|
+ IPT_ALIGN(sizeof(struct ip_nat_multi_range));
|
|
target = calloc(1, size);
|
|
target->u.target_size = size;
|
|
strncpy(target->u.user.name, "DNAT", sizeof(target->u.user.name));
|
|
/* one ip_nat_range already included in ip_nat_multi_range */
|
|
mr = (struct ip_nat_multi_range *)&target->data[0];
|
|
mr->rangesize = 1;
|
|
range = &mr->range[0];
|
|
range->min_ip = range->max_ip = inet_addr(daddr);
|
|
range->flags |= IP_NAT_RANGE_MAP_IPS;
|
|
range->min.all = range->max.all = htons(dport);
|
|
range->flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
|
|
return target;
|
|
}
|
|
|
|
static struct ipt_entry_target *
|
|
get_snat_target(const char * saddr, unsigned short sport)
|
|
{
|
|
struct ipt_entry_target * target;
|
|
struct ip_nat_multi_range * mr;
|
|
struct ip_nat_range * range;
|
|
size_t size;
|
|
|
|
size = IPT_ALIGN(sizeof(struct ipt_entry_target))
|
|
+ IPT_ALIGN(sizeof(struct ip_nat_multi_range));
|
|
target = calloc(1, size);
|
|
target->u.target_size = size;
|
|
strncpy(target->u.user.name, "SNAT", sizeof(target->u.user.name));
|
|
/* one ip_nat_range already included in ip_nat_multi_range */
|
|
mr = (struct ip_nat_multi_range *)&target->data[0];
|
|
mr->rangesize = 1;
|
|
range = &mr->range[0];
|
|
range->min_ip = range->max_ip = inet_addr(saddr);
|
|
range->flags |= IP_NAT_RANGE_MAP_IPS;
|
|
range->min.all = range->max.all = htons(sport);
|
|
range->flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
|
|
return target;
|
|
}
|
|
|
|
static struct ipt_entry_target *
|
|
get_dscp_target(unsigned char dscp)
|
|
{
|
|
struct ipt_entry_target * target;
|
|
struct xt_DSCP_info * di;
|
|
size_t size;
|
|
|
|
size = IPT_ALIGN(sizeof(struct ipt_entry_target))
|
|
+ IPT_ALIGN(sizeof(struct xt_DSCP_info));
|
|
target = calloc(1, size);
|
|
target->u.target_size = size;
|
|
strncpy(target->u.user.name, "DSCP", sizeof(target->u.user.name));
|
|
/* one ip_nat_range already included in ip_nat_multi_range */
|
|
di = (struct xt_DSCP_info *)&target->data[0];
|
|
di->dscp=dscp;
|
|
return target;
|
|
}
|
|
|
|
static struct ipt_entry_target *
|
|
get_masquerade_target(unsigned short port)
|
|
{
|
|
struct ipt_entry_target * target;
|
|
struct ip_nat_multi_range * mr;
|
|
struct ip_nat_range * range;
|
|
size_t size;
|
|
|
|
size = IPT_ALIGN(sizeof(struct ipt_entry_target))
|
|
+ IPT_ALIGN(sizeof(struct ip_nat_multi_range));
|
|
target = calloc(1, size);
|
|
target->u.target_size = size;
|
|
strncpy(target->u.user.name, "MASQUERADE", sizeof(target->u.user.name));
|
|
/* one ip_nat_range already included in ip_nat_multi_range */
|
|
mr = (struct ip_nat_multi_range *)&target->data[0];
|
|
mr->rangesize = 1;
|
|
range = &mr->range[0];
|
|
range->min.tcp.port = range->max.tcp.port = htons(port);
|
|
/*range->min.all = range->max.all = htons(port);*/
|
|
range->flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
|
|
return target;
|
|
}
|
|
|
|
/* iptc_init_verify_and_append()
|
|
* return 0 on success, -1 on failure */
|
|
static int
|
|
iptc_init_verify_and_append(const char * table,
|
|
const char * miniupnpd_chain,
|
|
struct ipt_entry * e,
|
|
const char * logcaller)
|
|
{
|
|
IPTC_HANDLE h;
|
|
h = iptc_init(table);
|
|
if(!h)
|
|
{
|
|
syslog(LOG_ERR, "%s() : iptc_init() error : %s\n",
|
|
logcaller, iptc_strerror(errno));
|
|
return -1;
|
|
}
|
|
if(!iptc_is_chain(miniupnpd_chain, h))
|
|
{
|
|
syslog(LOG_ERR, "%s() : chain %s not found",
|
|
logcaller, miniupnpd_chain);
|
|
if(h)
|
|
#ifdef IPTABLES_143
|
|
iptc_free(h);
|
|
#else
|
|
iptc_free(&h);
|
|
#endif
|
|
return -1;
|
|
}
|
|
/* iptc_insert_entry(miniupnpd_chain, e, n, h/&h) could also be used */
|
|
#ifdef IPTABLES_143
|
|
if(!iptc_append_entry(miniupnpd_chain, e, h))
|
|
#else
|
|
if(!iptc_append_entry(miniupnpd_chain, e, &h))
|
|
#endif
|
|
{
|
|
syslog(LOG_ERR, "%s() : iptc_append_entry() error : %s\n",
|
|
logcaller, iptc_strerror(errno));
|
|
if(h)
|
|
#ifdef IPTABLES_143
|
|
iptc_free(h);
|
|
#else
|
|
iptc_free(&h);
|
|
#endif
|
|
return -1;
|
|
}
|
|
#ifdef IPTABLES_143
|
|
if(!iptc_commit(h))
|
|
#else
|
|
if(!iptc_commit(&h))
|
|
#endif
|
|
{
|
|
syslog(LOG_ERR, "%s() : iptc_commit() error : %s\n",
|
|
logcaller, iptc_strerror(errno));
|
|
if(h)
|
|
#ifdef IPTABLES_143
|
|
iptc_free(h);
|
|
#else
|
|
iptc_free(&h);
|
|
#endif
|
|
return -1;
|
|
}
|
|
if(h)
|
|
#ifdef IPTABLES_143
|
|
iptc_free(h);
|
|
#else
|
|
iptc_free(&h);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
/* add nat rule
|
|
* iptables -t nat -A MINIUPNPD -p proto --dport eport -j DNAT --to iaddr:iport
|
|
* */
|
|
static int
|
|
addnatrule(int proto, unsigned short eport,
|
|
const char * iaddr, unsigned short iport,
|
|
const char * rhost)
|
|
{
|
|
int r = 0;
|
|
struct ipt_entry * e;
|
|
struct ipt_entry * tmp;
|
|
struct ipt_entry_match *match = NULL;
|
|
struct ipt_entry_target *target = NULL;
|
|
|
|
e = calloc(1, sizeof(struct ipt_entry));
|
|
if(!e) {
|
|
syslog(LOG_ERR, "%s: calloc(%d) error", "addnatrule",
|
|
(int)sizeof(struct ipt_entry));
|
|
return -1;
|
|
}
|
|
e->ip.proto = proto;
|
|
if(proto == IPPROTO_TCP) {
|
|
match = get_tcp_match(eport, 0);
|
|
} else {
|
|
match = get_udp_match(eport, 0);
|
|
}
|
|
e->nfcache = NFC_IP_DST_PT;
|
|
target = get_dnat_target(iaddr, iport);
|
|
e->nfcache |= NFC_UNKNOWN;
|
|
tmp = realloc(e, sizeof(struct ipt_entry)
|
|
+ match->u.match_size
|
|
+ target->u.target_size);
|
|
if(!tmp) {
|
|
syslog(LOG_ERR, "%s: realloc(%d) error", "addnatrule",
|
|
(int)(sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size));
|
|
free(e);
|
|
free(match);
|
|
free(target);
|
|
return -1;
|
|
}
|
|
e = tmp;
|
|
memcpy(e->elems, match, match->u.match_size);
|
|
memcpy(e->elems + match->u.match_size, target, target->u.target_size);
|
|
e->target_offset = sizeof(struct ipt_entry)
|
|
+ match->u.match_size;
|
|
e->next_offset = sizeof(struct ipt_entry)
|
|
+ match->u.match_size
|
|
+ target->u.target_size;
|
|
/* remote host */
|
|
if(rhost && (rhost[0] != '\0') && (0 != strcmp(rhost, "*"))) {
|
|
e->ip.src.s_addr = inet_addr(rhost);
|
|
e->ip.smsk.s_addr = INADDR_NONE;
|
|
}
|
|
|
|
r = iptc_init_verify_and_append("nat", miniupnpd_nat_chain, e, "addnatrule");
|
|
free(target);
|
|
free(match);
|
|
free(e);
|
|
return r;
|
|
}
|
|
|
|
/* for "Port Triggering"
|
|
* Section 2.5.16 figure 2.2 in UPnP-gw-WANIPConnection-v2-Service.pdf
|
|
* iptables -t nat -I POSTROUTING -o extif -s iaddr -p UDP --sport iport -j MASQUERADE --to-ports eport
|
|
* iptables -t nat -A MINIUPNPD-POSTROUTING -o extif -s iaddr -p UDP --sport iport -j MASQUERADE --to-ports eport
|
|
*/
|
|
static int
|
|
addmasqueraderule(int proto,
|
|
unsigned short eport,
|
|
const char * iaddr, unsigned short iport,
|
|
const char * rhost, const char * extif)
|
|
{
|
|
int r = 0;
|
|
struct ipt_entry * e;
|
|
struct ipt_entry * tmp;
|
|
struct ipt_entry_match *match = NULL;
|
|
struct ipt_entry_target *target = NULL;
|
|
|
|
e = calloc(1, sizeof(struct ipt_entry));
|
|
if(!e) {
|
|
syslog(LOG_ERR, "%s: calloc(%d) error", "addmasqueraderule",
|
|
(int)sizeof(struct ipt_entry));
|
|
return -1;
|
|
}
|
|
e->ip.proto = proto;
|
|
if(proto == IPPROTO_TCP) {
|
|
match = get_tcp_match(0, iport);
|
|
} else {
|
|
match = get_udp_match(0, iport);
|
|
}
|
|
e->nfcache = NFC_IP_DST_PT;
|
|
target = get_masquerade_target(eport);
|
|
e->nfcache |= NFC_UNKNOWN;
|
|
tmp = realloc(e, sizeof(struct ipt_entry)
|
|
+ match->u.match_size
|
|
+ target->u.target_size);
|
|
if(!tmp) {
|
|
syslog(LOG_ERR, "%s: realloc(%d) error", "addmasqueraderule",
|
|
(int)(sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size));
|
|
free(e);
|
|
free(match);
|
|
free(target);
|
|
return -1;
|
|
}
|
|
e = tmp;
|
|
memcpy(e->elems, match, match->u.match_size);
|
|
memcpy(e->elems + match->u.match_size, target, target->u.target_size);
|
|
e->target_offset = sizeof(struct ipt_entry)
|
|
+ match->u.match_size;
|
|
e->next_offset = sizeof(struct ipt_entry)
|
|
+ match->u.match_size
|
|
+ target->u.target_size;
|
|
if(extif != NULL) {
|
|
strncpy(e->ip.outiface, extif, sizeof(e->ip.outiface));
|
|
memset(e->ip.outiface_mask, 0xff, strlen(e->ip.outiface) + 1);/* Include nul-terminator in match */
|
|
}
|
|
/* internal host */
|
|
if(iaddr && (iaddr[0] != '\0') && (0 != strcmp(iaddr, "*")))
|
|
{
|
|
e->ip.src.s_addr = inet_addr(iaddr);
|
|
e->ip.smsk.s_addr = INADDR_NONE;
|
|
}
|
|
/* remote host */
|
|
if(rhost && (rhost[0] != '\0') && (0 != strcmp(rhost, "*"))) {
|
|
e->ip.dst.s_addr = inet_addr(rhost);
|
|
e->ip.dmsk.s_addr = INADDR_NONE;
|
|
}
|
|
|
|
r = iptc_init_verify_and_append("nat", miniupnpd_nat_postrouting_chain, e, "addmasqueraderule");
|
|
free(target);
|
|
free(match);
|
|
free(e);
|
|
return r;
|
|
}
|
|
|
|
/* iptables -t nat -A MINIUPNPD-POSTROUTING -s iaddr -d rhost
|
|
* -p proto --sport iport --dport rport -j SNAT
|
|
* --to-source ext_ip:eport */
|
|
static int
|
|
addpeernatrule(int proto,
|
|
const char * eaddr, unsigned short eport,
|
|
const char * iaddr, unsigned short iport,
|
|
const char * rhost, unsigned short rport)
|
|
{
|
|
int r = 0;
|
|
struct ipt_entry * e;
|
|
struct ipt_entry * tmp;
|
|
struct ipt_entry_match *match = NULL;
|
|
struct ipt_entry_target *target = NULL;
|
|
|
|
e = calloc(1, sizeof(struct ipt_entry));
|
|
if(!e) {
|
|
syslog(LOG_ERR, "%s: calloc(%d) error", "addpeernatrule",
|
|
(int)sizeof(struct ipt_entry));
|
|
return -1;
|
|
}
|
|
e->ip.proto = proto;
|
|
/* TODO: Fill port matches and SNAT */
|
|
if(proto == IPPROTO_TCP) {
|
|
match = get_tcp_match(rport, iport);
|
|
} else {
|
|
match = get_udp_match(rport, iport);
|
|
}
|
|
e->nfcache = NFC_IP_DST_PT | NFC_IP_SRC_PT;
|
|
target = get_snat_target(eaddr, eport);
|
|
e->nfcache |= NFC_UNKNOWN;
|
|
tmp = realloc(e, sizeof(struct ipt_entry)
|
|
+ match->u.match_size
|
|
+ target->u.target_size);
|
|
if(!tmp) {
|
|
syslog(LOG_ERR, "%s: realloc(%d) error", "addpeernatrule",
|
|
(int)(sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size));
|
|
free(e);
|
|
free(match);
|
|
free(target);
|
|
return -1;
|
|
}
|
|
e = tmp;
|
|
memcpy(e->elems, match, match->u.match_size);
|
|
memcpy(e->elems + match->u.match_size, target, target->u.target_size);
|
|
e->target_offset = sizeof(struct ipt_entry)
|
|
+ match->u.match_size;
|
|
e->next_offset = sizeof(struct ipt_entry)
|
|
+ match->u.match_size
|
|
+ target->u.target_size;
|
|
|
|
/* internal host */
|
|
if(iaddr && (iaddr[0] != '\0') && (0 != strcmp(iaddr, "*")))
|
|
{
|
|
e->ip.src.s_addr = inet_addr(iaddr);
|
|
e->ip.smsk.s_addr = INADDR_NONE;
|
|
}
|
|
/* remote host */
|
|
if(rhost && (rhost[0] != '\0') && (0 != strcmp(rhost, "*")))
|
|
{
|
|
e->ip.dst.s_addr = inet_addr(rhost);
|
|
e->ip.dmsk.s_addr = INADDR_NONE;
|
|
}
|
|
|
|
r = iptc_init_verify_and_append("nat", miniupnpd_nat_postrouting_chain, e, "addpeernatrule");
|
|
free(target);
|
|
free(match);
|
|
free(e);
|
|
return r;
|
|
}
|
|
|
|
/* iptables -t mangle -A MINIUPNPD -s iaddr -d rhost
|
|
* -p proto --sport iport --dport rport -j DSCP
|
|
* --set-dscp 0xXXXX */
|
|
static int
|
|
addpeerdscprule(int proto, unsigned char dscp,
|
|
const char * iaddr, unsigned short iport,
|
|
const char * rhost, unsigned short rport)
|
|
{
|
|
int r = 0;
|
|
struct ipt_entry * e;
|
|
struct ipt_entry * tmp;
|
|
struct ipt_entry_match *match = NULL;
|
|
struct ipt_entry_target *target = NULL;
|
|
|
|
e = calloc(1, sizeof(struct ipt_entry));
|
|
if(!e) {
|
|
syslog(LOG_ERR, "%s: calloc(%d) error", "addpeerdscprule",
|
|
(int)sizeof(struct ipt_entry));
|
|
return -1;
|
|
}
|
|
e->ip.proto = proto;
|
|
/* TODO: Fill port matches and SNAT */
|
|
if(proto == IPPROTO_TCP) {
|
|
match = get_tcp_match(rport, iport);
|
|
} else {
|
|
match = get_udp_match(rport, iport);
|
|
}
|
|
e->nfcache = NFC_IP_DST_PT | NFC_IP_SRC_PT;
|
|
target = get_dscp_target(dscp);
|
|
e->nfcache |= NFC_UNKNOWN;
|
|
tmp = realloc(e, sizeof(struct ipt_entry)
|
|
+ match->u.match_size
|
|
+ target->u.target_size);
|
|
if(!tmp) {
|
|
syslog(LOG_ERR, "%s: realloc(%d) error", "addpeerdscprule",
|
|
(int)(sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size));
|
|
free(e);
|
|
free(match);
|
|
free(target);
|
|
return -1;
|
|
}
|
|
e = tmp;
|
|
memcpy(e->elems, match, match->u.match_size);
|
|
memcpy(e->elems + match->u.match_size, target, target->u.target_size);
|
|
e->target_offset = sizeof(struct ipt_entry)
|
|
+ match->u.match_size;
|
|
e->next_offset = sizeof(struct ipt_entry)
|
|
+ match->u.match_size
|
|
+ target->u.target_size;
|
|
|
|
/* internal host */
|
|
if(iaddr && (iaddr[0] != '\0') && (0 != strcmp(iaddr, "*")))
|
|
{
|
|
e->ip.src.s_addr = inet_addr(iaddr);
|
|
e->ip.smsk.s_addr = INADDR_NONE;
|
|
}
|
|
/* remote host */
|
|
if(rhost && (rhost[0] != '\0') && (0 != strcmp(rhost, "*")))
|
|
{
|
|
e->ip.dst.s_addr = inet_addr(rhost);
|
|
e->ip.dmsk.s_addr = INADDR_NONE;
|
|
}
|
|
|
|
r = iptc_init_verify_and_append("mangle", miniupnpd_nat_chain, e,
|
|
"addpeerDSCPrule");
|
|
free(target);
|
|
free(match);
|
|
free(e);
|
|
return r;
|
|
}
|
|
|
|
|
|
/* ================================= */
|
|
static struct ipt_entry_target *
|
|
get_accept_target(void)
|
|
{
|
|
struct ipt_entry_target * target = NULL;
|
|
size_t size;
|
|
size = IPT_ALIGN(sizeof(struct ipt_entry_target))
|
|
+ IPT_ALIGN(sizeof(int));
|
|
target = calloc(1, size);
|
|
target->u.user.target_size = size;
|
|
strncpy(target->u.user.name, "ACCEPT", sizeof(target->u.user.name));
|
|
return target;
|
|
}
|
|
|
|
/* add_filter_rule()
|
|
* */
|
|
static int
|
|
add_filter_rule(int proto, const char * rhost,
|
|
const char * iaddr, unsigned short iport)
|
|
{
|
|
int r = 0;
|
|
struct ipt_entry * e;
|
|
struct ipt_entry * tmp;
|
|
struct ipt_entry_match *match = NULL;
|
|
struct ipt_entry_target *target = NULL;
|
|
|
|
e = calloc(1, sizeof(struct ipt_entry));
|
|
if(!e) {
|
|
syslog(LOG_ERR, "%s: calloc(%d) error", "add_filter_rule",
|
|
(int)sizeof(struct ipt_entry));
|
|
return -1;
|
|
}
|
|
e->ip.proto = proto;
|
|
if(proto == IPPROTO_TCP) {
|
|
match = get_tcp_match(iport,0);
|
|
} else {
|
|
match = get_udp_match(iport,0);
|
|
}
|
|
e->nfcache = NFC_IP_DST_PT;
|
|
e->ip.dst.s_addr = inet_addr(iaddr);
|
|
e->ip.dmsk.s_addr = INADDR_NONE;
|
|
target = get_accept_target();
|
|
e->nfcache |= NFC_UNKNOWN;
|
|
tmp = realloc(e, sizeof(struct ipt_entry)
|
|
+ match->u.match_size
|
|
+ target->u.target_size);
|
|
if(!tmp) {
|
|
syslog(LOG_ERR, "%s: realloc(%d) error", "add_filter_rule",
|
|
(int)(sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size));
|
|
free(e);
|
|
free(match);
|
|
free(target);
|
|
return -1;
|
|
}
|
|
e = tmp;
|
|
memcpy(e->elems, match, match->u.match_size);
|
|
memcpy(e->elems + match->u.match_size, target, target->u.target_size);
|
|
e->target_offset = sizeof(struct ipt_entry)
|
|
+ match->u.match_size;
|
|
e->next_offset = sizeof(struct ipt_entry)
|
|
+ match->u.match_size
|
|
+ target->u.target_size;
|
|
/* remote host */
|
|
if(rhost && (rhost[0] != '\0') && (0 != strcmp(rhost, "*")))
|
|
{
|
|
e->ip.src.s_addr = inet_addr(rhost);
|
|
e->ip.smsk.s_addr = INADDR_NONE;
|
|
}
|
|
|
|
r = iptc_init_verify_and_append("filter", miniupnpd_forward_chain, e, "add_filter_rule");
|
|
free(target);
|
|
free(match);
|
|
free(e);
|
|
return r;
|
|
}
|
|
|
|
/* return an (malloc'ed) array of "external" port for which there is
|
|
* a port mapping. number is the size of the array */
|
|
unsigned short *
|
|
get_portmappings_in_range(unsigned short startport, unsigned short endport,
|
|
int proto, unsigned int * number)
|
|
{
|
|
unsigned short * array;
|
|
unsigned int capacity;
|
|
unsigned short eport;
|
|
IPTC_HANDLE h;
|
|
const struct ipt_entry * e;
|
|
const struct ipt_entry_match *match;
|
|
|
|
*number = 0;
|
|
capacity = 128;
|
|
array = calloc(capacity, sizeof(unsigned short));
|
|
if(!array)
|
|
{
|
|
syslog(LOG_ERR, "get_portmappings_in_range() : calloc error");
|
|
return NULL;
|
|
}
|
|
|
|
h = iptc_init("nat");
|
|
if(!h)
|
|
{
|
|
syslog(LOG_ERR, "get_redirect_rule_by_index() : "
|
|
"iptc_init() failed : %s",
|
|
iptc_strerror(errno));
|
|
free(array);
|
|
return NULL;
|
|
}
|
|
if(!iptc_is_chain(miniupnpd_nat_chain, h))
|
|
{
|
|
syslog(LOG_ERR, "chain %s not found", miniupnpd_nat_chain);
|
|
free(array);
|
|
array = NULL;
|
|
}
|
|
else
|
|
{
|
|
#ifdef IPTABLES_143
|
|
for(e = iptc_first_rule(miniupnpd_nat_chain, h);
|
|
e;
|
|
e = iptc_next_rule(e, h))
|
|
#else
|
|
for(e = iptc_first_rule(miniupnpd_nat_chain, &h);
|
|
e;
|
|
e = iptc_next_rule(e, &h))
|
|
#endif
|
|
{
|
|
if(proto == e->ip.proto)
|
|
{
|
|
match = (const struct ipt_entry_match *)&e->elems;
|
|
if(0 == strncmp(match->u.user.name, "tcp", IPT_FUNCTION_MAXNAMELEN))
|
|
{
|
|
const struct ipt_tcp * info;
|
|
info = (const struct ipt_tcp *)match->data;
|
|
eport = info->dpts[0];
|
|
}
|
|
else
|
|
{
|
|
const struct ipt_udp * info;
|
|
info = (const struct ipt_udp *)match->data;
|
|
eport = info->dpts[0];
|
|
}
|
|
if(startport <= eport && eport <= endport)
|
|
{
|
|
if(*number >= capacity)
|
|
{
|
|
unsigned short * tmp;
|
|
/* need to increase the capacity of the array */
|
|
tmp = realloc(array, sizeof(unsigned short)*capacity);
|
|
if(!tmp)
|
|
{
|
|
syslog(LOG_ERR, "get_portmappings_in_range() : realloc(%u) error",
|
|
(unsigned)sizeof(unsigned short)*capacity);
|
|
*number = 0;
|
|
free(array);
|
|
array = NULL;
|
|
break;
|
|
}
|
|
array = tmp;
|
|
}
|
|
array[*number] = eport;
|
|
(*number)++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(h)
|
|
#ifdef IPTABLES_143
|
|
iptc_free(h);
|
|
#else
|
|
iptc_free(&h);
|
|
#endif
|
|
return array;
|
|
}
|
|
|
|
/* ================================ */
|
|
#ifdef DEBUG
|
|
static int
|
|
print_match(const struct ipt_entry_match *match)
|
|
{
|
|
printf("match %s\n", match->u.user.name);
|
|
if(0 == strncmp(match->u.user.name, "tcp", IPT_FUNCTION_MAXNAMELEN))
|
|
{
|
|
struct ipt_tcp * tcpinfo;
|
|
tcpinfo = (struct ipt_tcp *)match->data;
|
|
printf("srcport = %hu:%hu dstport = %hu:%hu\n",
|
|
tcpinfo->spts[0], tcpinfo->spts[1],
|
|
tcpinfo->dpts[0], tcpinfo->dpts[1]);
|
|
}
|
|
else if(0 == strncmp(match->u.user.name, "udp", IPT_FUNCTION_MAXNAMELEN))
|
|
{
|
|
struct ipt_udp * udpinfo;
|
|
udpinfo = (struct ipt_udp *)match->data;
|
|
printf("srcport = %hu:%hu dstport = %hu:%hu\n",
|
|
udpinfo->spts[0], udpinfo->spts[1],
|
|
udpinfo->dpts[0], udpinfo->dpts[1]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
print_iface(const char * iface, const unsigned char * mask, int invert)
|
|
{
|
|
unsigned i;
|
|
if(mask[0] == 0)
|
|
return;
|
|
if(invert)
|
|
printf("! ");
|
|
for(i=0; i<IFNAMSIZ; i++)
|
|
{
|
|
if(mask[i])
|
|
{
|
|
if(iface[i])
|
|
putchar(iface[i]);
|
|
}
|
|
else
|
|
{
|
|
if(iface[i-1])
|
|
putchar('+');
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
printip(uint32_t ip)
|
|
{
|
|
printf("%u.%u.%u.%u", ip >> 24, (ip >> 16) & 0xff,
|
|
(ip >> 8) & 0xff, ip & 0xff);
|
|
}
|
|
|
|
/* for debug */
|
|
/* read the "filter" and "nat" tables */
|
|
int
|
|
list_redirect_rule(const char * ifname)
|
|
{
|
|
IPTC_HANDLE h;
|
|
const struct ipt_entry * e;
|
|
const struct ipt_entry_target * target;
|
|
const struct ip_nat_multi_range * mr;
|
|
const char * target_str;
|
|
char addr[16], mask[16];
|
|
(void)ifname;
|
|
|
|
h = iptc_init("nat");
|
|
if(!h)
|
|
{
|
|
printf("iptc_init() error : %s\n", iptc_strerror(errno));
|
|
return -1;
|
|
}
|
|
if(!iptc_is_chain(miniupnpd_nat_chain, h))
|
|
{
|
|
printf("chain %s not found\n", miniupnpd_nat_chain);
|
|
#ifdef IPTABLES_143
|
|
iptc_free(h);
|
|
#else
|
|
iptc_free(&h);
|
|
#endif
|
|
return -1;
|
|
}
|
|
#ifdef IPTABLES_143
|
|
for(e = iptc_first_rule(miniupnpd_nat_chain, h);
|
|
e;
|
|
e = iptc_next_rule(e, h))
|
|
{
|
|
target_str = iptc_get_target(e, h);
|
|
#else
|
|
for(e = iptc_first_rule(miniupnpd_nat_chain, &h);
|
|
e;
|
|
e = iptc_next_rule(e, &h))
|
|
{
|
|
target_str = iptc_get_target(e, &h);
|
|
#endif
|
|
printf("===\n");
|
|
inet_ntop(AF_INET, &e->ip.src, addr, sizeof(addr));
|
|
inet_ntop(AF_INET, &e->ip.smsk, mask, sizeof(mask));
|
|
printf("src = %s%s/%s\n", (e->ip.invflags & IPT_INV_SRCIP)?"! ":"",
|
|
/*inet_ntoa(e->ip.src), inet_ntoa(e->ip.smsk)*/
|
|
addr, mask);
|
|
inet_ntop(AF_INET, &e->ip.dst, addr, sizeof(addr));
|
|
inet_ntop(AF_INET, &e->ip.dmsk, mask, sizeof(mask));
|
|
printf("dst = %s%s/%s\n", (e->ip.invflags & IPT_INV_DSTIP)?"! ":"",
|
|
/*inet_ntoa(e->ip.dst), inet_ntoa(e->ip.dmsk)*/
|
|
addr, mask);
|
|
/*printf("in_if = %s out_if = %s\n", e->ip.iniface, e->ip.outiface);*/
|
|
printf("in_if = ");
|
|
print_iface(e->ip.iniface, e->ip.iniface_mask,
|
|
e->ip.invflags & IPT_INV_VIA_IN);
|
|
printf(" out_if = ");
|
|
print_iface(e->ip.outiface, e->ip.outiface_mask,
|
|
e->ip.invflags & IPT_INV_VIA_OUT);
|
|
printf("\n");
|
|
printf("ip.proto = %s%d\n", (e->ip.invflags & IPT_INV_PROTO)?"! ":"",
|
|
e->ip.proto);
|
|
/* display matches stuff */
|
|
if(e->target_offset)
|
|
{
|
|
IPT_MATCH_ITERATE(e, print_match);
|
|
/*printf("\n");*/
|
|
}
|
|
printf("target = %s\n", target_str);
|
|
target = (void *)e + e->target_offset;
|
|
mr = (const struct ip_nat_multi_range *)&target->data[0];
|
|
printf("ips ");
|
|
printip(ntohl(mr->range[0].min_ip));
|
|
printf(" ");
|
|
printip(ntohl(mr->range[0].max_ip));
|
|
printf("\nports %hu %hu\n", ntohs(mr->range[0].min.all),
|
|
ntohs(mr->range[0].max.all));
|
|
printf("flags = %x\n", mr->range[0].flags);
|
|
}
|
|
if(h)
|
|
#ifdef IPTABLES_143
|
|
iptc_free(h);
|
|
#else
|
|
iptc_free(&h);
|
|
#endif
|
|
return 0;
|
|
}
|
|
#endif
|