diff options
Diffstat (limited to 'sbin/natd')
-rw-r--r-- | sbin/natd/HISTORY | 146 | ||||
-rw-r--r-- | sbin/natd/Makefile | 12 | ||||
-rw-r--r-- | sbin/natd/README | 50 | ||||
-rw-r--r-- | sbin/natd/icmp.c | 127 | ||||
-rw-r--r-- | sbin/natd/natd.8 | 606 | ||||
-rw-r--r-- | sbin/natd/natd.c | 1689 | ||||
-rw-r--r-- | sbin/natd/natd.h | 24 | ||||
-rw-r--r-- | sbin/natd/samples/natd.cf.sample | 92 | ||||
-rw-r--r-- | sbin/natd/samples/natd.test | 14 |
9 files changed, 2760 insertions, 0 deletions
diff --git a/sbin/natd/HISTORY b/sbin/natd/HISTORY new file mode 100644 index 0000000..f929e80 --- /dev/null +++ b/sbin/natd/HISTORY @@ -0,0 +1,146 @@ +* Version 0.1 + + Initial version of natd. + +* Version 0.2 + + - Alias address can now be set by giving interface name with + new (-n) command-line option. + + - New Makefile based on bsd.prog.mk. + + - Error messages are written to syslog + after natd has become a daemon. + +* Version 1.0 + + - Support for using only single socket (-p option) + +* Version 1.1 + + - -a option now understands a hostname also. + - -a option no longer dumps core. + - Packet aliasing software upgraded to v. 1.9 + - added long option names (like -address) + +* Version 1.2 + + - Fixed core dump with -port option. + - Added -Wall to CFLAGS and some headers added to natd.c + to get clean compile by Brian Somers [brian@awfulhak.org]. + +* Version 1.3 + + - Aliasing address initialization is delayed until first + packet arrives. This allows natd to start up before + interface address is set. + - SIGTERM is now catched to allow kernel to close + existing connections when system is shutting down. + - SIGHUP is now catched to allow natd to refresh aliasing + address from interface, which might be useful to tun devices. + +* Version 1.4 + + - Changed command line options to be compatible with + command names used in ppp+packetAlias package (which is the + original application using aliasing routines). + + The options which map directly to packet aliasing options are: + + -unregistered_only [yes|no] + -log [yes|no] + -deny_incoming [yes|no] + -use_sockets [yes|no] + -same_ports [yes|no] + + The short option names are the same as in previous + releases. + + - Command line parser rewritten to provide more flexible + way to support new packet aliasing options. + + - Support for natd.cf configuration file has been added. + + - SIGHUP no longer causes problems when running without + interface name option. + + - When using -interface command line option, routing socket + is optionally listened for interface address changes. This + mode is activated by -dynamic option. + + - Directory tree reorganized, alias package is now a library. + + - Manual page written by Brian Somers <brian@awfulhak.org> added. + - README file updated. + +* Version 1.5 + + - Support for sending ICMP 'need fragmentation' messages + when packet size exceeds mtu size of outgoing network interface. + + - ipfw rule example in manual page fixed. + +* Version 1.6 + + - Upgrade to new packet aliasing engine (2.1) + - redirect_port and redirect_address configuration + parameters added. + - It is no longer necessary to quote complex parameter values + in command line. + - Manual page fixed (same_port -> same_ports). + +* Version 1.7 + + - A bug in command-line parsing fixed (it appeared due + to changes made in 1.6). + +* Version 1.8 + + - Fixed problems with -dynamic option. + - Added /var/run/natd.pid + +* Version 1.9 + + - Changes to manual page by + Brian Somers <brian@awfulhak.org> integrated. + - Checksum for incoming packets is always recalculated + for FreeBSD 2.2 and never recalculated for newer + versions. This should fix the problem with wrong + checksum of fragmented packets. + - Buffer space problem found by Sergio Lenzi <lenzi@bsi.com.br> + fixed. Natd now waits with select(2) for buffer space + to become available if write fails. + - Packet aliasing library upgraded to 2.2. + +* Version 1.10 + + - Ignored incoming packets are now dropped when + deny_incoming option is set to yes. + - Packet aliasing library upgraded to 2.4. + +* Version 1.11 + + - Code cleanup work done in FreeBSD-current development merged. + - Port numbers are now unsigned as they should always have been. + +* Version 1.12 + + - Typos in comment fixed. Copyright message added to + source & header files that were missing it. + - A small patch to libalias to make static NAT work correctly. + +* Version 2.0 + + - Upgrade to libalias 3.0 which gives: + - Transparent proxy support. + - permanent_link is now obsolete, use redirect_port instead. + - Drop support for early FreeBSD 2.2 versions + - If separate input & output sockets are being used + use them to find out packet direction instead of + normal mechanism. This can be handy in complex environments + with multiple interfaces. + - libalias is no longer part of this distribution. + - New sample configuration file + from Ted Mittelstaedt <tedm@portsoft.com>. + - PPTP redirect support by Dru Nelson <dnelson@redwoodsoft.com> added. + - Logging enhancements from Martin Machacek <mm@i.cz> added. diff --git a/sbin/natd/Makefile b/sbin/natd/Makefile new file mode 100644 index 0000000..ba6730e --- /dev/null +++ b/sbin/natd/Makefile @@ -0,0 +1,12 @@ +# $FreeBSD$ + +MAINTAINER = ru@FreeBSD.org +MAINTAINER += ari@suutari.iki.fi +PROG = natd +SRCS = natd.c icmp.c +WARNS= 0 +LDADD = -lalias +DPADD = ${LIBALIAS} +MAN = natd.8 + +.include <bsd.prog.mk> diff --git a/sbin/natd/README b/sbin/natd/README new file mode 100644 index 0000000..8d1209c --- /dev/null +++ b/sbin/natd/README @@ -0,0 +1,50 @@ +# $FreeBSD$ + + A Network Address Translation Daemon for FreeBSD + + +1. WHAT IS NATD ? + + This is a simple daemon based on FreeBSD divert sockets + which performs network address translation (or masquerading) + for IP packets (see related RFCs 1631 and 1918). + It is based on packet aliasing package (see README.alias) + written by Charles Mott (cmott@scientech.com). + + This package works with any network interface (doesn't have + to be ppp). I run it on a computer having two ethernet cards, + one connected to internet and the other one to local network. + +2. GETTING IT RUNNING + + 1) Get FreeBSD 2.2 - I think the divert sockets are + not available on earlier versions, + + 2) Compile this software by executing "make". + + 3) Install the software by executing "make install". + + 4) See man natd for further instructions. + +3. FTP SITES FOR NATD + + This package is available at ftp://ftp.suutari.iki.fi/pub/natd. + +4. AUTHORS + + This program is the result of the efforts of many people + at different times: + + Archie Cobbs <archie@whistle.com> Divert sockets + Charles Mott <cmott@scientech.com> Packet aliasing engine + Eivind Eklund <eivind@dimaga.com> Packet aliasing engine + Ari Suutari <suutari@iki.fi> Natd + Brian Somers <brian@awfulhak.org> Manual page, glue and + bunch of good ideas. + + Happy Networking - comments and fixes are welcome! + + Ari S. (suutari@iki.fi) + + + diff --git a/sbin/natd/icmp.c b/sbin/natd/icmp.c new file mode 100644 index 0000000..176adde --- /dev/null +++ b/sbin/natd/icmp.c @@ -0,0 +1,127 @@ +/* + * 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 (icmp.c) freely. + * + * Ari Suutari <suutari@iki.fi> + * + * $FreeBSD$ + */ + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <errno.h> +#include <signal.h> + +#include <netdb.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_icmp.h> +#include <machine/in_cksum.h> + +#include <alias.h> + +#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 = PacketAliasInternetChecksum ((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/sbin/natd/natd.8 b/sbin/natd/natd.8 new file mode 100644 index 0000000..5ade022 --- /dev/null +++ b/sbin/natd/natd.8 @@ -0,0 +1,606 @@ +.\" $FreeBSD$ +.Dd June 27, 2000 +.Dt NATD 8 +.Os +.Sh NAME +.Nm natd +.Nd Network Address Translation daemon +.Sh SYNOPSIS +.Nm +.Bk -words +.Op Fl unregistered_only | u +.Op Fl log | l +.Op Fl proxy_only +.Op Fl reverse +.Op Fl deny_incoming | d +.Op Fl use_sockets | s +.Op Fl same_ports | m +.Op Fl verbose | v +.Op Fl dynamic +.Op Fl in_port | i Ar port +.Op Fl out_port | o Ar port +.Op Fl port | p Ar port +.Op Fl alias_address | a Ar address +.Op Fl target_address | t Ar address +.Op Fl interface | n Ar interface +.Op Fl proxy_rule Ar proxyspec +.Op Fl redirect_port Ar linkspec +.Op Fl redirect_proto Ar linkspec +.Op Fl redirect_address Ar linkspec +.Op Fl config | f Ar configfile +.Op Fl log_denied +.Op Fl log_facility Ar facility_name +.Op Fl punch_fw Ar firewall_range +.Op Fl log_ipfw_denied +.Ek +.Sh DESCRIPTION +This program provides a Network Address Translation facility for use +with +.Xr divert 4 +sockets under +.Fx . +It is intended for use with NICs - if you want to do NAT on a PPP link, +use the +.Fl nat +switch to +.Xr ppp 8 . +.Pp +The +.Nm +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 +It 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 +.Nm +is started. +.It Fl deny_incoming | d +Do not pass incoming packets that have no +entry in the internal translation table. +.Pp +If this option is not used, then such a packet will be altered +using the rules in +.Fl target_address +below, and the entry will be made in the internal translation table. +.It Fl log_denied +Log denied incoming packets via +.Xr syslog 3 +(see also +.Fl log_facility ) . +.It Fl log_facility Ar facility_name +Use specified log facility when logging information via +.Xr syslog 3 . +Argument +.Ar facility_name +is one of the keywords specified in +.Xr syslog.conf 5 . +.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 +Do not call +.Xr daemon 3 +on startup. +Instead, stay attached to the controlling 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 +.Em 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 proto Xo +.Ar targetIP Ns : Ns Xo +.Ar targetPORT Ns Op - Ns Ar targetPORT Xc +.Op Ar aliasIP Ns : Ns Xo +.Ar aliasPORT Ns Op - Ns Ar aliasPORT Xc +.Oo Ar remoteIP Ns Oo : Ns +.Ar remotePORT Ns Op - Ns Ar remotePORT +.Oc Oc +.Xc +Redirect incoming connections arriving to given port(s) to another host +and port(s). +Argument +.Ar proto +is either +.Ar tcp +or +.Ar udp , +.Ar targetIP +is the desired target IP number, +.Ar targetPORT +is the desired target port number or range, +.Ar aliasPORT +is the requested port number or range, and +.Ar aliasIP +is the aliasing address. +Arguments +.Ar remoteIP +and +.Ar remotePORT +can be used to specify the connection more accurately if necessary. +The +.Ar targetPORT +range and +.Ar aliasPORT +range need not be the same numerically, but must have the same size. +If +.Ar remotePORT +is not specified, it is assumed to be all ports. +If +.Ar remotePORT +is specified, it must match the size of +.Ar targetPORT , +or be 0 (all ports). +For example, the argument +.Pp +.Dl Ar tcp inside1:telnet 6666 +.Pp +means that incoming TCP packets destined for port 6666 on this machine +will be sent to the telnet port on the inside1 machine. +.Pp +.Dl Ar tcp inside2:2300-2399 3300-3399 +.Pp +will redirect incoming connections on ports 3300-3399 to host +inside2, ports 2300-2399. +The mapping is 1:1 meaning port 3300 maps to 2300, 3301 maps to 2301, etc. +.It Fl redirect_proto Ar proto localIP Oo +.Ar publicIP Op Ar remoteIP +.Oc +Redirect incoming IP packets of protocol +.Ar proto +(see +.Xr protocols 5 ) +destined for +.Ar publicIP +address to a +.Ar localIP +address and vice versa. +.Pp +If +.Ar publicIP +is not specified, then the default aliasing address is used. +If +.Ar remoteIP +is specified, then only packets coming from/to +.Ar remoteIP +will match the rule. +.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 +.Em 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: +.Pp +.Dl Ar redirect_address 10.0.0.8 0.0.0.0 +.Pp +The above command would redirect all incoming traffic +to machine 10.0.0.8. +.Pp +If several address aliases specify the same public address +as follows +.Bd -literal -offset indent +.Ar redirect_address 192.168.0.2 public_addr +.Ar redirect_address 192.168.0.3 public_addr +.Ar redirect_address 192.168.0.4 public_addr +.Ed +.Pp +the incoming traffic will be directed to the last +translated local address (192.168.0.4), but outgoing +traffic from the first two addresses will still be aliased +to appear from the specified +.Ar public_addr . +.It Fl redirect_port Ar proto Xo +.Ar targetIP Ns : Ns Xo +.Ar targetPORT Ns Oo , Ns +.Ar targetIP Ns : Ns Xo +.Ar targetPORT Ns Oo , Ns +.Ar ...\& +.Oc Oc +.Xc +.Xc +.Op Ar aliasIP Ns : Ns Xo +.Ar aliasPORT +.Xc +.Oo Ar remoteIP Ns +.Op : Ns Ar remotePORT +.Oc +.Xc +.It Fl redirect_address Xo +.Ar localIP Ns Oo , Ns +.Ar localIP Ns Oo , Ns +.Ar ...\& +.Oc Oc +.Ar publicIP +.Xc +These forms of +.Fl redirect_port +and +.Fl redirect_address +are used to transparently offload network load on a single server and +distribute the load across a pool of servers. +This function is known as +.Em LSNAT +(RFC 2391). +For example, the argument +.Pp +.Dl Ar tcp www1:http,www2:http,www3:http www:http +.Pp +means that incoming HTTP requests for host www will be transparently +redirected to one of the www1, www2 or www3, where a host is selected +simply on a round-robin basis, without regard to load on the net. +.It Fl dynamic +If the +.Fl n +or +.Fl interface +option is used, +.Nm +will monitor the routing socket for alterations to the +.Ar interface +passed. +If the interface's IP number is changed, +.Nm +will dynamically alter its concept of the alias address. +.It Fl in_port | i Ar port +Read from and write to +.Xr divert 4 +port +.Ar port , +treating all packets as +.Dq incoming . +.It Fl out_port | o Ar port +Read from and write to +.Xr divert 4 +port +.Ar port , +treating all packets as +.Dq outgoing . +.It Fl port | p Ar port +Read from and write to +.Xr divert 4 +port +.Ar port , +distinguishing packets as +.Dq incoming +or +.Dq outgoing +using the rules specified in +.Xr divert 4 . +If +.Ar port +is not numeric, it is searched for in the +.Xr services 5 +database. +If this option is not specified, the divert port named +.Ar natd +will be used as a default. +.It Fl alias_address | a Ar address +Use +.Ar address +as the aliasing address. +If this option is not specified, the +.Fl interface +option must be used. +The specified address is usually the address assigned to the +.Dq public +network interface. +.Pp +All data passing +.Em out +will be rewritten with a source address equal to +.Ar address . +All data coming +.Em in +will be checked to see if it matches any already-aliased outgoing +connection. +If it does, the packet is altered accordingly. +If not, all +.Fl redirect_port , +.Fl redirect_proto +and +.Fl redirect_address +assignments are checked and actioned. +If no other action can be made and if +.Fl deny_incoming +is not specified, the packet is delivered to the local machine +using the rules specified in +.Fl target_address +option below. +.It Fl t | target_address Ar address +Set the target address. +When an incoming packet not associated with any pre-existing link +arrives at the host machine, it will be sent to the specified +.Ar address . +.Pp +The target address may be set to +.Ar 255.255.255.255 , +in which case all new incoming packets go to the alias address set by +.Fl alias_address +or +.Fl interface . +.Pp +If this option is not used, or called with the argument +.Ar 0.0.0.0 , +then all new incoming packets go to the address specified in +the packet. +This allows external machines to talk directly to internal machines if +they can route packets to the machine in question. +.It Fl interface | n Ar interface +Use +.Ar interface +to determine the aliasing address. +If there is a possibility that the IP number associated with +.Ar interface +may change, the +.Fl dynamic +option should also be used. +If this option is not specified, the +.Fl alias_address +option must be used. +.Pp +The specified +.Ar interface +is usually the +.Dq public +(or +.Dq external ) +network interface. +.It Fl config | f Ar file +Read configuration from +.Ar file . +A +.Ar file +should contain a list of options, one per line, in the same form +as the long form of the above command line options. +For example, the line +.Pp +.Dl alias_address 158.152.17.1 +.Pp +would specify an alias address of 158.152.17.1. +Options that do not take an argument are specified with an argument of +.Ar yes +or +.Ar no +in the configuration file. +For example, the line +.Pp +.Dl log yes +.Pp +is synonymous with +.Fl log . +.Pp +Trailing spaces and empty lines are ignored. +A +.Ql \&# +sign will mark the rest of the line as a comment. +.It Fl reverse +This option makes +.Nm +reverse the way it handles +.Dq incoming +and +.Dq outgoing +packets, allowing it to operate on the +.Dq internal +network interface rather than the +.Dq external +one. +.Pp +This can be useful in some transparent proxying situations +when outgoing traffic is redirected to the local machine +and +.Nm +is running on the internal interface (it usually runs on the +external interface). +.It Fl proxy_only +Force +.Nm +to perform transparent proxying only. +Normal address translation is not performed. +.It Fl proxy_rule Xo +.Op Ar type encode_ip_hdr | encode_tcp_stream +.Ar port xxxx +.Ar server a.b.c.d:yyyy +.Xc +Enable transparent proxying. +Outgoing TCP packets with the given port going through this +host to any other host are redirected to the given server and port. +Optionally, the original target address can be encoded into the packet. +Use +.Ar encode_ip_hdr +to put this information into the IP option field or +.Ar encode_tcp_stream +to inject the data into the beginning of the TCP stream. +.It Fl punch_fw Xo +.Ar basenumber Ns : Ns Ar count +.Xc +This option directs +.Nm +to +.Dq punch holes +in an +.Xr ipfirewall 4 +based firewall for FTP/IRC DCC connections. +This is done dynamically by installing temporary firewall rules which +allow a particular connection (and only that connection) to go through +the firewall. +The rules are removed once the corresponding connection terminates. +.Pp +A maximum of +.Ar count +rules starting from the rule number +.Ar basenumber +will be used for punching firewall holes. +The range will be cleared for all rules on startup. +.It Fl log_ipfw_denied +Log when a packet can not be re-injected because an +.Xr ipfw 8 +rule blocks it. +This is the default with +.Fl verbose . +.El +.Sh RUNNING NATD +The following steps are necessary before attempting to run +.Nm : +.Bl -enum +.It +Build a custom kernel with the following options: +.Bd -literal -offset indent +options IPFIREWALL +options IPDIVERT +.Ed +.Pp +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 +.Pp +.Dl gateway_enable=YES +.Pp +in the +.Pa /etc/rc.conf +file or using the command +.Pp +.Dl "sysctl net.inet.ip.forwarding=1" +.Pp +.It +If you use the +.Fl interface +option, make sure that your interface is already configured. +If, for example, you wish to specify +.Ql tun0 +as your +.Ar interface , +and you are using +.Xr ppp 8 +on that interface, you must make sure that you start +.Nm ppp +prior to starting +.Nm . +.El +.Pp +Running +.Nm +is fairly straight forward. +The line +.Pp +.Dl natd -interface ed0 +.Pp +should suffice in most cases (substituting the correct interface name). +Please check +.Xr rc.conf 5 +on how to configure it to be started automatically during boot. +Once +.Nm +is running, you must ensure that traffic is diverted to +.Nm : +.Bl -enum +.It +You will need to adjust the +.Pa /etc/rc.firewall +script to taste. +If you are not interested in having a firewall, the +following lines will do: +.Bd -literal -offset indent +/sbin/ipfw -f flush +/sbin/ipfw add divert natd all from any to any via ed0 +/sbin/ipfw add pass all from any to any +.Ed +.Pp +The second line depends on your interface (change +.Ql ed0 +as appropriate). +.Pp +You should be aware of the fact that, with these firewall settings, +everyone on your local network can fake his source-address using your +host as gateway. +If there are other hosts on your local network, you are strongly +encouraged to create firewall rules that only allow traffic to and +from trusted hosts. +.Pp +If you specify real firewall rules, it is best to specify line 2 at +the start of the script so that +.Nm +sees all packets before they are dropped by the firewall. +.Pp +After translation by +.Nm , +packets re-enter the firewall at the rule number following the rule number +that caused the diversion (not the next rule if there are several at the +same number). +.It +Enable your firewall by setting +.Pp +.Dl firewall_enable=YES +.Pp +in +.Pa /etc/rc.conf . +This tells the system startup scripts to run the +.Pa /etc/rc.firewall +script. +If you do not wish to reboot now, just run this by hand from the console. +NEVER run this from a remote session unless you put it into the background. +If you do, you will 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 divert 4 , +.Xr protocols 5 , +.Xr rc.conf 5 , +.Xr services 5 , +.Xr syslog.conf 5 , +.Xr ipfw 8 , +.Xr ppp 8 +.Sh AUTHORS +This program is the result of the efforts of many people at different +times: +.Pp +.An Archie Cobbs Aq archie@whistle.com +(divert sockets) +.An Charles Mott Aq cmott@scientech.com +(packet aliasing) +.An Eivind Eklund Aq perhaps@yes.no +(IRC support & misc additions) +.An Ari Suutari Aq suutari@iki.fi +(natd) +.An Dru Nelson Aq dnelson@redwoodsoft.com +(early PPTP support) +.An Brian Somers Aq brian@awfulhak.org +(glue) +.An Ruslan Ermilov Aq ru@FreeBSD.org +(natd, packet aliasing, glue) 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); +} diff --git a/sbin/natd/natd.h b/sbin/natd/natd.h new file mode 100644 index 0000000..ac0dd75 --- /dev/null +++ b/sbin/natd/natd.h @@ -0,0 +1,24 @@ +/* + * 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.h) freely. + * + * Ari Suutari <suutari@iki.fi> + * + * $FreeBSD$ + */ + +#define PIDFILE "/var/run/natd.pid" +#define INPUT 1 +#define OUTPUT 2 +#define DONT_KNOW 3 + +extern void Quit (const char* msg); +extern void Warn (const char* msg); +extern int SendNeedFragIcmp (int sock, struct ip* failedDgram, int mtu); + + diff --git a/sbin/natd/samples/natd.cf.sample b/sbin/natd/samples/natd.cf.sample new file mode 100644 index 0000000..d4dcd71 --- /dev/null +++ b/sbin/natd/samples/natd.cf.sample @@ -0,0 +1,92 @@ +# +# $FreeBSD$ +# +# +# Configuration file for natd. +# +# +# Enable logging to file /var/log/alias.log +# +log no +# +# Incoming connections. Should NEVER be set to "yes" if redirect_port +# or redirect_address statements are activated in this file! +# +# Setting to yes provides additional anti-crack protection +# +deny_incoming no +# +# Use sockets to avoid port clashes. Uses additional system resources, but +# guarantees successful connections when port numbers conflict +# +use_sockets no +# +# Avoid port changes if possible when altering outbound packets. Makes rlogin +# work in most cases. +# +same_ports yes +# +# Verbose mode. Enables dumping of packets and disables +# forking to background. Only set to yes for debugging. +# +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. +# +# Obtain interface name from the command output of "ifconfig -a" +# +# alias_address 192.168.0.1 +interface ep0 +# +# Alias unregistered addresses or all addresses. Set this to yes if +# the inside network is all RFC1918 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. +# +# Note: Current versions of FreeBSD all call /etc/rc.firewall +# BEFORE running named, so if the DNS server and NAT are on the same +# machine, the nameserver won't be up if natd is called from /etc/rc.firewall +# +# Map connections coming to port 30000 to telnet in my_private_host. +# Remember to allow the connection /etc/rc.firewall also. +# +#redirect_port tcp my_private_host:telnet 30000 +# +# Map connections coming from host.xyz.com to port 30001 to +# telnet in another_host. +#redirect_port tcp another_host:telnet 30001 host.xyz.com +# +# Static NAT address mapping: +# +# ipconfig must apply any legal IP numbers that inside hosts +# will be known by to the outside interface. These are sometimes known as +# virtual IP numbers. It's suggested to use the "interface" directive +# instead of the "alias_address" directive to make it more clear what is +# going on. (although both will work) +# +# DNS in this situation can get hairy. For example, an inside host +# named aweb.company.com is located at 192.168.1.56, and needs to be +# accessible through a legal IP number like 198.105.232.1. If both +# 192.168.1.56 and 198.105.232.1 are set up as address records in the DNS +# for aweb.company.com, then external hosts attempting to access +# aweb.company.com may use address 192.168.1.56 which is inaccessible to them. +# +# The obvious solution is to use only a single address for the name, the +# outside address. However, this creates needless traffic through the +# NAT, because inside hosts will go through the NAT to get to the legal +# number, even when the inside number is on the same subnet as they are! +# +# It's probably not a good idea to use DNS names in redirect_address statements +# +#The following mapping points outside address 198.105.232.1 to 192.168.1.56 +#redirect_address 192.168.1.56 198.105.232.1 diff --git a/sbin/natd/samples/natd.test b/sbin/natd/samples/natd.test new file mode 100644 index 0000000..cfdbd15 --- /dev/null +++ b/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 + |