/* $Id: obsdrdr.c.html,v 1.1 2006/05/31 21:21:41 nanard Exp $ */
/* miniupnp project : http://miniupnp.free.fr/
*
* Copyright (c) 2006 Thomas Bernard
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/pfvar.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <syslog.h>
#include <stdio.h>
#include <stdlib.h>
#include "obsdrdr.h"
static const char anchor_name[] = "miniupnpd";
void
add_redirect_rule(const char * ifname, unsigned short eport,
const char * iaddr, unsigned short iport)
{
int dev;
struct pfioc_trans io;
struct pfioc_trans_e ioe;
struct pfioc_pooladdr pp;
struct pfioc_rule pr;
struct pf_pooladdr *a;
dev = open("/dev/pf", O_RDWR);
if(dev<0)
{
syslog(LOG_ERR, "open(\"/dev/pf\"): %m");
}
else
{
/*
* DIOCXBEGIN
* DIOCBEGINADDRS
* DIOCADDADDR ???
* DIOCADDRULE
* DIOCXCOMMIT
rdr on $ext_if proto tcp from any to any port 2022 -> 192.168.0.55 port 22
rdr on ep0 inet proto tcp from any to any port = 2022 -> 192.168.0.55 port 22
*/
memset(&ioe, 0, sizeof(ioe));
io.size = 1;
io.esize = sizeof(ioe);
io.array = &ioe;
ioe.rs_num = PF_RULESET_RDR;
strlcpy(ioe.anchor, anchor_name, MAXPATHLEN);
if(ioctl(dev, DIOCXBEGIN, &io) < 0)
syslog(LOG_ERR, "ioctl(dev, DIOCXBEGIN, ...): %m");
memset(&pp, 0, sizeof(pp));
strlcpy(pp.anchor, anchor_name, MAXPATHLEN);
if(ioctl(dev, DIOCBEGINADDRS, &pp) < 0)
syslog(LOG_ERR, "ioctl(dev, DIOCBEGINADDRS, ...): %m");
memset(&pr, 0, sizeof(pr));
pr.ticket = ioe.ticket;
pr.pool_ticket = pp.ticket;
pr.rule.dst.port_op = PF_OP_EQ;
pr.rule.dst.port[0] = htons(eport);
pr.rule.dst.port[1] = htons(eport);
pr.rule.action = PF_RDR;
pr.rule.af = AF_INET;
strlcpy(pr.rule.ifname, ifname, IFNAMSIZ);
pr.rule.proto = IPPROTO_TCP;
pr.rule.rpool.proxy_port[0] = iport;
pr.rule.rpool.proxy_port[1] = iport;
TAILQ_INIT(&pr.rule.rpool.list);
a = calloc(1, sizeof(struct pf_pooladdr));
inet_pton(AF_INET, iaddr, &a->addr.v.a.addr.v4.s_addr);
a->addr.v.a.mask.v4.s_addr = htonl(INADDR_NONE);
TAILQ_INSERT_TAIL(&pr.rule.rpool.list, a, entries);
strlcpy(pp.anchor, anchor_name, MAXPATHLEN);
memcpy(&pp.addr, a, sizeof(struct pf_pooladdr));
if(ioctl(dev, DIOCADDADDR, &pp) < 0)
syslog(LOG_ERR, "ioctl(dev, DIOCADDADDR, ...): %m");
strlcpy(pr.anchor, anchor_name, MAXPATHLEN);
if(ioctl(dev, DIOCADDRULE, &pr) < 0)
syslog(LOG_ERR, "ioctl(dev, DIOCADDRULE, ...): %m");
else
{
if(ioctl(dev, DIOCXCOMMIT, &io) < 0)
syslog(LOG_ERR, "ioctl(dev, DIOCXCOMMIT, ...): %m");
}
free(a);
close(dev);
}
}
int
add_redirect_rule2(const char * ifname, unsigned short eport,
const char * iaddr, unsigned short iport, int proto)
{
int dev;
int r;
struct pfioc_rule pcr;
struct pfioc_pooladdr pp;
struct pf_pooladdr *a;
dev = open("/dev/pf", O_RDWR);
if(dev<0)
{
syslog(LOG_ERR, "open(\"/dev/pf\"): %m");
return -1;
}
r = 0;
memset(&pcr, 0, sizeof(pcr));
strlcpy(pcr.anchor, anchor_name, MAXPATHLEN);
memset(&pp, 0, sizeof(pp));
strlcpy(pp.anchor, anchor_name, MAXPATHLEN);
if(ioctl(dev, DIOCBEGINADDRS, &pp) < 0)
{
syslog(LOG_ERR, "ioctl(dev, DIOCBEGINADDRS, ...): %m");
r = -1;
}
else
{
pcr.pool_ticket = pp.ticket;
pcr.rule.dst.port_op = PF_OP_EQ;
pcr.rule.dst.port[0] = htons(eport);
pcr.rule.dst.port[1] = htons(eport);
pcr.rule.action = PF_RDR;
pcr.rule.af = AF_INET;
strlcpy(pcr.rule.ifname, ifname, IFNAMSIZ);
pcr.rule.proto = proto;
pcr.rule.rpool.proxy_port[0] = iport;
pcr.rule.rpool.proxy_port[1] = iport;
TAILQ_INIT(&pcr.rule.rpool.list);
a = calloc(1, sizeof(struct pf_pooladdr));
inet_pton(AF_INET, iaddr, &a->addr.v.a.addr.v4.s_addr);
a->addr.v.a.mask.v4.s_addr = htonl(INADDR_NONE);
TAILQ_INSERT_TAIL(&pcr.rule.rpool.list, a, entries);
memcpy(&pp.addr, a, sizeof(struct pf_pooladdr));
if(ioctl(dev, DIOCADDADDR, &pp) < 0)
{
syslog(LOG_ERR, "ioctl(dev, DIOCADDADDR, ...): %m");
r = -1;
}
else
{
pcr.action = PF_CHANGE_GET_TICKET;
if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0)
{
syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m");
r = -1;
}
else
{
pcr.action = PF_CHANGE_ADD_TAIL;
if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0)
{
syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_ADD_TAIL: %m");
r = -1;
}
}
}
free(a);
}
close(dev);
return r;
}
/* get_redirect_rule()
* return value : 0 success (found)
* -1 = error */
int
get_redirect_rule(const char * ifname, unsigned short eport, int proto,
char * iaddr, int iaddrlen, unsigned short * iport)
{
int dev;
int i, n;
struct pfioc_rule pr;
struct pfioc_pooladdr pp;
dev = open("/dev/pf", O_RDWR);
if(dev<0)
{
syslog(LOG_ERR, "open(\"/dev/pf\"): %m");
return -1;
}
memset(&pr, 0, sizeof(pr));
strlcpy(pr.anchor, anchor_name, MAXPATHLEN);
pr.rule.action = PF_RDR;
if(ioctl(dev, DIOCGETRULES, &pr) < 0)
{
syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m");
goto error;
}
n = pr.nr;
for(i=0; i<n; i++)
{
pr.nr = i;
if(ioctl(dev, DIOCGETRULE, &pr) < 0)
{
syslog(LOG_ERR, "ioctl(dev, DIOCGETRULE): %m");
goto error;
}
if( (eport == ntohs(pr.rule.dst.port[0]))
&& (eport == ntohs(pr.rule.dst.port[1]))
&& (pr.rule.proto == proto) )
{
*iport = pr.rule.rpool.proxy_port[0];
memset(&pp, 0, sizeof(pp));
strlcpy(pp.anchor, anchor_name, MAXPATHLEN);
pp.r_action = PF_RDR;
pp.r_num = i;
pp.ticket = pr.ticket;
if(ioctl(dev, DIOCGETADDRS, &pp) < 0)
{
syslog(LOG_ERR, "ioctl(dev, DIOCGETADDRS, ...): %m");
goto error;
}
if(pp.nr != 1)
{
syslog(LOG_NOTICE, "no address associated with pf rule!");
goto error;
}
pp.nr = 0; // 1er
if(ioctl(dev, DIOCGETADDR, &pp) < 0)
{
syslog(LOG_ERR, "ioctl(dev, DIOCGETADDR, ...): %m");
goto error;
}
inet_ntop(AF_INET, &pp.addr.addr.v.a.addr.v4.s_addr,
iaddr, iaddrlen);
close(dev);
return 0;
}
}
error:
close(dev);
return -1;
}
int
delete_redirect_rule(const char * ifname, unsigned short eport, int proto)
{
int dev;
int i, n;
struct pfioc_rule pr;
dev = open("/dev/pf", O_RDWR);
if(dev<0)
{
syslog(LOG_ERR, "open(\"/dev/pf\"): %m");
return -1;
}
memset(&pr, 0, sizeof(pr));
strlcpy(pr.anchor, anchor_name, MAXPATHLEN);
pr.rule.action = PF_RDR;
if(ioctl(dev, DIOCGETRULES, &pr) < 0)
{
syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m");
goto error;
}
n = pr.nr;
for(i=0; i<n; i++)
{
pr.nr = i;
if(ioctl(dev, DIOCGETRULE, &pr) < 0)
{
syslog(LOG_ERR, "ioctl(dev, DIOCGETRULE): %m");
goto error;
}
if( (eport == ntohs(pr.rule.dst.port[0]))
&& (eport == ntohs(pr.rule.dst.port[1]))
&& (pr.rule.proto == proto) )
{
pr.action = PF_CHANGE_GET_TICKET;
if(ioctl(dev, DIOCCHANGERULE, &pr) < 0)
{
syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m");
goto error;
}
pr.action = PF_CHANGE_REMOVE;
pr.nr = i;
if(ioctl(dev, DIOCCHANGERULE, &pr) < 0)
{
syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_REMOVE: %m");
goto error;
}
close(dev);
return 0;
}
}
error:
close(dev);
return -1;
}
/* this function is only for testing */
void
list_rules(void)
{
char buf[32];
int dev;
int i, n;
struct pfioc_rule pr;
struct pfioc_pooladdr pp;
dev = open("/dev/pf", O_RDWR);
if(dev<0)
{
perror("open(\"/dev/pf\")");
syslog(LOG_ERR, "open(\"/dev/pf\"): %m");
return ;//-1;
}
memset(&pr, 0, sizeof(pr));
strlcpy(pr.anchor, anchor_name, MAXPATHLEN);
pr.rule.action = PF_RDR;
if(ioctl(dev, DIOCGETRULES, &pr) < 0)
perror("DIOCGETRULES");
printf("ticket = %d, nr = %d\n", pr.ticket, pr.nr);
n = pr.nr;
for(i=0; i<n; i++)
{
printf("-- rule %d --\n", i);
pr.nr = i;
if(ioctl(dev, DIOCGETRULE, &pr) < 0)
perror("DIOCGETRULE");
printf(" %d:%d -> %d:%d proto %d\n",
(int)ntohs(pr.rule.dst.port[0]),
(int)ntohs(pr.rule.dst.port[1]),
(int)pr.rule.rpool.proxy_port[0],
(int)pr.rule.rpool.proxy_port[1],
(int)pr.rule.proto);
memset(&pp, 0, sizeof(pp));
strlcpy(pp.anchor, anchor_name, MAXPATHLEN);
pp.r_action = PF_RDR;
pp.r_num = i;
pp.ticket = pr.ticket;
if(ioctl(dev, DIOCGETADDRS, &pp) < 0)
perror("DIOCGETADDRS");
printf(" nb pool addr = %d ticket=%d\n", pp.nr, pp.ticket);
//if(ioctl(dev, DIOCGETRULE, &pr) < 0)
// perror("DIOCGETRULE");
pp.nr = 0; // 1er
if(ioctl(dev, DIOCGETADDR, &pp) < 0)
perror("DIOCGETADDR");
// addr.v.a.addr.v4.s_addr
printf(" %s\n", inet_ntop(AF_INET, &pp.addr.addr.v.a.addr.v4.s_addr, buf, 32));
}
close(dev);
}