From 59a701d2eb6099d33d593a0f994d15fe5a605440 Mon Sep 17 00:00:00 2001 From: brian Date: Sun, 22 Jun 1997 04:19:08 +0000 Subject: Bring natd into main source tree now that the pppd/natd combination works ok. Submitted by: Ari Suutari --- usr.sbin/natd/.depend | 30 + usr.sbin/natd/Makefile | 10 + usr.sbin/natd/icmp.c | 112 +++ usr.sbin/natd/natd.8 | 380 +++++++++ usr.sbin/natd/natd.c | 1396 ++++++++++++++++++++++++++++++++++ usr.sbin/natd/natd.h | 5 + usr.sbin/natd/samples/natd.cf.sample | 52 ++ usr.sbin/natd/samples/natd.test | 14 + 8 files changed, 1999 insertions(+) create mode 100644 usr.sbin/natd/.depend create mode 100644 usr.sbin/natd/Makefile create mode 100644 usr.sbin/natd/icmp.c create mode 100644 usr.sbin/natd/natd.8 create mode 100644 usr.sbin/natd/natd.c create mode 100644 usr.sbin/natd/natd.h create mode 100644 usr.sbin/natd/samples/natd.cf.sample create mode 100644 usr.sbin/natd/samples/natd.test (limited to 'usr.sbin') diff --git a/usr.sbin/natd/.depend b/usr.sbin/natd/.depend new file mode 100644 index 0000000..86e9112 --- /dev/null +++ b/usr.sbin/natd/.depend @@ -0,0 +1,30 @@ +# natd.c icmp.c +natd.o: natd.c /usr/include/stdlib.h /usr/include/machine/ansi.h \ + /usr/include/machine/types.h /usr/include/sys/cdefs.h \ + /usr/include/stdio.h /usr/include/unistd.h /usr/include/sys/types.h \ + /usr/include/machine/endian.h /usr/include/sys/unistd.h \ + /usr/include/string.h /usr/include/ctype.h /usr/include/runetype.h \ + /usr/include/sys/socket.h /usr/include/sys/time.h /usr/include/time.h \ + /usr/include/errno.h /usr/include/signal.h /usr/include/sys/signal.h \ + /usr/include/machine/signal.h /usr/include/machine/trap.h \ + /usr/include/netdb.h /usr/include/netinet/in.h \ + /usr/include/netinet/in_systm.h /usr/include/netinet/ip.h \ + /usr/include/machine/in_cksum.h /usr/include/netinet/tcp.h \ + /usr/include/sys/ioctl.h /usr/include/sys/ttycom.h \ + /usr/include/sys/ioccom.h /usr/include/sys/filio.h \ + /usr/include/sys/sockio.h /usr/include/net/if.h \ + /usr/include/net/route.h /usr/include/net/radix.h \ + /usr/include/arpa/inet.h /usr/include/syslog.h /usr/include/alias.h \ + natd.h +icmp.o: icmp.c /usr/include/stdlib.h /usr/include/machine/ansi.h \ + /usr/include/machine/types.h /usr/include/sys/cdefs.h \ + /usr/include/stdio.h /usr/include/unistd.h /usr/include/sys/types.h \ + /usr/include/machine/endian.h /usr/include/sys/unistd.h \ + /usr/include/string.h /usr/include/ctype.h /usr/include/runetype.h \ + /usr/include/sys/socket.h /usr/include/sys/time.h /usr/include/time.h \ + /usr/include/errno.h /usr/include/signal.h /usr/include/sys/signal.h \ + /usr/include/machine/signal.h /usr/include/machine/trap.h \ + /usr/include/netdb.h /usr/include/netinet/in.h \ + /usr/include/netinet/in_systm.h /usr/include/netinet/ip.h \ + /usr/include/netinet/ip_icmp.h /usr/include/machine/in_cksum.h \ + /usr/include/alias.h natd.h diff --git a/usr.sbin/natd/Makefile b/usr.sbin/natd/Makefile new file mode 100644 index 0000000..015d645 --- /dev/null +++ b/usr.sbin/natd/Makefile @@ -0,0 +1,10 @@ +# $Id:$ + +PROG= natd +SRCS= natd.c icmp.c +CFLAGS+=-Wall +LDADD= -lalias +DPADD= ${LIBALIAS} +MAN8= natd.8 + +.include diff --git a/usr.sbin/natd/icmp.c b/usr.sbin/natd/icmp.c new file mode 100644 index 0000000..40464ff --- /dev/null +++ b/usr.sbin/natd/icmp.c @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include "natd.h" + +int SendNeedFragIcmp (int sock, struct ip* failedDgram, int mtu) +{ + char icmpBuf[IP_MAXPACKET]; + struct ip* ip; + struct icmp* icmp; + int icmpLen; + int failBytes; + int failHdrLen; + struct sockaddr_in addr; + int wrote; + struct in_addr swap; +/* + * Don't send error if packet is + * not the first fragment. + */ + if (ntohs (failedDgram->ip_off) & ~(IP_MF | IP_DF)) + return 0; +/* + * Dont respond if failed datagram is ICMP. + */ + if (failedDgram->ip_p == IPPROTO_ICMP) + return 0; +/* + * Start building the message. + */ + ip = (struct ip*) icmpBuf; + icmp = (struct icmp*) (icmpBuf + sizeof (struct ip)); +/* + * Complete ICMP part. + */ + icmp->icmp_type = ICMP_UNREACH; + icmp->icmp_code = ICMP_UNREACH_NEEDFRAG; + icmp->icmp_cksum = 0; + icmp->icmp_void = 0; + icmp->icmp_nextmtu = htons (mtu); +/* + * Copy header + 64 bits of original datagram. + */ + failHdrLen = (failedDgram->ip_hl << 2); + failBytes = failedDgram->ip_len - failHdrLen; + if (failBytes > 8) + failBytes = 8; + + failBytes += failHdrLen; + icmpLen = ICMP_MINLEN + failBytes; + + memcpy (&icmp->icmp_ip, failedDgram, failBytes); +/* + * Calculate checksum. + */ + icmp->icmp_cksum = InternetChecksum ((u_short*) icmp, icmpLen); +/* + * Add IP header using old IP header as template. + */ + memcpy (ip, failedDgram, sizeof (struct ip)); + + ip->ip_v = 4; + ip->ip_hl = 5; + ip->ip_len = htons (sizeof (struct ip) + icmpLen); + ip->ip_p = IPPROTO_ICMP; + ip->ip_tos = 0; + + swap = ip->ip_dst; + ip->ip_dst = ip->ip_src; + ip->ip_src = swap; + + PacketAliasIn ((char*) ip, IP_MAXPACKET); + + addr.sin_family = AF_INET; + addr.sin_addr = ip->ip_dst; + addr.sin_port = 0; +/* + * Put packet into processing queue. + */ + wrote = sendto (sock, + icmp, + icmpLen, + 0, + (struct sockaddr*) &addr, + sizeof addr); + + if (wrote != icmpLen) + Warn ("Cannot send ICMP message."); + + return 1; +} + + diff --git a/usr.sbin/natd/natd.8 b/usr.sbin/natd/natd.8 new file mode 100644 index 0000000..91d7b6d --- /dev/null +++ b/usr.sbin/natd/natd.8 @@ -0,0 +1,380 @@ +.\" manual page [] for natd 1.4 +.Dd 15 April 1997 +.Os FreeBSD +.Dt NATD 8 +.Sh NAME +.Nm natd +.Nd +Network Address Translation Daemon +.Sh SYNOPSIS +.Nm +.Op Fl ldsmvu +.Op Fl permanent_link +.Op Fl dynamic +.Op Fl i Ar inport +.Op Fl o Ar outport +.Op Fl p Ar port +.Op Fl a Ar address +.Op Fl i Ar interface +.Op Fl f Ar configfile + +.Nm +.Op Fl log +.Op Fl deny_incoming +.Op Fl use_sockets +.Op Fl same_ports +.Op Fl verbose +.Op Fl unregistered_only +.Op Fl permanent_link +.Op Fl dynamic +.Op Fl inport Ar inport +.Op Fl outport Ar outport +.Op Fl port Ar port +.Op Fl alias_address Ar address +.Op Fl interface Ar interface +.Op Fl config Ar configfile + +.Sh DESCRIPTION +This program provides a Network Address Translation facility for use +with +.Xr divert 4 +sockets under FreeBSD. Most of the command line options are available +in a single character short form or in a long form. Use of the long +form is encouraged as it makes things clearer to the casual observer. + +.Pp +.Nm Natd +normally runs in the background as a daemon. It is passed raw IP packets +as they travel into and out of the machine, and will possibly change these +before re-injecting them back into the IP packet stream. + +.Pp +.Nm Natd +changes all packets destined for another host so that their source +IP number is that of the current machine. For each packet changed +in this manner, an internal table entry is created to record this +fact. The source port number is also changed to indicate the +table entry applying to the packet. Packets that are received with +a target IP of the current host are checked against this internal +table. If an entry is found, it is used to determine the correct +target IP number and port to place in the packet. + +.Pp +The following command line options are available. +.Bl -tag -width Fl + +.It Fl log | l +Log various aliasing statistics and information to the file +.Pa /var/log/alias.log . +This file is truncated each time natd is started. + +.It Fl deny_incoming | d +Reject packets destined for the current IP number that have no entry +in the internal translation table. + +.It Fl use_sockets | s +Allocate a +.Xr socket 2 +in order to establish an FTP data or IRC DCC send connection. This +option uses more system resources, but guarantees successful connections +when port numbers conflict. + +.It Fl same_ports | m +Try to keep the same port number when altering outgoing packets. +With this option, protocols such as RPC will have a better chance +of working. If it is not possible to maintain the port number, it +will be silently changed as per normal. + +.It Fl verbose | v +Don't call +.Xr fork 2 +or +.Xr daemon 3 +on startup. Instead, stay attached to the controling terminal and +display all packet alterations to the standard output. This option +should only be used for debugging purposes. + +.It Fl unregistered_only | u +Only alter outgoing packets with an unregistered source address. +According to rfc 1918, unregistered source addresses are 10.0.0.0/8, +172.16.0.0/12 and 192.168.0.0/16. + +.It Fl redirect_port Ar linkspec +Redirect incoming connections arriving to given port to another host and port. +Linkspec is of the form + + proto targetIP:targetPORT [aliasIP:]aliasPORT [remoteIP[:remotePORT]] + +where proto is either tcp or udp, targetIP is the desired target IP +number, targetPORT is the desired target PORT number, aliasPORT +is the requested PORT number and aliasIP is the aliasing address. +RemoteIP and remotePORT can be used to specify the connection +more accurately if necessary. +For example, the argument + +.Ar tcp inside1:telnet 6666 + +means that tcp packets destined for port 6666 on this machine will +be sent to the telnet port on the inside1 machine. + +.It Fl redirect_address Ar localIP publicIP +Redirect traffic for public IP address to a machine on the local +network. This function is known as "static NAT". Normally static NAT +is useful if your ISP has allocated a small block of IP addresses to you, +but it can even be used in the case of single address: + + redirect_address 10.0.0.8 0.0.0.0 + +The above command would redirect all incoming traffic +to machine 10.0.0.8. + +If several address aliases specify the same public address +as follows + + redirect_address 192.168.0.2 public_addr + redirect_address 192.168.0.3 public_addr + redirect_address 192.168.0.4 public_addr + +the incoming traffic will be directed to the last +translated local address (192.168.0.4), but outgoing +traffice to the first two addresses will still be aliased +to specified public address. + +.It Fl permanent_link Ar linkspec +Create a permanent entry in the internal alias table. Linkspec is +of the form + + proto targetIP:targetPORT sourceIP:sourcePORT aliasPORT + +where proto is either tcp or udp, targetIP is the desired target IP +number, targetPORT is the desired target PORT number, sourceIP and +sourcePORT match the incoming packet, and aliasPORT is the requested +PORT number. Values of zero are considered as wildcards. For example, +the argument + +.Ar tcp inside1:telnet outside1:0 6666 + +means that tcp packets destined for port 6666 on this machine from the +outside1 machine (any port) will be sent to the telnet port on the +inside1 machine. + +New installations are encouraged to use redirect_port instead. + +.It Fl dynamic +If the +.Fl n +or +.Fl interface +option is used, +.Nm natd +will monitor the routing socket for alterations to the +.Ar interface +passed. If the interfaces IP number is changed, +.Nm natd +will dynamically alter its concept of the alias address. + +.It Fl i | inport Ar inport +Read from and write to +.Ar inport , +treating all packets as packets coming into the machine. + +.It Fl o | outport Ar outport +Read from and write to +.Ar outport , +treating all packets as packets going out of the machine. + +.It Fl p | port Ar port +Read from and write to +.Ar port , +distinguishing packets as incoming our outgoing using the rules specified in +.Xr divert 4 . +If +.Ar port +is not numeric, it is searched for in the +.Pa /etc/services +database using the +.Xr getservbyname 3 +function. If this flag is not specified, the divert port named natd will +be used as a default. An example entry in the +.Pa /etc/services +database would be: + + natd 6668/divert # Network Address Translation socket + +Refer to +.Xr services 5 +for further details. + +.It Fl a | alias_address Ar address +Use +.Ar address +as the alias address. If this option is not specified, the +.Fl n +or +.Fl interface +option must be used. + +.It Fl n | interface Ar interface +Use +.Ar interface +to determine the alias address. If there is a possibility that the +IP number associated with +.Ar interface +may change, the +.Fl dynamic +flag should also be used. If this option is not specified, the +.Fl a +or +.Fl alias_address +flag must be used. + +.It Fl f | config Ar configfile +Read configuration from +.Ar configfile . +.Ar Configfile +contains a list of options, one per line in the same form as the +long form of the above command line flags. For example, the line + + alias_address 158.152.17.1 + +would specify an alias address of 158.152.17.1. Options that don't +take an argument are specified with an option of +.Ar yes +or +.Ar no +in the configuration file. For example, the line + + log yes + +is synonomous with +.Fl log . +Empty lines and lines beginning with '#' are ignored. + +.El + +.Sh RUNNING NATD +The following steps are necessary before attempting to run +.Nm natd : + +.Bl -enum +.It +Get FreeBSD version 2.2 or higher. Versions before this do not support +.Xr divert 4 +sockets. + +.It +Build a custom kernel with the following options: + + options IPFIREWALL + options IPDIVERT + +Refer to the handbook for detailed instructions on building a custom +kernel. + +.It +Ensure that your machine is acting as a gateway. This can be done by +specifying the line + + gateway=YES + +in +.Pa /etc/sysconfig , +or using the command + + sysctl -w net.inet.ip.forwarding=1 + +.It +If you wish to use the +.Fl n +or +.Fl interface +flags, make sure that your interface is already configured. If, for +example, you wish to specify tun0 as your +.Ar interface , +and you're using +.Xr ppp 8 +on that interface, you must make sure that you start +.Nm ppp +prior to starting +.Nm natd . + +.It +Create an entry in +.Pa /etc/services : + + natd 6668/divert # Network Address Translation socket + +This gives a default for the +.Fl p +or +.Fl port +flag. + +.El +.Pp +Running +.Nm natd +is fairly straight forward. The line + + natd -interface ed0 + +should suffice in most cases (substituting the correct interface name). Once +.Nm natd +is running, you must ensure that traffic is diverted to natd: + +.Bl -enum +.It +You will need to adjust the +.Pa /etc/rc.firewall +script to taste. If you're not interested in having a firewall, the +following lines will do: + + /sbin/ipfw -f flush + /sbin/ipfw add divert 6668 all from any to any via ed0 + /sbin/ipfw add pass all from any to any + +The second line depends on your interface and assumes that you've updated +.Pa /etc/services +as above. If you specify real firewall rules, it's best to specify +line 2 at the start of the script so that +.Nm natd +sees all packets before they are dropped by the firewall. The firewall +rules will be run again on each packet after translation by +.Nm natd , +minus any divert rules. + +.It +Enable your firewall by setting + + firewall=YES + +in +.Pa /etc/sysconfig . +This tells the system startup scripts to run the +.Pa /etc/rc.firewall +script. If you don't wish to reboot now, just run this by hand from the +console. NEVER run this from a virtual session unless you put it into +the background. If you do, you'll lock yourself out after the flush +takes place, and execution of +.Pa /etc/rc.firewall +will stop at this point - blocking all accesses permanently. Running +the script in the background should be enough to prevent this disaster. + +.El + +.Sh SEE ALSO +.Xr socket 2 , +.Xr getservbyname 2 , +.Xr divert 4 , +.Xr services 5 , +.Xr ipfw 8 + +.Sh AUTHORS +This program is the result of the efforts of many people at different +times: + + Divert sockets: Archie Cobbs + Packet aliasing: Charles Mott + IRC support & misc additions: Eivind Eklund + Natd: Ari Suutari + Glue: Brian Somers diff --git a/usr.sbin/natd/natd.c b/usr.sbin/natd/natd.c new file mode 100644 index 0000000..f166765 --- /dev/null +++ b/usr.sbin/natd/natd.c @@ -0,0 +1,1396 @@ +/* + * natd - Network Address Translation Daemon for FreeBSD. + * + * This software ois 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 (ari@kn6-045.ktvlpr.inet.fi, ari@ps.carel.fi) + * + */ + +#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" + +/* + * Default values for input and output + * divert socket ports. + */ + +#define DEFAULT_SERVICE "natd" + +/* + * Function prototypes. + */ + +static void DoAliasing (int fd); +static void DaemonMode (); +static void HandleRoutingInfo (int fd); +static void Usage (); +static void PrintPacket (struct ip*); +static void SetAliasAddressFromIfName (char* ifName); +static void InitiateShutdown (); +static void Shutdown (); +static void RefreshAddr (); +static void ParseOption (char* option, char* parms, int cmdLine); +static void ReadConfigFile (char* fileName); +static void SetupPermanentLink (char* parms); +static void SetupPortRedirect (char* parms); +static void SetupAddressRedirect (char* parms); +static void StrToAddr (char* str, struct in_addr* addr); +static int StrToPort (char* str, char* proto); +static int StrToProto (char* str); +static int StrToAddrAndPort (char* str, struct in_addr* addr, char* proto); +static void ParseArgs (int argc, char** argv); + +/* + * Globals. + */ + +static int verbose; +static int background; +static int running; +static int assignAliasAddr; +static char* ifName; +static int ifIndex; +static int inPort; +static int outPort; +static int inOutPort; +static struct in_addr aliasAddr; +static int dynamicMode; +static int ifMTU; +static int aliasOverhead; +static int icmpSock; + +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. + */ + InitPacketAlias (); +/* + * 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; + + ParseArgs (argc, argv); +/* + * Check that valid aliasing address has been given. + */ + if (aliasAddr.s_addr == INADDR_NONE && ifName == NULL) { + + fprintf (stderr, "Aliasing address not given.\n"); + exit (1); + } + + if (aliasAddr.s_addr != INADDR_NONE && ifName != NULL) { + + fprintf (stderr, "Both alias address and interface name " + "are not allowed.\n"); + exit (1); + } +/* + * Check that valid port number is known. + */ + if (inPort != 0 || outPort != 0) + if (inPort == 0 || outPort == 0) { + + fprintf (stderr, "Both input and output ports" + " are required.\n"); + exit (1); + } + + if (inPort == 0 && outPort == 0 && inOutPort == 0) + ParseOption ("port", DEFAULT_SERVICE, 0); + +/* + * 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. + */ + if (ifName && dynamicMode) { + + routeSock = socket (PF_ROUTE, SOCK_RAW, 0); + if (routeSock == -1) + Quit ("Unable to create routing info socket."); + } + else + routeSock = -1; +/* + * Create socket for sending ICMP messages. + */ + icmpSock = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP); + if (icmpSock == -1) + Quit ("Unable to create ICMP socket."); +/* + * Become a daemon unless verbose mode was requested. + */ + if (!verbose) + DaemonMode (); +/* + * Catch signals to manage shutdown and + * refresh of interface address. + */ + signal (SIGTERM, InitiateShutdown); + signal (SIGHUP, RefreshAddr); +/* + * Set alias address if it has been given. + */ + if (aliasAddr.s_addr != INADDR_NONE) + SetPacketAliasAddress (aliasAddr); + + if (divertInOut != -1 && !ifName) { + +/* + * When using only one socket, just call + * DoAliasing repeatedly to process packets. + */ + while (running) + DoAliasing (divertInOut); + } + else { +/* + * 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) { +/* + * Build read mask from socket descriptors to select. + */ + FD_ZERO (&readMask); + + if (divertIn != -1) + FD_SET (divertIn, &readMask); + + if (divertOut != -1) + FD_SET (divertOut, &readMask); + + if (divertInOut != -1) + FD_SET (divertInOut, &readMask); + + 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); + + if (divertOut != -1) + if (FD_ISSET (divertOut, &readMask)) + DoAliasing (divertOut); + + if (divertInOut != -1) + if (FD_ISSET (divertInOut, &readMask)) + DoAliasing (divertInOut); + + 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* parm; + char* opt; + char parmBuf[256]; + + for (arg = 1; arg < argc; arg++) { + + opt = argv[arg]; + if (*opt != '-') { + + fprintf (stderr, "Invalid option %s.\n", opt); + Usage (); + } + + parm = NULL; + parmBuf[0] = '\0'; + + while (arg < argc - 1) { + + if (argv[arg + 1][0] == '-') + break; + + if (parm) + strcat (parmBuf, " "); + + ++arg; + parm = parmBuf; + strcat (parmBuf, argv[arg]); + } + + ParseOption (opt + 1, parm, 1); + } +} + +static void DoAliasing (int fd) +{ + int bytes; + int origBytes; + char buf[IP_MAXPACKET]; + struct sockaddr_in addr; + int wrote; + 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 (verbose) { + +/* + * Print packet direction and protocol type. + */ + + if (addr.sin_addr.s_addr == INADDR_ANY) + printf ("Out "); + else + printf ("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 ("[?] "); + break; + } +/* + * Print addresses. + */ + PrintPacket (ip); + } + + if (addr.sin_addr.s_addr == INADDR_ANY) { +/* + * Outgoing packets. Do aliasing. + */ + PacketAliasOut (buf, IP_MAXPACKET); + } + else { +/* + * Incoming packets may have ip checksum zeroed + * when read from divert socket. Re-calculate it. + */ + if (ip->ip_sum == 0) + ip->ip_sum = in_cksum_hdr (ip); +/* + * Do aliasing. + */ + PacketAliasIn (buf, IP_MAXPACKET); + } +/* + * Length might have changed during aliasing. + */ + bytes = ntohs (ip->ip_len); +/* + * Update alias overhead size for outgoing packets. + */ + if (addr.sin_addr.s_addr == INADDR_ANY && + 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 (addr.sin_addr.s_addr == INADDR_ANY && + ifMTU != -1) + SendNeedFragIcmp (icmpSock, + (struct ip*) buf, + ifMTU - aliasOverhead); + } + else { + + 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) + return; + + if (verbose && ifMsg.ifm_index == ifIndex) + printf ("Interface address has changed.\n"); + + if (ifMsg.ifm_index == ifIndex) + assignAliasAddr = 1; +} + +static void PrintPacket (struct ip* ip) +{ + struct tcphdr* tcphdr; + + if (ip->ip_p == IPPROTO_TCP) + tcphdr = (struct tcphdr*) ((char*) ip + (ip->ip_hl << 2)); + else + tcphdr = NULL; + + printf ("%s", inet_ntoa (ip->ip_src)); + if (tcphdr) + printf (":%d", ntohs (tcphdr->th_sport)); + + printf (" -> "); + printf ("%s", inet_ntoa (ip->ip_dst)); + if (tcphdr) + printf (":%d", ntohs (tcphdr->th_dport)); +} + +static void SetAliasAddressFromIfName (char* ifName) +{ + struct ifconf cf; + struct ifreq buf[32]; + char msg[80]; + struct ifreq* ifPtr; + int extra; + int helperSock; + int bytes; + struct sockaddr_in* addr; + int found; + struct ifreq req; + char last[10]; +/* + * Create a dummy socket to access interface information. + */ + helperSock = socket (AF_INET, SOCK_DGRAM, 0); + if (helperSock == -1) { + + Quit ("Failed to create helper socket."); + exit (1); + } + + cf.ifc_len = sizeof (buf); + cf.ifc_req = buf; +/* + * Get interface data. + */ + if (ioctl (helperSock, SIOCGIFCONF, &cf) == -1) { + + Quit ("Ioctl SIOCGIFCONF failed."); + exit (1); + } + + ifIndex = 0; + ifPtr = buf; + bytes = cf.ifc_len; + found = 0; + last[0] = '\0'; +/* + * Loop through interfaces until one with + * given name is found. This is done to + * find correct interface index for routing + * message processing. + */ + while (bytes) { + + if (ifPtr->ifr_addr.sa_family == AF_INET && + !strcmp (ifPtr->ifr_name, ifName)) { + + found = 1; + break; + } + + if (strcmp (last, ifPtr->ifr_name)) { + + strcpy (last, ifPtr->ifr_name); + ++ifIndex; + } + + extra = ifPtr->ifr_addr.sa_len - sizeof (struct sockaddr); + + ifPtr++; + ifPtr = (struct ifreq*) ((char*) ifPtr + extra); + bytes -= sizeof (struct ifreq) + extra; + } + + if (!found) { + + close (helperSock); + sprintf (msg, "Unknown interface name %s.\n", ifName); + Quit (msg); + } +/* + * Get MTU size. + */ + strcpy (req.ifr_name, ifName); + + if (ioctl (helperSock, SIOCGIFMTU, &req) == -1) + Quit ("Cannot get interface mtu size."); + + ifMTU = req.ifr_mtu; +/* + * Get interface address. + */ + if (ioctl (helperSock, SIOCGIFADDR, &req) == -1) + Quit ("Cannot get interface address."); + + addr = (struct sockaddr_in*) &req.ifr_addr; + SetPacketAliasAddress (addr->sin_addr); + syslog (LOG_INFO, "Aliasing to %s, mtu %d bytes", + inet_ntoa (addr->sin_addr), + ifMTU); + + close (helperSock); +} + +void Quit (char* msg) +{ + Warn (msg); + exit (1); +} + +void Warn (char* msg) +{ + if (background) + syslog (LOG_ALERT, "%s (%m)", msg); + else + perror (msg); +} + +static void RefreshAddr () +{ + signal (SIGHUP, RefreshAddr); + if (ifName) + assignAliasAddr = 1; +} + +static void InitiateShutdown () +{ +/* + * Start timer to allow kernel gracefully + * shutdown existing connections when system + * is shut down. + */ + signal (SIGALRM, Shutdown); + alarm (10); +} + +static void Shutdown () +{ + running = 0; +} + +/* + * Different options recognized by this program. + */ + +enum Option { + + PacketAliasOption, + Verbose, + InPort, + OutPort, + Port, + AliasAddress, + InterfaceName, + PermanentLink, + RedirectPort, + RedirectAddress, + ConfigFile, + DynamicMode +}; + +enum Param { + + YesNo, + Numeric, + String, + None, + Address, + Service +}; + +/* + * Option information structure (used by ParseOption). + */ + +struct OptionInfo { + + enum Option type; + int packetAliasOpt; + enum Param parm; + char* parmDescription; + char* description; + char* name; + 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_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" }, + + { InterfaceName, + 0, + String, + "network_if_name", + "take aliasing address from interface", + "interface", + "n" }, + + { PermanentLink, + 0, + String, + "tcp|udp src:port dst:port alias", + "define permanent link for incoming connection", + "permanent_link", + NULL }, + + { RedirectPort, + 0, + String, + "tcp|udp local_addr:local_port [public_addr:]public_port" + " [remote_addr[:remote_port]]", + "redirect a port for incoming traffic", + "redirect_port", + 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" } +}; + +static void ParseOption (char* option, char* parms, int cmdLine) +{ + int i; + struct OptionInfo* info; + int yesNoValue; + int aliasValue; + int numValue; + char* strValue; + struct in_addr addrValue; + int max; + char* end; +/* + * 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) { + + fprintf (stderr, "Unknown option %s.\n", option); + Usage (); + } + + 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 { + + fprintf (stderr, "%s needs yes/no parameter.\n", + option); + exit (1); + } + break; + + case Service: + if (!parms) { + + fprintf (stderr, "%s needs service name or " + "port number parameter.\n", + option); + exit (1); + } + + numValue = StrToPort (parms, "divert"); + break; + + case Numeric: + if (parms) + numValue = strtol (parms, &end, 10); + else + end = parms; + + if (end == parms) { + + fprintf (stderr, "%s needs numeric parameter.\n", + option); + exit (1); + } + break; + + case String: + strValue = parms; + if (!strValue) { + + fprintf (stderr, "%s needs parameter.\n", + option); + exit (1); + } + break; + + case None: + if (parms) { + + fprintf (stderr, "%s does not take parameters.\n", + option); + exit (1); + } + break; + + case Address: + if (!parms) { + + fprintf (stderr, "%s needs address/host parameter.\n", + option); + exit (1); + } + + StrToAddr (parms, &addrValue); + break; + } + + switch (info->type) { + case PacketAliasOption: + + aliasValue = yesNoValue ? info->packetAliasOpt : 0; + SetPacketAliasMode (aliasValue, info->packetAliasOpt); + break; + + case Verbose: + verbose = yesNoValue; + break; + + case DynamicMode: + dynamicMode = yesNoValue; + break; + + case InPort: + inPort = numValue; + break; + + case OutPort: + outPort = numValue; + break; + + case Port: + inOutPort = numValue; + break; + + case AliasAddress: + memcpy (&aliasAddr, &addrValue, sizeof (struct in_addr)); + break; + + case PermanentLink: + SetupPermanentLink (strValue); + break; + + case RedirectPort: + SetupPortRedirect (strValue); + break; + + case RedirectAddress: + SetupAddressRedirect (strValue); + break; + + case InterfaceName: + if (ifName) + free (ifName); + + ifName = strdup (strValue); + assignAliasAddr = 1; + break; + + case ConfigFile: + ReadConfigFile (strValue); + break; + } +} + +void ReadConfigFile (char* fileName) +{ + FILE* file; + char buf[128]; + char* ptr; + char* option; + + file = fopen (fileName, "r"); + if (!file) { + + sprintf (buf, "Cannot open config file %s.\n", fileName); + Quit (buf); + } + + while (fgets (buf, sizeof (buf), file)) { + + ptr = strchr (buf, '\n'); + if (!ptr) { + + fprintf (stderr, "config line too link: %s\n", buf); + exit (1); + } + + *ptr = '\0'; + if (buf[0] == '#') + continue; + + ptr = buf; +/* + * Skip white space at beginning of line. + */ + while (*ptr && isspace (*ptr)) + ++ptr; + + if (*ptr == '\0') + continue; +/* + * 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, 0); + } + + 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 SetupPermanentLink (char* parms) +{ + char buf[128]; + char* ptr; + struct in_addr srcAddr; + struct in_addr dstAddr; + int srcPort; + int dstPort; + int aliasPort; + int proto; + char* protoName; + + strcpy (buf, parms); +/* + * Extract protocol. + */ + protoName = strtok (buf, " \t"); + if (!protoName) { + + fprintf (stderr, "permanent_link: missing protocol.\n"); + exit (1); + } + + proto = StrToProto (protoName); +/* + * Extract source address. + */ + ptr = strtok (NULL, " \t"); + if (!ptr) { + + fprintf (stderr, "permanent_link: missing src address.\n"); + exit (1); + } + + srcPort = StrToAddrAndPort (ptr, &srcAddr, protoName); +/* + * Extract destination address. + */ + ptr = strtok (NULL, " \t"); + if (!ptr) { + + fprintf (stderr, "permanent_link: missing dst address.\n"); + exit (1); + } + + dstPort = StrToAddrAndPort (ptr, &dstAddr, protoName); +/* + * Export alias port. + */ + ptr = strtok (NULL, " \t"); + if (!ptr) { + + fprintf (stderr, "permanent_link: missing alias port.\n"); + exit (1); + } + + aliasPort = StrToPort (ptr, protoName); + + PacketAliasPermanentLink (srcAddr, + srcPort, + dstAddr, + dstPort, + aliasPort, + proto); +} + +void SetupPortRedirect (char* parms) +{ + char buf[128]; + char* ptr; + struct in_addr localAddr; + struct in_addr publicAddr; + struct in_addr remoteAddr; + int localPort; + int publicPort; + int remotePort; + int proto; + char* protoName; + char* separator; + + strcpy (buf, parms); +/* + * Extract protocol. + */ + protoName = strtok (buf, " \t"); + if (!protoName) { + + fprintf (stderr, "redirect_port: missing protocol.\n"); + exit (1); + } + + proto = StrToProto (protoName); +/* + * Extract local address. + */ + ptr = strtok (NULL, " \t"); + if (!ptr) { + + fprintf (stderr, "redirect_port: missing local address.\n"); + exit (1); + } + + localPort = StrToAddrAndPort (ptr, &localAddr, protoName); +/* + * Extract public port and optinally address. + */ + ptr = strtok (NULL, " \t"); + if (!ptr) { + + fprintf (stderr, "redirect_port: missing public port.\n"); + exit (1); + } + + separator = strchr (ptr, ':'); + if (separator) + publicPort = StrToAddrAndPort (ptr, &publicAddr, protoName); + else { + + publicAddr.s_addr = INADDR_ANY; + publicPort = StrToPort (ptr, protoName); + } + +/* + * Extract remote address and optionally port. + */ + ptr = strtok (NULL, " \t"); + if (ptr) { + + + separator = strchr (ptr, ':'); + if (separator) + remotePort = StrToAddrAndPort (ptr, + &remoteAddr, + protoName); + else { + + remotePort = 0; + StrToAddr (ptr, &remoteAddr); + } + } + else { + + remotePort = 0; + remoteAddr.s_addr = INADDR_ANY; + } + + PacketAliasRedirectPort (localAddr, + localPort, + remoteAddr, + remotePort, + publicAddr, + publicPort, + proto); +} + +void SetupAddressRedirect (char* parms) +{ + char buf[128]; + char* ptr; + struct in_addr localAddr; + struct in_addr publicAddr; + + strcpy (buf, parms); +/* + * Extract local address. + */ + ptr = strtok (buf, " \t"); + if (!ptr) { + + fprintf (stderr, "redirect_address: missing local address.\n"); + exit (1); + } + + StrToAddr (ptr, &localAddr); +/* + * Extract public address. + */ + ptr = strtok (NULL, " \t"); + if (!ptr) { + + fprintf (stderr, "redirect_address: missing public address.\n"); + exit (1); + } + + StrToAddr (ptr, &publicAddr); + PacketAliasRedirectAddr (localAddr, publicAddr); +} + +void StrToAddr (char* str, struct in_addr* addr) +{ + struct hostent* hp; + + if (inet_aton (str, addr)) + return; + + hp = gethostbyname (str); + if (!hp) { + + fprintf (stderr, "Unknown host %s.\n", str); + exit (1); + } + + memcpy (addr, hp->h_addr, sizeof (struct in_addr)); +} + +int StrToPort (char* str, char* proto) +{ + int port; + struct servent* sp; + char* end; + + port = strtol (str, &end, 10); + if (end != str) + return htons (port); + + sp = getservbyname (str, proto); + if (!sp) { + + fprintf (stderr, "Unknown service %s/%s.\n", + str, proto); + exit (1); + } + + return sp->s_port; +} + +int StrToProto (char* str) +{ + if (!strcmp (str, "tcp")) + return IPPROTO_TCP; + + if (!strcmp (str, "udp")) + return IPPROTO_UDP; + + fprintf (stderr, "Unknown protocol %s. Expected tcp or udp.\n", str); + exit (1); +} + +int StrToAddrAndPort (char* str, struct in_addr* addr, char* proto) +{ + char* ptr; + + ptr = strchr (str, ':'); + if (!ptr) { + + fprintf (stderr, "%s is missing port number.\n", str); + exit (1); + } + + *ptr = '\0'; + ++ptr; + + StrToAddr (str, addr); + return StrToPort (ptr, proto); +} + diff --git a/usr.sbin/natd/natd.h b/usr.sbin/natd/natd.h new file mode 100644 index 0000000..d398241 --- /dev/null +++ b/usr.sbin/natd/natd.h @@ -0,0 +1,5 @@ +#define PIDFILE "/var/run/natd.pid" + +extern void Quit (char* msg); +extern void Warn (char* msg); +extern int SendNeedFragIcmp (int sock, struct ip* failedDgram, int mtu); diff --git a/usr.sbin/natd/samples/natd.cf.sample b/usr.sbin/natd/samples/natd.cf.sample new file mode 100644 index 0000000..46eee91 --- /dev/null +++ b/usr.sbin/natd/samples/natd.cf.sample @@ -0,0 +1,52 @@ +# +# Configuration file for natd. +# +# +# Logging to /var/log +# +log no +# +# Incoming connections. +# +deny_incoming no +# +# Use sockets to avoid port clashes. +# +use_sockets no +# +# Avoid port changes if possible. Makes rlogin work +# in most cases. +# +same_port yes +# +# Verbose mode. Enables dumping of packets and disables +# forking to background. +# +verbose no +# +# Divert port. Can be a name in /etc/services or numeric value. +# +port 32000 +# +# Interface name or address being aliased. Either one, +# not both is required. +# +# alias_address 192.168.0.1 +interface ep0 +# +# Alias unregistered addresses or all addresses. +# +unregistered_only no +# +# Configure permanent links. If you use host names instead +# of addresses here, be sure that name server works BEFORE +# natd is up - this is usually not the case. So either use +# numeric addresses or hosts that are in /etc/hosts. +# +# Map connections coming to port 30000 to telnet in my_private_host. +# Remember to allow the connection /etc/rc.firewall also. +#permanent_link tcp my_private_host:telnet 0.0.0.0:0 30000 +# +# Map connections coming from host.xyz.com to port 30001 to +# telnet in another_host. +#permanent_link tcp another_host:telnet host.xyz.com:0 30001 diff --git a/usr.sbin/natd/samples/natd.test b/usr.sbin/natd/samples/natd.test new file mode 100644 index 0000000..cfdbd15 --- /dev/null +++ b/usr.sbin/natd/samples/natd.test @@ -0,0 +1,14 @@ +#!/bin/sh + + if [ $# != 1 ] + then + echo "usage: natd.test ifname" + exit 1 + fi + + ipfw flush + ipfw add divert 32000 ip from any to any via $1 + ipfw add pass ip from any to any + + ./natd -port 32000 -interface $1 -verbose + -- cgit v1.1