summaryrefslogtreecommitdiffstats
path: root/sbin/natd/natd.c
diff options
context:
space:
mode:
Diffstat (limited to 'sbin/natd/natd.c')
-rw-r--r--sbin/natd/natd.c1689
1 files changed, 1689 insertions, 0 deletions
diff --git a/sbin/natd/natd.c b/sbin/natd/natd.c
new file mode 100644
index 0000000..fbb8aed
--- /dev/null
+++ b/sbin/natd/natd.c
@@ -0,0 +1,1689 @@
+/*
+ * natd - Network Address Translation Daemon for FreeBSD.
+ *
+ * This software is provided free of charge, with no
+ * warranty of any kind, either expressed or implied.
+ * Use at your own risk.
+ *
+ * You may copy, modify and distribute this software (natd.c) freely.
+ *
+ * Ari Suutari <suutari@iki.fi>
+ *
+ * $FreeBSD$
+ */
+
+#define SYSLOG_NAMES
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <machine/in_cksum.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+#include <netinet/ip_icmp.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/route.h>
+#include <arpa/inet.h>
+
+#include <alias.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "natd.h"
+
+/*
+ * Default values for input and output
+ * divert socket ports.
+ */
+
+#define DEFAULT_SERVICE "natd"
+
+/*
+ * Definition of a port range, and macros to deal with values.
+ * FORMAT: HI 16-bits == first port in range, 0 == all ports.
+ * LO 16-bits == number of ports in range
+ * NOTES: - Port values are not stored in network byte order.
+ */
+
+typedef u_long port_range;
+
+#define GETLOPORT(x) ((x) >> 0x10)
+#define GETNUMPORTS(x) ((x) & 0x0000ffff)
+#define GETHIPORT(x) (GETLOPORT((x)) + GETNUMPORTS((x)))
+
+/* Set y to be the low-port value in port_range variable x. */
+#define SETLOPORT(x,y) ((x) = ((x) & 0x0000ffff) | ((y) << 0x10))
+
+/* Set y to be the number of ports in port_range variable x. */
+#define SETNUMPORTS(x,y) ((x) = ((x) & 0xffff0000) | (y))
+
+/*
+ * Function prototypes.
+ */
+
+static void DoAliasing (int fd, int direction);
+static void DaemonMode (void);
+static void HandleRoutingInfo (int fd);
+static void Usage (void);
+static char* FormatPacket (struct ip*);
+static void PrintPacket (struct ip*);
+static void SyslogPacket (struct ip*, int priority, const char *label);
+static void SetAliasAddressFromIfName (const char *ifName);
+static void InitiateShutdown (int);
+static void Shutdown (int);
+static void RefreshAddr (int);
+static void ParseOption (const char* option, const char* parms);
+static void ReadConfigFile (const char* fileName);
+static void SetupPortRedirect (const char* parms);
+static void SetupProtoRedirect(const char* parms);
+static void SetupAddressRedirect (const char* parms);
+static void StrToAddr (const char* str, struct in_addr* addr);
+static u_short StrToPort (const char* str, const char* proto);
+static int StrToPortRange (const char* str, const char* proto, port_range *portRange);
+static int StrToProto (const char* str);
+static int StrToAddrAndPortRange (const char* str, struct in_addr* addr, char* proto, port_range *portRange);
+static void ParseArgs (int argc, char** argv);
+static void SetupPunchFW(const char *strValue);
+
+/*
+ * Globals.
+ */
+
+static int verbose;
+static int background;
+static int running;
+static int assignAliasAddr;
+static char* ifName;
+static int ifIndex;
+static u_short inPort;
+static u_short outPort;
+static u_short inOutPort;
+static struct in_addr aliasAddr;
+static int dynamicMode;
+static int ifMTU;
+static int aliasOverhead;
+static int icmpSock;
+static int dropIgnoredIncoming;
+static int logDropped;
+static int logFacility;
+static int logIpfwDenied;
+
+int main (int argc, char** argv)
+{
+ int divertIn;
+ int divertOut;
+ int divertInOut;
+ int routeSock;
+ struct sockaddr_in addr;
+ fd_set readMask;
+ int fdMax;
+/*
+ * Initialize packet aliasing software.
+ * Done already here to be able to alter option bits
+ * during command line and configuration file processing.
+ */
+ PacketAliasInit ();
+/*
+ * Parse options.
+ */
+ inPort = 0;
+ outPort = 0;
+ verbose = 0;
+ inOutPort = 0;
+ ifName = NULL;
+ ifMTU = -1;
+ background = 0;
+ running = 1;
+ assignAliasAddr = 0;
+ aliasAddr.s_addr = INADDR_NONE;
+ aliasOverhead = 12;
+ dynamicMode = 0;
+ logDropped = 0;
+ logFacility = LOG_DAEMON;
+ logIpfwDenied = -1;
+
+ ParseArgs (argc, argv);
+/*
+ * Log ipfw(8) denied packets by default in verbose mode.
+ */
+ if (logIpfwDenied == -1)
+ logIpfwDenied = verbose;
+/*
+ * Open syslog channel.
+ */
+ openlog ("natd", LOG_CONS | LOG_PID | (verbose ? LOG_PERROR : 0),
+ logFacility);
+/*
+ * Check that valid aliasing address has been given.
+ */
+ if (aliasAddr.s_addr == INADDR_NONE && ifName == NULL)
+ errx (1, "aliasing address not given");
+
+ if (aliasAddr.s_addr != INADDR_NONE && ifName != NULL)
+ errx (1, "both alias address and interface "
+ "name are not allowed");
+/*
+ * Check that valid port number is known.
+ */
+ if (inPort != 0 || outPort != 0)
+ if (inPort == 0 || outPort == 0)
+ errx (1, "both input and output ports are required");
+
+ if (inPort == 0 && outPort == 0 && inOutPort == 0)
+ ParseOption ("port", DEFAULT_SERVICE);
+
+/*
+ * Check if ignored packets should be dropped.
+ */
+ dropIgnoredIncoming = PacketAliasSetMode (0, 0);
+ dropIgnoredIncoming &= PKT_ALIAS_DENY_INCOMING;
+/*
+ * Create divert sockets. Use only one socket if -p was specified
+ * on command line. Otherwise, create separate sockets for
+ * outgoing and incoming connnections.
+ */
+ if (inOutPort) {
+
+ divertInOut = socket (PF_INET, SOCK_RAW, IPPROTO_DIVERT);
+ if (divertInOut == -1)
+ Quit ("Unable to create divert socket.");
+
+ divertIn = -1;
+ divertOut = -1;
+/*
+ * Bind socket.
+ */
+
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = INADDR_ANY;
+ addr.sin_port = inOutPort;
+
+ if (bind (divertInOut,
+ (struct sockaddr*) &addr,
+ sizeof addr) == -1)
+ Quit ("Unable to bind divert socket.");
+ }
+ else {
+
+ divertIn = socket (PF_INET, SOCK_RAW, IPPROTO_DIVERT);
+ if (divertIn == -1)
+ Quit ("Unable to create incoming divert socket.");
+
+ divertOut = socket (PF_INET, SOCK_RAW, IPPROTO_DIVERT);
+ if (divertOut == -1)
+ Quit ("Unable to create outgoing divert socket.");
+
+ divertInOut = -1;
+
+/*
+ * Bind divert sockets.
+ */
+
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = INADDR_ANY;
+ addr.sin_port = inPort;
+
+ if (bind (divertIn,
+ (struct sockaddr*) &addr,
+ sizeof addr) == -1)
+ Quit ("Unable to bind incoming divert socket.");
+
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = INADDR_ANY;
+ addr.sin_port = outPort;
+
+ if (bind (divertOut,
+ (struct sockaddr*) &addr,
+ sizeof addr) == -1)
+ Quit ("Unable to bind outgoing divert socket.");
+ }
+/*
+ * Create routing socket if interface name specified and in dynamic mode.
+ */
+ routeSock = -1;
+ if (ifName) {
+ if (dynamicMode) {
+
+ routeSock = socket (PF_ROUTE, SOCK_RAW, 0);
+ if (routeSock == -1)
+ Quit ("Unable to create routing info socket.");
+
+ assignAliasAddr = 1;
+ }
+ else
+ SetAliasAddressFromIfName (ifName);
+ }
+/*
+ * Create socket for sending ICMP messages.
+ */
+ icmpSock = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP);
+ if (icmpSock == -1)
+ Quit ("Unable to create ICMP socket.");
+
+/*
+ * And disable reads for the socket, otherwise it slowly fills
+ * up with received icmps which we do not use.
+ */
+ shutdown(icmpSock, SHUT_RD);
+
+/*
+ * Become a daemon unless verbose mode was requested.
+ */
+ if (!verbose)
+ DaemonMode ();
+/*
+ * Catch signals to manage shutdown and
+ * refresh of interface address.
+ */
+ siginterrupt(SIGTERM, 1);
+ siginterrupt(SIGHUP, 1);
+ signal (SIGTERM, InitiateShutdown);
+ signal (SIGHUP, RefreshAddr);
+/*
+ * Set alias address if it has been given.
+ */
+ if (aliasAddr.s_addr != INADDR_NONE)
+ PacketAliasSetAddress (aliasAddr);
+/*
+ * We need largest descriptor number for select.
+ */
+
+ fdMax = -1;
+
+ if (divertIn > fdMax)
+ fdMax = divertIn;
+
+ if (divertOut > fdMax)
+ fdMax = divertOut;
+
+ if (divertInOut > fdMax)
+ fdMax = divertInOut;
+
+ if (routeSock > fdMax)
+ fdMax = routeSock;
+
+ while (running) {
+
+ if (divertInOut != -1 && !ifName) {
+/*
+ * When using only one socket, just call
+ * DoAliasing repeatedly to process packets.
+ */
+ DoAliasing (divertInOut, DONT_KNOW);
+ continue;
+ }
+/*
+ * Build read mask from socket descriptors to select.
+ */
+ FD_ZERO (&readMask);
+/*
+ * Check if new packets are available.
+ */
+ if (divertIn != -1)
+ FD_SET (divertIn, &readMask);
+
+ if (divertOut != -1)
+ FD_SET (divertOut, &readMask);
+
+ if (divertInOut != -1)
+ FD_SET (divertInOut, &readMask);
+/*
+ * Routing info is processed always.
+ */
+ if (routeSock != -1)
+ FD_SET (routeSock, &readMask);
+
+ if (select (fdMax + 1,
+ &readMask,
+ NULL,
+ NULL,
+ NULL) == -1) {
+
+ if (errno == EINTR)
+ continue;
+
+ Quit ("Select failed.");
+ }
+
+ if (divertIn != -1)
+ if (FD_ISSET (divertIn, &readMask))
+ DoAliasing (divertIn, INPUT);
+
+ if (divertOut != -1)
+ if (FD_ISSET (divertOut, &readMask))
+ DoAliasing (divertOut, OUTPUT);
+
+ if (divertInOut != -1)
+ if (FD_ISSET (divertInOut, &readMask))
+ DoAliasing (divertInOut, DONT_KNOW);
+
+ if (routeSock != -1)
+ if (FD_ISSET (routeSock, &readMask))
+ HandleRoutingInfo (routeSock);
+ }
+
+ if (background)
+ unlink (PIDFILE);
+
+ return 0;
+}
+
+static void DaemonMode ()
+{
+ FILE* pidFile;
+
+ daemon (0, 0);
+ background = 1;
+
+ pidFile = fopen (PIDFILE, "w");
+ if (pidFile) {
+
+ fprintf (pidFile, "%d\n", getpid ());
+ fclose (pidFile);
+ }
+}
+
+static void ParseArgs (int argc, char** argv)
+{
+ int arg;
+ char* opt;
+ char parmBuf[256];
+ int len; /* bounds checking */
+
+ for (arg = 1; arg < argc; arg++) {
+
+ opt = argv[arg];
+ if (*opt != '-') {
+
+ warnx ("invalid option %s", opt);
+ Usage ();
+ }
+
+ parmBuf[0] = '\0';
+ len = 0;
+
+ while (arg < argc - 1) {
+
+ if (argv[arg + 1][0] == '-')
+ break;
+
+ if (len) {
+ strncat (parmBuf, " ", sizeof(parmBuf) - (len + 1));
+ len += strlen(parmBuf + len);
+ }
+
+ ++arg;
+ strncat (parmBuf, argv[arg], sizeof(parmBuf) - (len + 1));
+ len += strlen(parmBuf + len);
+
+ }
+
+ ParseOption (opt + 1, (len ? parmBuf : NULL));
+
+ }
+}
+
+static void DoAliasing (int fd, int direction)
+{
+ int bytes;
+ int origBytes;
+ char buf[IP_MAXPACKET];
+ struct sockaddr_in addr;
+ int wrote;
+ int status;
+ int addrSize;
+ struct ip* ip;
+ char msgBuf[80];
+
+ if (assignAliasAddr) {
+
+ SetAliasAddressFromIfName (ifName);
+ assignAliasAddr = 0;
+ }
+/*
+ * Get packet from socket.
+ */
+ addrSize = sizeof addr;
+ origBytes = recvfrom (fd,
+ buf,
+ sizeof buf,
+ 0,
+ (struct sockaddr*) &addr,
+ &addrSize);
+
+ if (origBytes == -1) {
+
+ if (errno != EINTR)
+ Warn ("read from divert socket failed");
+
+ return;
+ }
+/*
+ * This is a IP packet.
+ */
+ ip = (struct ip*) buf;
+ if (direction == DONT_KNOW) {
+ if (addr.sin_addr.s_addr == INADDR_ANY)
+ direction = OUTPUT;
+ else
+ direction = INPUT;
+ }
+
+ if (verbose) {
+/*
+ * Print packet direction and protocol type.
+ */
+ printf (direction == OUTPUT ? "Out " : "In ");
+
+ switch (ip->ip_p) {
+ case IPPROTO_TCP:
+ printf ("[TCP] ");
+ break;
+
+ case IPPROTO_UDP:
+ printf ("[UDP] ");
+ break;
+
+ case IPPROTO_ICMP:
+ printf ("[ICMP] ");
+ break;
+
+ default:
+ printf ("[%d] ", ip->ip_p);
+ break;
+ }
+/*
+ * Print addresses.
+ */
+ PrintPacket (ip);
+ }
+
+ if (direction == OUTPUT) {
+/*
+ * Outgoing packets. Do aliasing.
+ */
+ PacketAliasOut (buf, IP_MAXPACKET);
+ }
+ else {
+
+/*
+ * Do aliasing.
+ */
+ status = PacketAliasIn (buf, IP_MAXPACKET);
+ if (status == PKT_ALIAS_IGNORED &&
+ dropIgnoredIncoming) {
+
+ if (verbose)
+ printf (" dropped.\n");
+
+ if (logDropped)
+ SyslogPacket (ip, LOG_WARNING, "denied");
+
+ return;
+ }
+ }
+/*
+ * Length might have changed during aliasing.
+ */
+ bytes = ntohs (ip->ip_len);
+/*
+ * Update alias overhead size for outgoing packets.
+ */
+ if (direction == OUTPUT &&
+ bytes - origBytes > aliasOverhead)
+ aliasOverhead = bytes - origBytes;
+
+ if (verbose) {
+
+/*
+ * Print addresses after aliasing.
+ */
+ printf (" aliased to\n");
+ printf (" ");
+ PrintPacket (ip);
+ printf ("\n");
+ }
+
+/*
+ * Put packet back for processing.
+ */
+ wrote = sendto (fd,
+ buf,
+ bytes,
+ 0,
+ (struct sockaddr*) &addr,
+ sizeof addr);
+
+ if (wrote != bytes) {
+
+ if (errno == EMSGSIZE) {
+
+ if (direction == OUTPUT &&
+ ifMTU != -1)
+ SendNeedFragIcmp (icmpSock,
+ (struct ip*) buf,
+ ifMTU - aliasOverhead);
+ }
+ else if (errno == EACCES && logIpfwDenied) {
+
+ sprintf (msgBuf, "failed to write packet back");
+ Warn (msgBuf);
+ }
+ }
+}
+
+static void HandleRoutingInfo (int fd)
+{
+ int bytes;
+ struct if_msghdr ifMsg;
+/*
+ * Get packet from socket.
+ */
+ bytes = read (fd, &ifMsg, sizeof ifMsg);
+ if (bytes == -1) {
+
+ Warn ("read from routing socket failed");
+ return;
+ }
+
+ if (ifMsg.ifm_version != RTM_VERSION) {
+
+ Warn ("unexpected packet read from routing socket");
+ return;
+ }
+
+ if (verbose)
+ printf ("Routing message %#x received.\n", ifMsg.ifm_type);
+
+ if ((ifMsg.ifm_type == RTM_NEWADDR || ifMsg.ifm_type == RTM_IFINFO) &&
+ ifMsg.ifm_index == ifIndex) {
+ if (verbose)
+ printf("Interface address/MTU has probably changed.\n");
+ assignAliasAddr = 1;
+ }
+}
+
+static void PrintPacket (struct ip* ip)
+{
+ printf ("%s", FormatPacket (ip));
+}
+
+static void SyslogPacket (struct ip* ip, int priority, const char *label)
+{
+ syslog (priority, "%s %s", label, FormatPacket (ip));
+}
+
+static char* FormatPacket (struct ip* ip)
+{
+ static char buf[256];
+ struct tcphdr* tcphdr;
+ struct udphdr* udphdr;
+ struct icmp* icmphdr;
+ char src[20];
+ char dst[20];
+
+ strcpy (src, inet_ntoa (ip->ip_src));
+ strcpy (dst, inet_ntoa (ip->ip_dst));
+
+ switch (ip->ip_p) {
+ case IPPROTO_TCP:
+ tcphdr = (struct tcphdr*) ((char*) ip + (ip->ip_hl << 2));
+ sprintf (buf, "[TCP] %s:%d -> %s:%d",
+ src,
+ ntohs (tcphdr->th_sport),
+ dst,
+ ntohs (tcphdr->th_dport));
+ break;
+
+ case IPPROTO_UDP:
+ udphdr = (struct udphdr*) ((char*) ip + (ip->ip_hl << 2));
+ sprintf (buf, "[UDP] %s:%d -> %s:%d",
+ src,
+ ntohs (udphdr->uh_sport),
+ dst,
+ ntohs (udphdr->uh_dport));
+ break;
+
+ case IPPROTO_ICMP:
+ icmphdr = (struct icmp*) ((char*) ip + (ip->ip_hl << 2));
+ sprintf (buf, "[ICMP] %s -> %s %u(%u)",
+ src,
+ dst,
+ icmphdr->icmp_type,
+ icmphdr->icmp_code);
+ break;
+
+ default:
+ sprintf (buf, "[%d] %s -> %s ", ip->ip_p, src, dst);
+ break;
+ }
+
+ return buf;
+}
+
+static void
+SetAliasAddressFromIfName(const char *ifn)
+{
+ size_t needed;
+ int mib[6];
+ char *buf, *lim, *next;
+ struct if_msghdr *ifm;
+ struct ifa_msghdr *ifam;
+ struct sockaddr_dl *sdl;
+ struct sockaddr_in *sin;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = AF_INET; /* Only IP addresses please */
+ mib[4] = NET_RT_IFLIST;
+ mib[5] = 0; /* ifIndex??? */
+/*
+ * Get interface data.
+ */
+ if (sysctl(mib, 6, NULL, &needed, NULL, 0) == -1)
+ err(1, "iflist-sysctl-estimate");
+ if ((buf = malloc(needed)) == NULL)
+ errx(1, "malloc failed");
+ if (sysctl(mib, 6, buf, &needed, NULL, 0) == -1)
+ err(1, "iflist-sysctl-get");
+ lim = buf + needed;
+/*
+ * Loop through interfaces until one with
+ * given name is found. This is done to
+ * find correct interface index for routing
+ * message processing.
+ */
+ ifIndex = 0;
+ next = buf;
+ while (next < lim) {
+ ifm = (struct if_msghdr *)next;
+ next += ifm->ifm_msglen;
+ if (ifm->ifm_version != RTM_VERSION) {
+ if (verbose)
+ warnx("routing message version %d "
+ "not understood", ifm->ifm_version);
+ continue;
+ }
+ if (ifm->ifm_type == RTM_IFINFO) {
+ sdl = (struct sockaddr_dl *)(ifm + 1);
+ if (strlen(ifn) == sdl->sdl_nlen &&
+ strncmp(ifn, sdl->sdl_data, sdl->sdl_nlen) == 0) {
+ ifIndex = ifm->ifm_index;
+ ifMTU = ifm->ifm_data.ifi_mtu;
+ break;
+ }
+ }
+ }
+ if (!ifIndex)
+ errx(1, "unknown interface name %s", ifn);
+/*
+ * Get interface address.
+ */
+ sin = NULL;
+ while (next < lim) {
+ ifam = (struct ifa_msghdr *)next;
+ next += ifam->ifam_msglen;
+ if (ifam->ifam_version != RTM_VERSION) {
+ if (verbose)
+ warnx("routing message version %d "
+ "not understood", ifam->ifam_version);
+ continue;
+ }
+ if (ifam->ifam_type != RTM_NEWADDR)
+ break;
+ if (ifam->ifam_addrs & RTA_IFA) {
+ int i;
+ char *cp = (char *)(ifam + 1);
+
+#define ROUNDUP(a) \
+ ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
+#define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))
+
+ for (i = 1; i < RTA_IFA; i <<= 1)
+ if (ifam->ifam_addrs & i)
+ ADVANCE(cp, (struct sockaddr *)cp);
+ if (((struct sockaddr *)cp)->sa_family == AF_INET) {
+ sin = (struct sockaddr_in *)cp;
+ break;
+ }
+ }
+ }
+ if (sin == NULL)
+ errx(1, "%s: cannot get interface address", ifn);
+
+ PacketAliasSetAddress(sin->sin_addr);
+ syslog(LOG_INFO, "Aliasing to %s, mtu %d bytes",
+ inet_ntoa(sin->sin_addr), ifMTU);
+
+ free(buf);
+}
+
+void Quit (const char* msg)
+{
+ Warn (msg);
+ exit (1);
+}
+
+void Warn (const char* msg)
+{
+ if (background)
+ syslog (LOG_ALERT, "%s (%m)", msg);
+ else
+ warn ("%s", msg);
+}
+
+static void RefreshAddr (int sig)
+{
+ if (ifName)
+ assignAliasAddr = 1;
+}
+
+static void InitiateShutdown (int sig)
+{
+/*
+ * Start timer to allow kernel gracefully
+ * shutdown existing connections when system
+ * is shut down.
+ */
+ siginterrupt(SIGALRM, 1);
+ signal (SIGALRM, Shutdown);
+ alarm (10);
+}
+
+static void Shutdown (int sig)
+{
+ running = 0;
+}
+
+/*
+ * Different options recognized by this program.
+ */
+
+enum Option {
+
+ PacketAliasOption,
+ Verbose,
+ InPort,
+ OutPort,
+ Port,
+ AliasAddress,
+ TargetAddress,
+ InterfaceName,
+ RedirectPort,
+ RedirectProto,
+ RedirectAddress,
+ ConfigFile,
+ DynamicMode,
+ ProxyRule,
+ LogDenied,
+ LogFacility,
+ PunchFW,
+ LogIpfwDenied
+};
+
+enum Param {
+
+ YesNo,
+ Numeric,
+ String,
+ None,
+ Address,
+ Service
+};
+
+/*
+ * Option information structure (used by ParseOption).
+ */
+
+struct OptionInfo {
+
+ enum Option type;
+ int packetAliasOpt;
+ enum Param parm;
+ const char* parmDescription;
+ const char* description;
+ const char* name;
+ const char* shortName;
+};
+
+/*
+ * Table of known options.
+ */
+
+static struct OptionInfo optionTable[] = {
+
+ { PacketAliasOption,
+ PKT_ALIAS_UNREGISTERED_ONLY,
+ YesNo,
+ "[yes|no]",
+ "alias only unregistered addresses",
+ "unregistered_only",
+ "u" },
+
+ { PacketAliasOption,
+ PKT_ALIAS_LOG,
+ YesNo,
+ "[yes|no]",
+ "enable logging",
+ "log",
+ "l" },
+
+ { PacketAliasOption,
+ PKT_ALIAS_PROXY_ONLY,
+ YesNo,
+ "[yes|no]",
+ "proxy only",
+ "proxy_only",
+ NULL },
+
+ { PacketAliasOption,
+ PKT_ALIAS_REVERSE,
+ YesNo,
+ "[yes|no]",
+ "operate in reverse mode",
+ "reverse",
+ NULL },
+
+ { PacketAliasOption,
+ PKT_ALIAS_DENY_INCOMING,
+ YesNo,
+ "[yes|no]",
+ "allow incoming connections",
+ "deny_incoming",
+ "d" },
+
+ { PacketAliasOption,
+ PKT_ALIAS_USE_SOCKETS,
+ YesNo,
+ "[yes|no]",
+ "use sockets to inhibit port conflict",
+ "use_sockets",
+ "s" },
+
+ { PacketAliasOption,
+ PKT_ALIAS_SAME_PORTS,
+ YesNo,
+ "[yes|no]",
+ "try to keep original port numbers for connections",
+ "same_ports",
+ "m" },
+
+ { Verbose,
+ 0,
+ YesNo,
+ "[yes|no]",
+ "verbose mode, dump packet information",
+ "verbose",
+ "v" },
+
+ { DynamicMode,
+ 0,
+ YesNo,
+ "[yes|no]",
+ "dynamic mode, automatically detect interface address changes",
+ "dynamic",
+ NULL },
+
+ { InPort,
+ 0,
+ Service,
+ "number|service_name",
+ "set port for incoming packets",
+ "in_port",
+ "i" },
+
+ { OutPort,
+ 0,
+ Service,
+ "number|service_name",
+ "set port for outgoing packets",
+ "out_port",
+ "o" },
+
+ { Port,
+ 0,
+ Service,
+ "number|service_name",
+ "set port (defaults to natd/divert)",
+ "port",
+ "p" },
+
+ { AliasAddress,
+ 0,
+ Address,
+ "x.x.x.x",
+ "address to use for aliasing",
+ "alias_address",
+ "a" },
+
+ { TargetAddress,
+ 0,
+ Address,
+ "x.x.x.x",
+ "address to use for incoming sessions",
+ "target_address",
+ "t" },
+
+ { InterfaceName,
+ 0,
+ String,
+ "network_if_name",
+ "take aliasing address from interface",
+ "interface",
+ "n" },
+
+ { ProxyRule,
+ 0,
+ String,
+ "[type encode_ip_hdr|encode_tcp_stream] port xxxx server "
+ "a.b.c.d:yyyy",
+ "add transparent proxying / destination NAT",
+ "proxy_rule",
+ NULL },
+
+ { RedirectPort,
+ 0,
+ String,
+ "tcp|udp local_addr:local_port_range[,...] [public_addr:]public_port_range"
+ " [remote_addr[:remote_port_range]]",
+ "redirect a port (or ports) for incoming traffic",
+ "redirect_port",
+ NULL },
+
+ { RedirectProto,
+ 0,
+ String,
+ "proto local_addr [public_addr] [remote_addr]",
+ "redirect packets of a given proto",
+ "redirect_proto",
+ NULL },
+
+ { RedirectAddress,
+ 0,
+ String,
+ "local_addr[,...] public_addr",
+ "define mapping between local and public addresses",
+ "redirect_address",
+ NULL },
+
+ { ConfigFile,
+ 0,
+ String,
+ "file_name",
+ "read options from configuration file",
+ "config",
+ "f" },
+
+ { LogDenied,
+ 0,
+ YesNo,
+ "[yes|no]",
+ "enable logging of denied incoming packets",
+ "log_denied",
+ NULL },
+
+ { LogFacility,
+ 0,
+ String,
+ "facility",
+ "name of syslog facility to use for logging",
+ "log_facility",
+ NULL },
+
+ { PunchFW,
+ 0,
+ String,
+ "basenumber:count",
+ "punch holes in the firewall for incoming FTP/IRC DCC connections",
+ "punch_fw",
+ NULL },
+
+ { LogIpfwDenied,
+ 0,
+ YesNo,
+ "[yes|no]",
+ "log packets converted by natd, but denied by ipfw",
+ "log_ipfw_denied",
+ NULL },
+};
+
+static void ParseOption (const char* option, const char* parms)
+{
+ int i;
+ struct OptionInfo* info;
+ int yesNoValue;
+ int aliasValue;
+ int numValue;
+ u_short uNumValue;
+ const char* strValue;
+ struct in_addr addrValue;
+ int max;
+ char* end;
+ CODE* fac_record = NULL;
+/*
+ * Find option from table.
+ */
+ max = sizeof (optionTable) / sizeof (struct OptionInfo);
+ for (i = 0, info = optionTable; i < max; i++, info++) {
+
+ if (!strcmp (info->name, option))
+ break;
+
+ if (info->shortName)
+ if (!strcmp (info->shortName, option))
+ break;
+ }
+
+ if (i >= max) {
+
+ warnx ("unknown option %s", option);
+ Usage ();
+ }
+
+ uNumValue = 0;
+ yesNoValue = 0;
+ numValue = 0;
+ strValue = NULL;
+/*
+ * Check parameters.
+ */
+ switch (info->parm) {
+ case YesNo:
+ if (!parms)
+ parms = "yes";
+
+ if (!strcmp (parms, "yes"))
+ yesNoValue = 1;
+ else
+ if (!strcmp (parms, "no"))
+ yesNoValue = 0;
+ else
+ errx (1, "%s needs yes/no parameter", option);
+ break;
+
+ case Service:
+ if (!parms)
+ errx (1, "%s needs service name or "
+ "port number parameter",
+ option);
+
+ uNumValue = StrToPort (parms, "divert");
+ break;
+
+ case Numeric:
+ if (parms)
+ numValue = strtol (parms, &end, 10);
+ else
+ end = NULL;
+
+ if (end == parms)
+ errx (1, "%s needs numeric parameter", option);
+ break;
+
+ case String:
+ strValue = parms;
+ if (!strValue)
+ errx (1, "%s needs parameter", option);
+ break;
+
+ case None:
+ if (parms)
+ errx (1, "%s does not take parameters", option);
+ break;
+
+ case Address:
+ if (!parms)
+ errx (1, "%s needs address/host parameter", option);
+
+ StrToAddr (parms, &addrValue);
+ break;
+ }
+
+ switch (info->type) {
+ case PacketAliasOption:
+
+ aliasValue = yesNoValue ? info->packetAliasOpt : 0;
+ PacketAliasSetMode (aliasValue, info->packetAliasOpt);
+ break;
+
+ case Verbose:
+ verbose = yesNoValue;
+ break;
+
+ case DynamicMode:
+ dynamicMode = yesNoValue;
+ break;
+
+ case InPort:
+ inPort = uNumValue;
+ break;
+
+ case OutPort:
+ outPort = uNumValue;
+ break;
+
+ case Port:
+ inOutPort = uNumValue;
+ break;
+
+ case AliasAddress:
+ memcpy (&aliasAddr, &addrValue, sizeof (struct in_addr));
+ break;
+
+ case TargetAddress:
+ PacketAliasSetTarget(addrValue);
+ break;
+
+ case RedirectPort:
+ SetupPortRedirect (strValue);
+ break;
+
+ case RedirectProto:
+ SetupProtoRedirect(strValue);
+ break;
+
+ case RedirectAddress:
+ SetupAddressRedirect (strValue);
+ break;
+
+ case ProxyRule:
+ PacketAliasProxyRule (strValue);
+ break;
+
+ case InterfaceName:
+ if (ifName)
+ free (ifName);
+
+ ifName = strdup (strValue);
+ break;
+
+ case ConfigFile:
+ ReadConfigFile (strValue);
+ break;
+
+ case LogDenied:
+ logDropped = yesNoValue;
+ break;
+
+ case LogFacility:
+
+ fac_record = facilitynames;
+ while (fac_record->c_name != NULL) {
+
+ if (!strcmp (fac_record->c_name, strValue)) {
+
+ logFacility = fac_record->c_val;
+ break;
+
+ }
+ else
+ fac_record++;
+ }
+
+ if(fac_record->c_name == NULL)
+ errx(1, "Unknown log facility name: %s", strValue);
+
+ break;
+
+ case PunchFW:
+ SetupPunchFW(strValue);
+ break;
+
+ case LogIpfwDenied:
+ logIpfwDenied = yesNoValue;;
+ break;
+ }
+}
+
+void ReadConfigFile (const char* fileName)
+{
+ FILE* file;
+ char *buf;
+ size_t len;
+ char *ptr, *p;
+ char* option;
+
+ file = fopen (fileName, "r");
+ if (!file)
+ err(1, "cannot open config file %s", fileName);
+
+ while ((buf = fgetln(file, &len)) != NULL) {
+ if (buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+ else
+ errx(1, "config file format error: "
+ "last line should end with newline");
+
+/*
+ * Check for comments, strip off trailing spaces.
+ */
+ if ((ptr = strchr(buf, '#')))
+ *ptr = '\0';
+ for (ptr = buf; isspace(*ptr); ++ptr)
+ continue;
+ if (*ptr == '\0')
+ continue;
+ for (p = strchr(buf, '\0'); isspace(*--p);)
+ continue;
+ *++p = '\0';
+
+/*
+ * Extract option name.
+ */
+ option = ptr;
+ while (*ptr && !isspace (*ptr))
+ ++ptr;
+
+ if (*ptr != '\0') {
+
+ *ptr = '\0';
+ ++ptr;
+ }
+/*
+ * Skip white space between name and parms.
+ */
+ while (*ptr && isspace (*ptr))
+ ++ptr;
+
+ ParseOption (option, *ptr ? ptr : NULL);
+ }
+
+ fclose (file);
+}
+
+static void Usage ()
+{
+ int i;
+ int max;
+ struct OptionInfo* info;
+
+ fprintf (stderr, "Recognized options:\n\n");
+
+ max = sizeof (optionTable) / sizeof (struct OptionInfo);
+ for (i = 0, info = optionTable; i < max; i++, info++) {
+
+ fprintf (stderr, "-%-20s %s\n", info->name,
+ info->parmDescription);
+
+ if (info->shortName)
+ fprintf (stderr, "-%-20s %s\n", info->shortName,
+ info->parmDescription);
+
+ fprintf (stderr, " %s\n\n", info->description);
+ }
+
+ exit (1);
+}
+
+void SetupPortRedirect (const char* parms)
+{
+ char buf[128];
+ char* ptr;
+ char* serverPool;
+ struct in_addr localAddr;
+ struct in_addr publicAddr;
+ struct in_addr remoteAddr;
+ port_range portRange;
+ u_short localPort = 0;
+ u_short publicPort = 0;
+ u_short remotePort = 0;
+ u_short numLocalPorts = 0;
+ u_short numPublicPorts = 0;
+ u_short numRemotePorts = 0;
+ int proto;
+ char* protoName;
+ char* separator;
+ int i;
+ struct alias_link *link = NULL;
+
+ strcpy (buf, parms);
+/*
+ * Extract protocol.
+ */
+ protoName = strtok (buf, " \t");
+ if (!protoName)
+ errx (1, "redirect_port: missing protocol");
+
+ proto = StrToProto (protoName);
+/*
+ * Extract local address.
+ */
+ ptr = strtok (NULL, " \t");
+ if (!ptr)
+ errx (1, "redirect_port: missing local address");
+
+ separator = strchr(ptr, ',');
+ if (separator) { /* LSNAT redirection syntax. */
+ localAddr.s_addr = INADDR_NONE;
+ localPort = ~0;
+ numLocalPorts = 1;
+ serverPool = ptr;
+ } else {
+ if ( StrToAddrAndPortRange (ptr, &localAddr, protoName, &portRange) != 0 )
+ errx (1, "redirect_port: invalid local port range");
+
+ localPort = GETLOPORT(portRange);
+ numLocalPorts = GETNUMPORTS(portRange);
+ serverPool = NULL;
+ }
+
+/*
+ * Extract public port and optionally address.
+ */
+ ptr = strtok (NULL, " \t");
+ if (!ptr)
+ errx (1, "redirect_port: missing public port");
+
+ separator = strchr (ptr, ':');
+ if (separator) {
+ if (StrToAddrAndPortRange (ptr, &publicAddr, protoName, &portRange) != 0 )
+ errx (1, "redirect_port: invalid public port range");
+ }
+ else {
+ publicAddr.s_addr = INADDR_ANY;
+ if (StrToPortRange (ptr, protoName, &portRange) != 0)
+ errx (1, "redirect_port: invalid public port range");
+ }
+
+ publicPort = GETLOPORT(portRange);
+ numPublicPorts = GETNUMPORTS(portRange);
+
+/*
+ * Extract remote address and optionally port.
+ */
+ ptr = strtok (NULL, " \t");
+ if (ptr) {
+ separator = strchr (ptr, ':');
+ if (separator) {
+ if (StrToAddrAndPortRange (ptr, &remoteAddr, protoName, &portRange) != 0)
+ errx (1, "redirect_port: invalid remote port range");
+ } else {
+ SETLOPORT(portRange, 0);
+ SETNUMPORTS(portRange, 1);
+ StrToAddr (ptr, &remoteAddr);
+ }
+ }
+ else {
+ SETLOPORT(portRange, 0);
+ SETNUMPORTS(portRange, 1);
+ remoteAddr.s_addr = INADDR_ANY;
+ }
+
+ remotePort = GETLOPORT(portRange);
+ numRemotePorts = GETNUMPORTS(portRange);
+
+/*
+ * Make sure port ranges match up, then add the redirect ports.
+ */
+ if (numLocalPorts != numPublicPorts)
+ errx (1, "redirect_port: port ranges must be equal in size");
+
+ /* Remote port range is allowed to be '0' which means all ports. */
+ if (numRemotePorts != numLocalPorts && (numRemotePorts != 1 || remotePort != 0))
+ errx (1, "redirect_port: remote port must be 0 or equal to local port range in size");
+
+ for (i = 0 ; i < numPublicPorts ; ++i) {
+ /* If remotePort is all ports, set it to 0. */
+ u_short remotePortCopy = remotePort + i;
+ if (numRemotePorts == 1 && remotePort == 0)
+ remotePortCopy = 0;
+
+ link = PacketAliasRedirectPort (localAddr,
+ htons(localPort + i),
+ remoteAddr,
+ htons(remotePortCopy),
+ publicAddr,
+ htons(publicPort + i),
+ proto);
+ }
+
+/*
+ * Setup LSNAT server pool.
+ */
+ if (serverPool != NULL && link != NULL) {
+ ptr = strtok(serverPool, ",");
+ while (ptr != NULL) {
+ if (StrToAddrAndPortRange(ptr, &localAddr, protoName, &portRange) != 0)
+ errx(1, "redirect_port: invalid local port range");
+
+ localPort = GETLOPORT(portRange);
+ if (GETNUMPORTS(portRange) != 1)
+ errx(1, "redirect_port: local port must be single in this context");
+ PacketAliasAddServer(link, localAddr, htons(localPort));
+ ptr = strtok(NULL, ",");
+ }
+ }
+}
+
+void
+SetupProtoRedirect(const char* parms)
+{
+ char buf[128];
+ char* ptr;
+ struct in_addr localAddr;
+ struct in_addr publicAddr;
+ struct in_addr remoteAddr;
+ int proto;
+ char* protoName;
+ struct protoent *protoent;
+
+ strcpy (buf, parms);
+/*
+ * Extract protocol.
+ */
+ protoName = strtok(buf, " \t");
+ if (!protoName)
+ errx(1, "redirect_proto: missing protocol");
+
+ protoent = getprotobyname(protoName);
+ if (protoent == NULL)
+ errx(1, "redirect_proto: unknown protocol %s", protoName);
+ else
+ proto = protoent->p_proto;
+/*
+ * Extract local address.
+ */
+ ptr = strtok(NULL, " \t");
+ if (!ptr)
+ errx(1, "redirect_proto: missing local address");
+ else
+ StrToAddr(ptr, &localAddr);
+/*
+ * Extract optional public address.
+ */
+ ptr = strtok(NULL, " \t");
+ if (ptr)
+ StrToAddr(ptr, &publicAddr);
+ else
+ publicAddr.s_addr = INADDR_ANY;
+/*
+ * Extract optional remote address.
+ */
+ ptr = strtok(NULL, " \t");
+ if (ptr)
+ StrToAddr(ptr, &remoteAddr);
+ else
+ remoteAddr.s_addr = INADDR_ANY;
+/*
+ * Create aliasing link.
+ */
+ (void)PacketAliasRedirectProto(localAddr, remoteAddr, publicAddr,
+ proto);
+}
+
+void SetupAddressRedirect (const char* parms)
+{
+ char buf[128];
+ char* ptr;
+ char* separator;
+ struct in_addr localAddr;
+ struct in_addr publicAddr;
+ char* serverPool;
+ struct alias_link *link;
+
+ strcpy (buf, parms);
+/*
+ * Extract local address.
+ */
+ ptr = strtok (buf, " \t");
+ if (!ptr)
+ errx (1, "redirect_address: missing local address");
+
+ separator = strchr(ptr, ',');
+ if (separator) { /* LSNAT redirection syntax. */
+ localAddr.s_addr = INADDR_NONE;
+ serverPool = ptr;
+ } else {
+ StrToAddr (ptr, &localAddr);
+ serverPool = NULL;
+ }
+/*
+ * Extract public address.
+ */
+ ptr = strtok (NULL, " \t");
+ if (!ptr)
+ errx (1, "redirect_address: missing public address");
+
+ StrToAddr (ptr, &publicAddr);
+ link = PacketAliasRedirectAddr(localAddr, publicAddr);
+
+/*
+ * Setup LSNAT server pool.
+ */
+ if (serverPool != NULL && link != NULL) {
+ ptr = strtok(serverPool, ",");
+ while (ptr != NULL) {
+ StrToAddr(ptr, &localAddr);
+ PacketAliasAddServer(link, localAddr, htons(~0));
+ ptr = strtok(NULL, ",");
+ }
+ }
+}
+
+void StrToAddr (const char* str, struct in_addr* addr)
+{
+ struct hostent* hp;
+
+ if (inet_aton (str, addr))
+ return;
+
+ hp = gethostbyname (str);
+ if (!hp)
+ errx (1, "unknown host %s", str);
+
+ memcpy (addr, hp->h_addr, sizeof (struct in_addr));
+}
+
+u_short StrToPort (const char* str, const char* proto)
+{
+ u_short port;
+ struct servent* sp;
+ char* end;
+
+ port = strtol (str, &end, 10);
+ if (end != str)
+ return htons (port);
+
+ sp = getservbyname (str, proto);
+ if (!sp)
+ errx (1, "unknown service %s/%s", str, proto);
+
+ return sp->s_port;
+}
+
+int StrToPortRange (const char* str, const char* proto, port_range *portRange)
+{
+ char* sep;
+ struct servent* sp;
+ char* end;
+ u_short loPort;
+ u_short hiPort;
+
+ /* First see if this is a service, return corresponding port if so. */
+ sp = getservbyname (str,proto);
+ if (sp) {
+ SETLOPORT(*portRange, ntohs(sp->s_port));
+ SETNUMPORTS(*portRange, 1);
+ return 0;
+ }
+
+ /* Not a service, see if it's a single port or port range. */
+ sep = strchr (str, '-');
+ if (sep == NULL) {
+ SETLOPORT(*portRange, strtol(str, &end, 10));
+ if (end != str) {
+ /* Single port. */
+ SETNUMPORTS(*portRange, 1);
+ return 0;
+ }
+
+ /* Error in port range field. */
+ errx (1, "unknown service %s/%s", str, proto);
+ }
+
+ /* Port range, get the values and sanity check. */
+ sscanf (str, "%hu-%hu", &loPort, &hiPort);
+ SETLOPORT(*portRange, loPort);
+ SETNUMPORTS(*portRange, 0); /* Error by default */
+ if (loPort <= hiPort)
+ SETNUMPORTS(*portRange, hiPort - loPort + 1);
+
+ if (GETNUMPORTS(*portRange) == 0)
+ errx (1, "invalid port range %s", str);
+
+ return 0;
+}
+
+
+int StrToProto (const char* str)
+{
+ if (!strcmp (str, "tcp"))
+ return IPPROTO_TCP;
+
+ if (!strcmp (str, "udp"))
+ return IPPROTO_UDP;
+
+ errx (1, "unknown protocol %s. Expected tcp or udp", str);
+}
+
+int StrToAddrAndPortRange (const char* str, struct in_addr* addr, char* proto, port_range *portRange)
+{
+ char* ptr;
+
+ ptr = strchr (str, ':');
+ if (!ptr)
+ errx (1, "%s is missing port number", str);
+
+ *ptr = '\0';
+ ++ptr;
+
+ StrToAddr (str, addr);
+ return StrToPortRange (ptr, proto, portRange);
+}
+
+static void
+SetupPunchFW(const char *strValue)
+{
+ unsigned int base, num;
+
+ if (sscanf(strValue, "%u:%u", &base, &num) != 2)
+ errx(1, "punch_fw: basenumber:count parameter required");
+
+ PacketAliasSetFWBase(base, num);
+ (void)PacketAliasSetMode(PKT_ALIAS_PUNCH_FW, PKT_ALIAS_PUNCH_FW);
+}
OpenPOWER on IntegriCloud