/* * 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 */ #include __FBSDID("$FreeBSD$"); #define SYSLOG_NAMES #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "natd.h" struct instance { const char *name; struct libalias *la; LIST_ENTRY(instance) list; int ifIndex; int assignAliasAddr; char* ifName; int logDropped; u_short inPort; u_short outPort; u_short inOutPort; struct in_addr aliasAddr; int ifMTU; int aliasOverhead; int dropIgnoredIncoming; int divertIn; int divertOut; int divertInOut; }; static LIST_HEAD(, instance) root = LIST_HEAD_INITIALIZER(root); struct libalias *mla; static struct instance *mip; static int ninstance = 1; /* * 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 int 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); static void SetupSkinnyPort(const char *strValue); static void NewInstance(const char *name); static void DoGlobal (int fd); static int CheckIpfwRulenum(unsigned int rnum); /* * Globals. */ static int verbose; static int background; static int running; static int logFacility; static int dynamicMode; static int icmpSock; static int logIpfwDenied; static const char* pidName; static int routeSock; static int globalPort; static int divertGlobal; static int exitDelay; int main (int argc, char** argv) { struct sockaddr_in addr; fd_set readMask; int fdMax; int rval; /* * Initialize packet aliasing software. * Done already here to be able to alter option bits * during command line and configuration file processing. */ NewInstance("default"); /* * Parse options. */ verbose = 0; background = 0; running = 1; dynamicMode = 0; logFacility = LOG_DAEMON; logIpfwDenied = -1; pidName = PIDFILE; routeSock = -1; icmpSock = -1; fdMax = -1; divertGlobal = -1; exitDelay = EXIT_DELAY; 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); LIST_FOREACH(mip, &root, list) { mla = mip->la; /* * If not doing the transparent proxying only, * check that valid aliasing address has been given. */ if (mip->aliasAddr.s_addr == INADDR_NONE && mip->ifName == NULL && !(LibAliasSetMode(mla, 0,0) & PKT_ALIAS_PROXY_ONLY)) errx (1, "instance %s: aliasing address not given", mip->name); if (mip->aliasAddr.s_addr != INADDR_NONE && mip->ifName != NULL) errx (1, "both alias address and interface " "name are not allowed"); /* * Check that valid port number is known. */ if (mip->inPort != 0 || mip->outPort != 0) if (mip->inPort == 0 || mip->outPort == 0) errx (1, "both input and output ports are required"); if (mip->inPort == 0 && mip->outPort == 0 && mip->inOutPort == 0) ParseOption ("port", DEFAULT_SERVICE); /* * Check if ignored packets should be dropped. */ mip->dropIgnoredIncoming = LibAliasSetMode (mla, 0, 0); mip->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 (mip->inOutPort) { mip->divertInOut = socket (PF_INET, SOCK_RAW, IPPROTO_DIVERT); if (mip->divertInOut == -1) Quit ("Unable to create divert socket."); if (mip->divertInOut > fdMax) fdMax = mip->divertInOut; mip->divertIn = -1; mip->divertOut = -1; /* * Bind socket. */ addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = mip->inOutPort; if (bind (mip->divertInOut, (struct sockaddr*) &addr, sizeof addr) == -1) Quit ("Unable to bind divert socket."); } else { mip->divertIn = socket (PF_INET, SOCK_RAW, IPPROTO_DIVERT); if (mip->divertIn == -1) Quit ("Unable to create incoming divert socket."); if (mip->divertIn > fdMax) fdMax = mip->divertIn; mip->divertOut = socket (PF_INET, SOCK_RAW, IPPROTO_DIVERT); if (mip->divertOut == -1) Quit ("Unable to create outgoing divert socket."); if (mip->divertOut > fdMax) fdMax = mip->divertOut; mip->divertInOut = -1; /* * Bind divert sockets. */ addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = mip->inPort; if (bind (mip->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 = mip->outPort; if (bind (mip->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. */ if (mip->ifName) { if (dynamicMode) { if (routeSock == -1) routeSock = socket (PF_ROUTE, SOCK_RAW, 0); if (routeSock == -1) Quit ("Unable to create routing info socket."); if (routeSock > fdMax) fdMax = routeSock; mip->assignAliasAddr = 1; } else { do { rval = SetAliasAddressFromIfName (mip->ifName); if (background == 0 || dynamicMode == 0) break; if (rval == EAGAIN) sleep(1); } while (rval == EAGAIN); if (rval != 0) exit(1); } } } if (globalPort) { divertGlobal = socket (PF_INET, SOCK_RAW, IPPROTO_DIVERT); if (divertGlobal == -1) Quit ("Unable to create divert socket."); if (divertGlobal > fdMax) fdMax = divertGlobal; /* * Bind socket. */ addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = globalPort; if (bind (divertGlobal, (struct sockaddr*) &addr, sizeof addr) == -1) Quit ("Unable to bind global divert socket."); } /* * 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); if (exitDelay) signal(SIGTERM, InitiateShutdown); else signal(SIGTERM, Shutdown); signal (SIGHUP, RefreshAddr); /* * Set alias address if it has been given. */ mip = LIST_FIRST(&root); /* XXX: simon */ LIST_FOREACH(mip, &root, list) { mla = mip->la; if (mip->aliasAddr.s_addr != INADDR_NONE) LibAliasSetAddress (mla, mip->aliasAddr); } while (running) { mip = LIST_FIRST(&root); /* XXX: simon */ if (mip->divertInOut != -1 && !mip->ifName && ninstance == 1) { /* * When using only one socket, just call * DoAliasing repeatedly to process packets. */ DoAliasing (mip->divertInOut, DONT_KNOW); continue; } /* * Build read mask from socket descriptors to select. */ FD_ZERO (&readMask); /* * Check if new packets are available. */ LIST_FOREACH(mip, &root, list) { if (mip->divertIn != -1) FD_SET (mip->divertIn, &readMask); if (mip->divertOut != -1) FD_SET (mip->divertOut, &readMask); if (mip->divertInOut != -1) FD_SET (mip->divertInOut, &readMask); } /* * Routing info is processed always. */ if (routeSock != -1) FD_SET (routeSock, &readMask); if (divertGlobal != -1) FD_SET (divertGlobal, &readMask); if (select (fdMax + 1, &readMask, NULL, NULL, NULL) == -1) { if (errno == EINTR) continue; Quit ("Select failed."); } if (divertGlobal != -1) if (FD_ISSET (divertGlobal, &readMask)) DoGlobal (divertGlobal); LIST_FOREACH(mip, &root, list) { mla = mip->la; if (mip->divertIn != -1) if (FD_ISSET (mip->divertIn, &readMask)) DoAliasing (mip->divertIn, INPUT); if (mip->divertOut != -1) if (FD_ISSET (mip->divertOut, &readMask)) DoAliasing (mip->divertOut, OUTPUT); if (mip->divertInOut != -1) if (FD_ISSET (mip->divertInOut, &readMask)) DoAliasing (mip->divertInOut, DONT_KNOW); } if (routeSock != -1) if (FD_ISSET (routeSock, &readMask)) HandleRoutingInfo (routeSock); } if (background) unlink (pidName); return 0; } static void DaemonMode(void) { FILE* pidFile; daemon (0, 0); background = 1; pidFile = fopen (pidName, "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 DoGlobal (int fd) { int bytes; int origBytes; char buf[IP_MAXPACKET]; struct sockaddr_in addr; int wrote; socklen_t addrSize; struct ip* ip; char msgBuf[80]; /* * 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; } #if 0 if (mip->assignAliasAddr) { if (SetAliasAddressFromIfName (mip->ifName) != 0) exit(1); mip->assignAliasAddr = 0; } #endif /* * This is an IP packet. */ ip = (struct ip*) buf; if (verbose) { /* * Print packet direction and protocol type. */ printf ("Glb "); 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); } LIST_FOREACH(mip, &root, list) { mla = mip->la; if (LibAliasOutTry (mla, buf, IP_MAXPACKET, 0) != PKT_ALIAS_IGNORED) break; } /* * Length might have changed during aliasing. */ bytes = ntohs (ip->ip_len); /* * Update alias overhead size for outgoing packets. */ if (mip != NULL && bytes - origBytes > mip->aliasOverhead) mip->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 (mip->ifMTU != -1) SendNeedFragIcmp (icmpSock, (struct ip*) buf, mip->ifMTU - mip->aliasOverhead); } else if (errno == EACCES && logIpfwDenied) { sprintf (msgBuf, "failed to write packet back"); Warn (msgBuf); } } } static void DoAliasing (int fd, int direction) { int bytes; int origBytes; char buf[IP_MAXPACKET]; struct sockaddr_in addr; int wrote; int status; socklen_t addrSize; struct ip* ip; char msgBuf[80]; int rval; if (mip->assignAliasAddr) { do { rval = SetAliasAddressFromIfName (mip->ifName); if (background == 0 || dynamicMode == 0) break; if (rval == EAGAIN) sleep(1); } while (rval == EAGAIN); if (rval != 0) exit(1); mip->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 an 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 "); if (ninstance > 1) printf ("{%s}", mip->name); 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. */ LibAliasOut (mla, buf, IP_MAXPACKET); } else { /* * Do aliasing. */ status = LibAliasIn (mla, buf, IP_MAXPACKET); if (status == PKT_ALIAS_IGNORED && mip->dropIgnoredIncoming) { if (verbose) printf (" dropped.\n"); if (mip->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 > mip->aliasOverhead) mip->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 && mip->ifMTU != -1) SendNeedFragIcmp (icmpSock, (struct ip*) buf, mip->ifMTU - mip->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)) { LIST_FOREACH(mip, &root, list) { mla = mip->la; if (ifMsg.ifm_index == mip->ifIndex) { if (verbose) printf("Interface address/MTU has probably changed.\n"); mip->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 int 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 && errno != ENOMEM) 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. */ mip->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) { mip->ifIndex = ifm->ifm_index; mip->ifMTU = ifm->ifm_data.ifi_mtu; break; } } } if (!mip->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); for (i = 1; i < RTA_IFA; i <<= 1) if (ifam->ifam_addrs & i) cp += SA_SIZE((struct sockaddr *)cp); if (((struct sockaddr *)cp)->sa_family == AF_INET) { sin = (struct sockaddr_in *)cp; break; } } } if (sin == NULL) { warnx("%s: cannot get interface address", ifn); free(buf); return EAGAIN; } LibAliasSetAddress(mla, sin->sin_addr); syslog(LOG_INFO, "Aliasing to %s, mtu %d bytes", inet_ntoa(sin->sin_addr), mip->ifMTU); free(buf); return 0; } 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 __unused) { LibAliasRefreshModules(); if (mip != NULL && mip->ifName != NULL) mip->assignAliasAddr = 1; } static void InitiateShutdown (int sig __unused) { /* * Start timer to allow kernel gracefully * shutdown existing connections when system * is shut down. */ siginterrupt(SIGALRM, 1); signal (SIGALRM, Shutdown); ualarm(exitDelay*1000, 1000); } static void Shutdown (int sig __unused) { running = 0; } /* * Different options recognized by this program. */ enum Option { LibAliasOption, Instance, Verbose, InPort, OutPort, Port, GlobalPort, AliasAddress, TargetAddress, InterfaceName, RedirectPort, RedirectProto, RedirectAddress, ConfigFile, DynamicMode, ProxyRule, LogDenied, LogFacility, PunchFW, SkinnyPort, LogIpfwDenied, PidFile, ExitDelay }; 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[] = { { LibAliasOption, PKT_ALIAS_UNREGISTERED_ONLY, YesNo, "[yes|no]", "alias only unregistered addresses", "unregistered_only", "u" }, { LibAliasOption, PKT_ALIAS_LOG, YesNo, "[yes|no]", "enable logging", "log", "l" }, { LibAliasOption, PKT_ALIAS_PROXY_ONLY, YesNo, "[yes|no]", "proxy only", "proxy_only", NULL }, { LibAliasOption, PKT_ALIAS_REVERSE, YesNo, "[yes|no]", "operate in reverse mode", "reverse", NULL }, { LibAliasOption, PKT_ALIAS_DENY_INCOMING, YesNo, "[yes|no]", "allow incoming connections", "deny_incoming", "d" }, { LibAliasOption, PKT_ALIAS_USE_SOCKETS, YesNo, "[yes|no]", "use sockets to inhibit port conflict", "use_sockets", "s" }, { LibAliasOption, 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" }, { GlobalPort, 0, Service, "number|service_name", "set globalport", "globalport", NULL }, { 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 }, { SkinnyPort, 0, String, "port", "set the TCP port for use with the Skinny Station protocol", "skinny_port", NULL }, { LogIpfwDenied, 0, YesNo, "[yes|no]", "log packets converted by natd, but denied by ipfw", "log_ipfw_denied", NULL }, { PidFile, 0, String, "file_name", "store PID in an alternate file", "pid_file", "P" }, { Instance, 0, String, "instance name", "name of aliasing engine instance", "instance", NULL }, { ExitDelay, 0, Numeric, "ms", "delay in ms before daemon exit after signal", "exit_delay", 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; const 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 LibAliasOption: aliasValue = yesNoValue ? info->packetAliasOpt : 0; LibAliasSetMode (mla, aliasValue, info->packetAliasOpt); break; case Verbose: verbose = yesNoValue; break; case DynamicMode: dynamicMode = yesNoValue; break; case InPort: mip->inPort = uNumValue; break; case OutPort: mip->outPort = uNumValue; break; case Port: mip->inOutPort = uNumValue; break; case GlobalPort: globalPort = uNumValue; break; case AliasAddress: memcpy (&mip->aliasAddr, &addrValue, sizeof (struct in_addr)); break; case TargetAddress: LibAliasSetTarget(mla, addrValue); break; case RedirectPort: SetupPortRedirect (strValue); break; case RedirectProto: SetupProtoRedirect(strValue); break; case RedirectAddress: SetupAddressRedirect (strValue); break; case ProxyRule: LibAliasProxyRule (mla, strValue); break; case InterfaceName: if (mip->ifName) free (mip->ifName); mip->ifName = strdup (strValue); break; case ConfigFile: ReadConfigFile (strValue); break; case LogDenied: mip->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 SkinnyPort: SetupSkinnyPort(strValue); break; case LogIpfwDenied: logIpfwDenied = yesNoValue; break; case PidFile: pidName = strdup (strValue); break; case Instance: NewInstance(strValue); break; case ExitDelay: if (numValue < 0 || numValue > MAX_EXIT_DELAY) errx(1, "Incorrect exit delay: %d", numValue); exitDelay = numValue; 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(void) { 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; 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 *aliaslink = NULL; buf = strdup (parms); if (!buf) errx (1, "redirect_port: strdup() failed"); /* * 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; aliaslink = LibAliasRedirectPort (mla, localAddr, htons(localPort + i), remoteAddr, htons(remotePortCopy), publicAddr, htons(publicPort + i), proto); } /* * Setup LSNAT server pool. */ if (serverPool != NULL && aliaslink != 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"); LibAliasAddServer(mla, aliaslink, localAddr, htons(localPort)); ptr = strtok(NULL, ","); } } free (buf); } void SetupProtoRedirect(const char* parms) { char *buf; char* ptr; struct in_addr localAddr; struct in_addr publicAddr; struct in_addr remoteAddr; int proto; char* protoName; struct protoent *protoent; buf = strdup (parms); if (!buf) errx (1, "redirect_port: strdup() failed"); /* * 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)LibAliasRedirectProto(mla, localAddr, remoteAddr, publicAddr, proto); free (buf); } void SetupAddressRedirect (const char* parms) { char *buf; char* ptr; char* separator; struct in_addr localAddr; struct in_addr publicAddr; char* serverPool; struct alias_link *aliaslink; buf = strdup (parms); if (!buf) errx (1, "redirect_port: strdup() failed"); /* * 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); aliaslink = LibAliasRedirectAddr(mla, localAddr, publicAddr); /* * Setup LSNAT server pool. */ if (serverPool != NULL && aliaslink != NULL) { ptr = strtok(serverPool, ","); while (ptr != NULL) { StrToAddr(ptr, &localAddr); LibAliasAddServer(mla, aliaslink, localAddr, htons(~0)); ptr = strtok(NULL, ","); } } free (buf); } 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, "%s/%s: unknown service", 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, "%s/%s: unknown service", 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"); if (CheckIpfwRulenum(base + num - 1) == -1) errx(1, "punch_fw: basenumber:count parameter should fit " "the maximum allowed rule numbers"); LibAliasSetFWBase(mla, base, num); (void)LibAliasSetMode(mla, PKT_ALIAS_PUNCH_FW, PKT_ALIAS_PUNCH_FW); } static void SetupSkinnyPort(const char *strValue) { unsigned int port; if (sscanf(strValue, "%u", &port) != 1) errx(1, "skinny_port: port parameter required"); LibAliasSetSkinnyPort(mla, port); } static void NewInstance(const char *name) { struct instance *ip; LIST_FOREACH(ip, &root, list) { if (!strcmp(ip->name, name)) { mla = ip->la; mip = ip; return; } } ninstance++; ip = calloc(sizeof *ip, 1); ip->name = strdup(name); ip->la = LibAliasInit (ip->la); ip->assignAliasAddr = 0; ip->ifName = NULL; ip->logDropped = 0; ip->inPort = 0; ip->outPort = 0; ip->inOutPort = 0; ip->aliasAddr.s_addr = INADDR_NONE; ip->ifMTU = -1; ip->aliasOverhead = 12; LIST_INSERT_HEAD(&root, ip, list); mla = ip->la; mip = ip; } static int CheckIpfwRulenum(unsigned int rnum) { unsigned int default_rule; size_t len = sizeof(default_rule); if (sysctlbyname("net.inet.ip.fw.default_rule", &default_rule, &len, NULL, 0) == -1) { warn("Failed to get the default ipfw rule number, using " "default historical value 65535. The reason was"); default_rule = 65535; } if (rnum >= default_rule) { return -1; } return 0; }