diff --git a/etc/rc.firewall b/etc/rc.firewall index 132df9ca3abd..f5696530e8c0 100644 --- a/etc/rc.firewall +++ b/etc/rc.firewall @@ -129,6 +129,14 @@ case ${firewall_type} in fi ;; esac + case ${firewall_nat_enable} in + [Yy][Ee][Ss]) + if [ -n "${firewall_nat_interface}" ]; then + ${fwcmd} nat 123 config if ${firewall_nat_interface} log + ${fwcmd} add 50 nat 123 ip4 from any to any via ${firewall_nat_interface} + fi + ;; + esac esac ############ diff --git a/sbin/ipfw/ipfw.8 b/sbin/ipfw/ipfw.8 index 391e0a65c2d7..f8740d7655c0 100644 --- a/sbin/ipfw/ipfw.8 +++ b/sbin/ipfw/ipfw.8 @@ -2035,6 +2035,59 @@ diverted to that port. If no socket is bound to the destination port, or if the divert module is not loaded, or if the kernel was not compiled with divert socket support, the packets are dropped. +.Sh NETWORK ADDRESS TRANSLATION (NAT) +The nat configuration command is the following: +.Bd -ragged -offset indent +.Bk -words +.Cm nat +.Ar nat_number +.Cm config +.Ar nat-configuration +.Ek +.Ed +.Pp +. +The following parameters can be configured: +.Bl -tag -width indent +.It Cm ip Ar ip_address +Define an ip address to use for aliasing. +.It Cm if Ar nic +Use ip addres of NIC for aliasing, dynamically changing +it if NIC's ip address change. +.It Cm log +Enable logging on this nat instance. +.It Cm deny_in +Deny any incoming connection from outside world. +.It Cm same_ports +Try to leave the alias port numbers unchanged from +the actual local port numbers. +.It Cm unreg_only +Traffic on the local network not originating from an +unregistered address spaces will be ignored. +.It Cm reset +Reset table of the packet aliasing engine on address change. +.It Cm reverse +Reverse the way libalias handles aliasing. +.It Cm proxy_only +Obey transparent proxy rules only, packet aliasing is not performed. +.El +.Pp +To let the packet continue after being (de)aliased, set the sysctl variable +.Em net.inet.ip.fw.one_pass +to 0. +For more information about aliasing modes, refer to +.Xr libalias 3 +. +See Section +.Sx EXAMPLES +for some examples about nat usage. +.Sh REDIRECT AND LSNAT SUPPORT IN IPFW +Redirect and LSNAT support follow closely the syntax used in +.Xr natd 8 +. +See Section +.Sx EXAMPLES +for some examples on how to do redirect and lsnat. .Sh SYSCTL VARIABLES A set of .Xr sysctl 8 @@ -2423,6 +2476,55 @@ terminates, and your ruleset will be left active. Otherwise, e.g.\& if you cannot access your box, the ruleset will be disabled after the sleep terminates thus restoring the previous situation. +.Ss NAT, REDIRECT AND LSNAT +First redirect all the traffic to nat instance 123: +.Pp +.Dl "ipfw add nat 123 all from any to any" +.Pp +Then to configure nat instance 123 to alias all the outgoing traffic with ip +192.168.0.123, blocking all incoming connections, trying to keep +same ports on both sides, clearing aliasing table on address change +and keeping a log of traffic/link statistics: +.Pp +.Dl "ipfw nat 123 config ip 192.168.0.123 log deny_in reset same_ports" +.Pp +Or to change address of instance 123, aliasing table will be cleared (see +reset option): +.Pp +.Dl "ipfw nat 123 config ip 10.0.0.1" +.Pp +To see configuration of nat instance 123: +.Pp +.Dl "ipfw nat 123 show config" +.Pp +To show logs of all the instances in range 111-999: +.Pp +.Dl "ipfw nat 111-999 show" +.Pp +To see configurations of all instances: +.Pp +.Dl "ipfw nat show config" +.Pp +Or a redirect rule with mixed modes could looks like: +.Pp +.Dl "ipfw nat 123 config redirect_addr 10.0.0.1 10.0.0.66" +.Dl " redirect_port tcp 192.168.0.1:80 500" +.Dl " redirect_proto udp 192.168.1.43 192.168.1.1" +.Dl " redirect_addr 192.168.0.10,192.168.0.11" +.Dl " 10.0.0.100 # LSNAT" +.Dl " redirect_port tcp 192.168.0.1:80,192.168.0.10:22" +.Dl " 500 # LSNAT" +.Pp +or it could be splitted in: +.Pp +.Dl "ipfw nat 1 config redirect_addr 10.0.0.1 10.0.0.66" +.Dl "ipfw nat 2 config redirect_port tcp 192.168.0.1:80 500" +.Dl "ipfw nat 3 config redirect_proto udp 192.168.1.43 192.168.1.1" +.Dl "ipfw nat 4 config redirect_addr 192.168.0.10,192.168.0.11,192.168.0.12" +.Dl " 10.0.0.100" +.Dl "ipfw nat 5 config redirect_port tcp" +.Dl " 192.168.0.1:80,192.168.0.10:22,192.168.0.20:25 500" +.Pp .Sh SEE ALSO .Xr cpp 1 , .Xr m4 1 , @@ -2464,6 +2566,11 @@ API based upon code written by .An Daniel Boulet for BSDI. .Pp +.An -nosplit +In-kernel NAT support written by +.An Paolo Pisati Aq piso@FreeBSD.org +as part of a Summer of Code 2005 project. +.Pp Work on .Xr dummynet 4 traffic shaper supported by Akamba Corp. @@ -2520,3 +2627,10 @@ violations in its implementation. Rule syntax is subject to the command line environment and some patterns may need to be escaped with the backslash character or quoted appropriately. +.Pp +Due to the architecture of +.Xr libalias 3 , +ipfw nat is not compatible with the tcp segmentation offloading +(TSO). Thus, to reliably nat your network traffic, please disable TSO +on your NICs using +.Xr ifconfig 8 . diff --git a/sbin/ipfw/ipfw2.c b/sbin/ipfw/ipfw2.c index 47c4a59005a8..35c579c3ab8e 100644 --- a/sbin/ipfw/ipfw2.c +++ b/sbin/ipfw/ipfw2.c @@ -48,6 +48,7 @@ #include #include +#include #include #include /* def. of struct route */ #include @@ -59,12 +60,14 @@ #include #include #include +#include int do_resolv, /* Would try to resolve all */ do_time, /* Show time stamps */ do_quiet, /* Be quiet in add and flush */ do_pipe, /* this cmd refers to a pipe */ + do_nat, /* Nat configuration. */ do_sort, /* field to sort results (0 = no) */ do_dynamic, /* display dynamic rules */ do_expired, /* display expired dynamic rules */ @@ -247,6 +250,7 @@ enum tokens { TOK_RESET, TOK_UNREACH, TOK_CHECKSTATE, + TOK_NAT, TOK_ALTQ, TOK_LOG, @@ -309,6 +313,18 @@ enum tokens { TOK_DROPTAIL, TOK_PROTO, TOK_WEIGHT, + TOK_IP, + TOK_IF, + TOK_ALOG, + TOK_DENY_INC, + TOK_SAME_PORTS, + TOK_UNREG_ONLY, + TOK_RESET_ADDR, + TOK_ALIAS_REV, + TOK_PROXY_ONLY, + TOK_REDIR_ADDR, + TOK_REDIR_PORT, + TOK_REDIR_PROTO, TOK_IPV6, TOK_FLOWID, @@ -351,6 +367,22 @@ struct _s_x dummynet_params[] = { { NULL, 0 } /* terminator */ }; +struct _s_x nat_params[] = { + { "ip", TOK_IP }, + { "if", TOK_IF }, + { "log", TOK_ALOG }, + { "deny_in", TOK_DENY_INC }, + { "same_ports", TOK_SAME_PORTS }, + { "unreg_only", TOK_UNREG_ONLY }, + { "reset", TOK_RESET_ADDR }, + { "reverse", TOK_ALIAS_REV }, + { "proxy_only", TOK_PROXY_ONLY }, + { "redirect_addr", TOK_REDIR_ADDR }, + { "redirect_port", TOK_REDIR_PORT }, + { "redirect_proto", TOK_REDIR_PROTO }, + { NULL, 0 } /* terminator */ +}; + struct _s_x rule_actions[] = { { "accept", TOK_ACCEPT }, { "pass", TOK_ACCEPT }, @@ -375,6 +407,7 @@ struct _s_x rule_actions[] = { { "unreach", TOK_UNREACH }, { "check-state", TOK_CHECKSTATE }, { "//", TOK_COMMENT }, + { "nat", TOK_NAT }, { NULL, 0 } /* terminator */ }; @@ -493,7 +526,9 @@ do_cmd(int optname, void *optval, uintptr_t optlen) if (optname == IP_FW_GET || optname == IP_DUMMYNET_GET || optname == IP_FW_ADD || optname == IP_FW_TABLE_LIST || - optname == IP_FW_TABLE_GETSIZE) + optname == IP_FW_TABLE_GETSIZE || + optname == IP_FW_NAT_GET_CONFIG || + optname == IP_FW_NAT_GET_LOG) i = getsockopt(s, IPPROTO_IP, optname, optval, (socklen_t *)optlen); else @@ -1567,6 +1602,10 @@ show_ipfw(struct ip_fw *rule, int pcwidth, int bcwidth) tagptr = cmd; break; + case O_NAT: + printf("nat %u", cmd->arg1); + break; + default: printf("** unrecognized action %d len %d ", cmd->opcode, cmd->len); @@ -2629,13 +2668,16 @@ help(void) "add [num] [set N] [prob x] RULE-BODY\n" "{pipe|queue} N config PIPE-BODY\n" "[pipe|queue] {zero|delete|show} [N{,N}]\n" +"nat N config {ip IPADDR|if IFNAME|log|deny_in|same_ports|unreg_only|reset|\n" +" reverse|proxy_only|redirect_addr linkspec|\n" +" redirect_port linkspec|redirect_proto linkspec}\n" "set [disable N... enable N...] | move [rule] X to Y | swap X Y | show\n" "table N {add ip[/bits] [value] | delete ip[/bits] | flush | list}\n" "\n" "RULE-BODY: check-state [PARAMS] | ACTION [PARAMS] ADDR [OPTION_LIST]\n" "ACTION: check-state | allow | count | deny | unreach{,6} CODE |\n" " skipto N | {divert|tee} PORT | forward ADDR |\n" -" pipe N | queue N\n" +" pipe N | queue N | nat N\n" "PARAMS: [log [logamount LOGLIMIT]] [altq QUEUE_NAME]\n" "ADDR: [ MAC dst src ether_type ] \n" " [ ip from IPADDR [ PORT ] to IPADDR [ PORTLIST ] ]\n" @@ -3148,7 +3190,13 @@ delete(int ac, char *av[]) /* Rule number */ while (ac && isdigit(**av)) { i = atoi(*av); av++; ac--; - if (do_pipe) { + if (do_nat) { + exitval = do_cmd(IP_FW_NAT_DEL, &i, sizeof i); + if (exitval) { + exitval = EX_UNAVAILABLE; + warn("rule %u not available", i); + } + } else if (do_pipe) { if (do_pipe == 1) p.pipe_nr = i; else @@ -3197,6 +3245,769 @@ fill_iface(ipfw_insn_if *cmd, char *arg) errx(EX_DATAERR, "bad ip address ``%s''", arg); } +/* + * Search for interface with name "ifn", and fill n accordingly: + * + * n->ip ip address of interface "ifn" + * n->if_name copy of interface name "ifn" + */ +static void +set_addr_dynamic(const char *ifn, struct cfg_nat *n) +{ + 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; + int ifIndex, ifMTU; + + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; + mib[3] = AF_INET; + mib[4] = NET_RT_IFLIST; + mib[5] = 0; +/* + * 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); + + for (i = 1; i < RTA_IFA; i <<= 1) { + if (ifam->ifam_addrs & i) + cp += SA_SIZE((struct sockaddr *)cp); + } + if (((struct sockaddr *)cp)->sa_family == AF_INET) { + sin = (struct sockaddr_in *)cp; + break; + } + } + } + if (sin == NULL) + errx(1, "%s: cannot get interface address", ifn); + + n->ip = sin->sin_addr; + strncpy(n->if_name, ifn, IF_NAMESIZE); + + free(buf); +} + +/* + * XXX - The following functions, macros and definitions come from natd.c: + * it would be better to move them outside natd.c, in a file + * (redirect_support.[ch]?) shared by ipfw and natd, but for now i can live + * with it. + */ + +/* + * 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. + */ + +#define port_range u_long + +#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)) + +static 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)); +} + +static 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 (EX_DATAERR, "%s/%s: unknown service", str, proto); + } + + /* Port range, get the values and sanity check. */ + sscanf (str, "%hu-%hu", &loPort, &hiPort); + SETLOPORT(*portRange, loPort); + SETNUMPORTS(*portRange, 0); /* Error by default */ + if (loPort <= hiPort) + SETNUMPORTS(*portRange, hiPort - loPort + 1); + + if (GETNUMPORTS(*portRange) == 0) + errx (EX_DATAERR, "invalid port range %s", str); + + return 0; +} + +static int +StrToProto (const char* str) +{ + if (!strcmp (str, "tcp")) + return IPPROTO_TCP; + + if (!strcmp (str, "udp")) + return IPPROTO_UDP; + + errx (EX_DATAERR, "unknown protocol %s. Expected tcp or udp", str); +} + +static int +StrToAddrAndPortRange (const char* str, struct in_addr* addr, char* proto, + port_range *portRange) +{ + char* ptr; + + ptr = strchr (str, ':'); + if (!ptr) + errx (EX_DATAERR, "%s is missing port number", str); + + *ptr = '\0'; + ++ptr; + + StrToAddr (str, addr); + return StrToPortRange (ptr, proto, portRange); +} + +/* End of stuff taken from natd.c. */ + +#define INC_ARGCV() do { \ + (*_av)++; \ + (*_ac)--; \ + av = *_av; \ + ac = *_ac; \ +} while(0) + +/* + * The next 3 functions add support for the addr, port and proto redirect and + * their logic is loosely based on SetupAddressRedirect(), SetupPortRedirect() + * and SetupProtoRedirect() from natd.c. + * + * Every setup_* function fills at least one redirect entry + * (struct cfg_redir) and zero or more server pool entry (struct cfg_spool) + * in buf. + * + * The format of data in buf is: + * + * + * cfg_nat cfg_redir cfg_spool ...... cfg_spool + * + * ------------------------------------- ------------ + * | | .....X ... | | | | ..... + * ------------------------------------- ...... ------------ + * ^ + * spool_cnt n=0 ...... n=(X-1) + * + * len points to the amount of available space in buf + * space counts the memory consumed by every function + * + * XXX - Every function get all the argv params so it + * has to check, in optional parameters, that the next + * args is a valid option for the redir entry and not + * another token. Only redir_port and redir_proto are + * affected by this. + */ + +static int +setup_redir_addr(char *spool_buf, int len, + int *_ac, char ***_av) +{ + char **av, *sep; /* Token separator. */ + /* Temporary buffer used to hold server pool ip's. */ + char tmp_spool_buf[NAT_BUF_LEN]; + int ac, i, space, lsnat; + struct cfg_redir *r; + struct cfg_spool *tmp; + + av = *_av; + ac = *_ac; + space = 0; + lsnat = 0; + if (len >= SOF_REDIR) { + r = (struct cfg_redir *)spool_buf; + /* Skip cfg_redir at beginning of buf. */ + spool_buf = &spool_buf[SOF_REDIR]; + space = SOF_REDIR; + len -= SOF_REDIR; + } else + goto nospace; + r->mode = REDIR_ADDR; + /* Extract local address. */ + if (ac == 0) + errx(EX_DATAERR, "redirect_addr: missing local address"); + sep = strchr(*av, ','); + if (sep) { /* LSNAT redirection syntax. */ + r->laddr.s_addr = INADDR_NONE; + /* Preserve av, copy spool servers to tmp_spool_buf. */ + strncpy(tmp_spool_buf, *av, strlen(*av)+1); + lsnat = 1; + } else + StrToAddr(*av, &r->laddr); + INC_ARGCV(); + + /* Extract public address. */ + if (ac == 0) + errx(EX_DATAERR, "redirect_addr: missing public address"); + StrToAddr(*av, &r->paddr); + INC_ARGCV(); + + /* Setup LSNAT server pool. */ + if (sep) { + sep = strtok(tmp_spool_buf, ","); + while (sep != NULL) { + tmp = (struct cfg_spool *)spool_buf; + if (len < SOF_SPOOL) + goto nospace; + len -= SOF_SPOOL; + space += SOF_SPOOL; + StrToAddr(sep, &tmp->addr); + tmp->port = ~0; + r->spool_cnt++; + /* Point to the next possible cfg_spool. */ + spool_buf = &spool_buf[SOF_SPOOL]; + sep = strtok(NULL, ","); + } + } + return(space); +nospace: + errx(EX_DATAERR, "redirect_addr: buf is too small\n"); +} + +static int +setup_redir_port(char *spool_buf, int len, + int *_ac, char ***_av) +{ + char **av, *sep, *protoName; + char tmp_spool_buf[NAT_BUF_LEN]; + int ac, space, lsnat; + struct cfg_redir *r; + struct cfg_spool *tmp; + u_short numLocalPorts; + port_range portRange; + + av = *_av; + ac = *_ac; + space = 0; + lsnat = 0; + numLocalPorts = 0; + + if (len >= SOF_REDIR) { + r = (struct cfg_redir *)spool_buf; + /* Skip cfg_redir at beginning of buf. */ + spool_buf = &spool_buf[SOF_REDIR]; + space = SOF_REDIR; + len -= SOF_REDIR; + } else + goto nospace; + r->mode = REDIR_PORT; + /* + * Extract protocol. + */ + if (ac == 0) + errx (EX_DATAERR, "redirect_port: missing protocol"); + r->proto = StrToProto(*av); + protoName = *av; + INC_ARGCV(); + + /* + * Extract local address. + */ + if (ac == 0) + errx (EX_DATAERR, "redirect_port: missing local address"); + + sep = strchr(*av, ','); + /* LSNAT redirection syntax. */ + if (sep) { + r->laddr.s_addr = INADDR_NONE; + r->lport = ~0; + numLocalPorts = 1; + /* Preserve av, copy spool servers to tmp_spool_buf. */ + strncpy(tmp_spool_buf, *av, strlen(*av)+1); + lsnat = 1; + } else { + if (StrToAddrAndPortRange (*av, &r->laddr, protoName, + &portRange) != 0) + errx(EX_DATAERR, "redirect_port:" + "invalid local port range"); + + r->lport = GETLOPORT(portRange); + numLocalPorts = GETNUMPORTS(portRange); + } + INC_ARGCV(); + + /* + * Extract public port and optionally address. + */ + if (ac == 0) + errx (EX_DATAERR, "redirect_port: missing public port"); + + sep = strchr (*av, ':'); + if (sep) { + if (StrToAddrAndPortRange (*av, &r->paddr, protoName, + &portRange) != 0) + errx(EX_DATAERR, "redirect_port:" + "invalid public port range"); + } else { + r->paddr.s_addr = INADDR_ANY; + if (StrToPortRange (*av, protoName, &portRange) != 0) + errx(EX_DATAERR, "redirect_port:" + "invalid public port range"); + } + + r->pport = GETLOPORT(portRange); + r->pport_cnt = GETNUMPORTS(portRange); + INC_ARGCV(); + + /* + * Extract remote address and optionally port. + */ + /* + * NB: isalpha(**av) => we've to check that next parameter is really an + * option for this redirect entry, else stop here processing arg[cv]. + */ + if (ac != 0 && !isalpha(**av)) { + sep = strchr (*av, ':'); + if (sep) { + if (StrToAddrAndPortRange (*av, &r->raddr, protoName, + &portRange) != 0) + errx(EX_DATAERR, "redirect_port:" + "invalid remote port range"); + } else { + SETLOPORT(portRange, 0); + SETNUMPORTS(portRange, 1); + StrToAddr (*av, &r->raddr); + } + INC_ARGCV(); + } else { + SETLOPORT(portRange, 0); + SETNUMPORTS(portRange, 1); + r->raddr.s_addr = INADDR_ANY; + } + r->rport = GETLOPORT(portRange); + r->rport_cnt = GETNUMPORTS(portRange); + + /* + * Make sure port ranges match up, then add the redirect ports. + */ + if (numLocalPorts != r->pport_cnt) + errx(EX_DATAERR, "redirect_port:" + "port ranges must be equal in size"); + + /* Remote port range is allowed to be '0' which means all ports. */ + if (r->rport_cnt != numLocalPorts && + (r->rport_cnt != 1 || r->rport != 0)) + errx(EX_DATAERR, "redirect_port: remote port must" + "be 0 or equal to local port range in size"); + + /* + * Setup LSNAT server pool. + */ + if (lsnat) { + sep = strtok(tmp_spool_buf, ","); + while (sep != NULL) { + tmp = (struct cfg_spool *)spool_buf; + if (len < SOF_SPOOL) + goto nospace; + len -= SOF_SPOOL; + space += SOF_SPOOL; + if (StrToAddrAndPortRange(sep, &tmp->addr, protoName, + &portRange) != 0) + errx(EX_DATAERR, "redirect_port:" + "invalid local port range"); + if (GETNUMPORTS(portRange) != 1) + errx(EX_DATAERR, "redirect_port: local port" + "must be single in this context"); + tmp->port = GETLOPORT(portRange); + r->spool_cnt++; + /* Point to the next possible cfg_spool. */ + spool_buf = &spool_buf[SOF_SPOOL]; + sep = strtok(NULL, ","); + } + } + return (space); +nospace: + errx(EX_DATAERR, "redirect_port: buf is too small\n"); +} + +static int +setup_redir_proto(char *spool_buf, int len, + int *_ac, char ***_av) +{ + char **av; + int ac, i, space; + struct protoent *protoent; + struct cfg_redir *r; + + av = *_av; + ac = *_ac; + if (len >= SOF_REDIR) { + r = (struct cfg_redir *)spool_buf; + /* Skip cfg_redir at beginning of buf. */ + spool_buf = &spool_buf[SOF_REDIR]; + space = SOF_REDIR; + len -= SOF_REDIR; + } else + goto nospace; + r->mode = REDIR_PROTO; + /* + * Extract protocol. + */ + if (ac == 0) + errx(EX_DATAERR, "redirect_proto: missing protocol"); + + protoent = getprotobyname(*av); + if (protoent == NULL) + errx(EX_DATAERR, "redirect_proto: unknown protocol %s", *av); + else + r->proto = protoent->p_proto; + + INC_ARGCV(); + + /* + * Extract local address. + */ + if (ac == 0) + errx(EX_DATAERR, "redirect_proto: missing local address"); + else + StrToAddr(*av, &r->laddr); + + INC_ARGCV(); + + /* + * Extract optional public address. + */ + if (ac == 0) { + r->paddr.s_addr = INADDR_ANY; + r->raddr.s_addr = INADDR_ANY; + } else { + /* see above in setup_redir_port() */ + if (!isalpha(**av)) { + StrToAddr(*av, &r->paddr); + INC_ARGCV(); + + /* + * Extract optional remote address. + */ + /* see above in setup_redir_port() */ + if (ac!=0 && !isalpha(**av)) { + StrToAddr(*av, &r->raddr); + INC_ARGCV(); + } + } + } + return (space); +nospace: + errx(EX_DATAERR, "redirect_proto: buf is too small\n"); +} + +static void +show_nat(int ac, char **av); + +static void +print_nat_config(char *buf) { + struct cfg_nat *n; + int i, cnt, flag, off; + struct cfg_redir *t; + struct cfg_spool *s; + struct protoent *p; + + n = (struct cfg_nat *)buf; + flag = 1; + off = sizeof(*n); + printf("ipfw nat %u config", n->id); + if (strlen(n->if_name) != 0) + printf(" if %s", n->if_name); + else if (n->ip.s_addr != 0) + printf(" ip %s", inet_ntoa(n->ip)); + while (n->mode != 0) { + if (n->mode & PKT_ALIAS_LOG) { + printf(" log"); + n->mode &= ~PKT_ALIAS_LOG; + } else if (n->mode & PKT_ALIAS_DENY_INCOMING) { + printf(" deny_in"); + n->mode &= ~PKT_ALIAS_DENY_INCOMING; + } else if (n->mode & PKT_ALIAS_SAME_PORTS) { + printf(" same_ports"); + n->mode &= ~PKT_ALIAS_SAME_PORTS; + } else if (n->mode & PKT_ALIAS_UNREGISTERED_ONLY) { + printf(" unreg_only"); + n->mode &= ~PKT_ALIAS_UNREGISTERED_ONLY; + } else if (n->mode & PKT_ALIAS_RESET_ON_ADDR_CHANGE) { + printf(" reset"); + n->mode &= ~PKT_ALIAS_RESET_ON_ADDR_CHANGE; + } else if (n->mode & PKT_ALIAS_REVERSE) { + printf(" reverse"); + n->mode &= ~PKT_ALIAS_REVERSE; + } else if (n->mode & PKT_ALIAS_PROXY_ONLY) { + printf(" proxy_only"); + n->mode &= ~PKT_ALIAS_PROXY_ONLY; + } + } + /* Print all the redirect's data configuration. */ + for (cnt = 0; cnt < n->redir_cnt; cnt++) { + t = (struct cfg_redir *)&buf[off]; + off += SOF_REDIR; + switch (t->mode) { + case REDIR_ADDR: + printf(" redirect_addr"); + if (t->spool_cnt == 0) + printf(" %s", inet_ntoa(t->laddr)); + else + for (i = 0; i < t->spool_cnt; i++) { + s = (struct cfg_spool *)&buf[off]; + if (i) + printf(","); + else + printf(" "); + printf("%s", inet_ntoa(s->addr)); + off += SOF_SPOOL; + } + printf(" %s", inet_ntoa(t->paddr)); + break; + case REDIR_PORT: + p = getprotobynumber(t->proto); + printf(" redirect_port %s ", p->p_name); + if (!t->spool_cnt) { + printf("%s:%u", inet_ntoa(t->laddr), t->lport); + if (t->pport_cnt > 1) + printf("-%u", t->lport + + t->pport_cnt - 1); + } else + for (i=0; i < t->spool_cnt; i++) { + s = (struct cfg_spool *)&buf[off]; + if (i) + printf(","); + printf("%s:%u", inet_ntoa(s->addr), + s->port); + off += SOF_SPOOL; + } + + printf(" "); + if (t->paddr.s_addr) + printf("%s:", inet_ntoa(t->paddr)); + printf("%u", t->pport); + if (!t->spool_cnt && t->pport_cnt > 1) + printf("-%u", t->pport + t->pport_cnt - 1); + + if (t->raddr.s_addr) { + printf(" %s", inet_ntoa(t->raddr)); + if (t->rport) { + printf(":%u", t->rport); + if (!t->spool_cnt && t->rport_cnt > 1) + printf("-%u", t->rport + + t->rport_cnt - 1); + } + } + break; + case REDIR_PROTO: + p = getprotobynumber(t->proto); + printf(" redirect_proto %s %s", p->p_name, + inet_ntoa(t->laddr)); + if (t->paddr.s_addr != 0) { + printf(" %s", inet_ntoa(t->paddr)); + if (t->raddr.s_addr) + printf(" %s", inet_ntoa(t->raddr)); + } + break; + default: + errx(EX_DATAERR, "unknown redir mode"); + break; + } + } + printf("\n"); +} + +static void +config_nat(int ac, char **av) +{ + struct cfg_nat *n; /* Nat instance configuration. */ + struct in_addr ip; + int i, len, off, tok; + char *id, buf[NAT_BUF_LEN]; /* Buffer for serialized data. */ + + len = NAT_BUF_LEN; + /* Offset in buf: save space for n at the beginning. */ + off = sizeof(*n); + memset(buf, 0, sizeof(buf)); + n = (struct cfg_nat *)buf; + + av++; ac--; + /* Nat id. */ + if (ac && isdigit(**av)) { + id = *av; + i = atoi(*av); + ac--; av++; + n->id = i; + } else + errx(EX_DATAERR, "missing nat id"); + if (ac == 0) + errx(EX_DATAERR, "missing option"); + + while (ac > 0) { + tok = match_token(nat_params, *av); + ac--; av++; + switch (tok) { + case TOK_IP: + if (ac == 0) + errx(EX_DATAERR, "missing option"); + if (!inet_aton(av[0], &(n->ip))) + errx(EX_DATAERR, "bad ip address ``%s''", + av[0]); + ac--; av++; + break; + case TOK_IF: + set_addr_dynamic(av[0], n); + ac--; av++; + break; + case TOK_ALOG: + n->mode |= PKT_ALIAS_LOG; + break; + case TOK_DENY_INC: + n->mode |= PKT_ALIAS_DENY_INCOMING; + break; + case TOK_SAME_PORTS: + n->mode |= PKT_ALIAS_SAME_PORTS; + break; + case TOK_UNREG_ONLY: + n->mode |= PKT_ALIAS_UNREGISTERED_ONLY; + break; + case TOK_RESET_ADDR: + n->mode |= PKT_ALIAS_RESET_ON_ADDR_CHANGE; + break; + case TOK_ALIAS_REV: + n->mode |= PKT_ALIAS_REVERSE; + break; + case TOK_PROXY_ONLY: + n->mode |= PKT_ALIAS_PROXY_ONLY; + break; + /* + * All the setup_redir_* functions work directly in the final + * buffer, see above for details. + */ + case TOK_REDIR_ADDR: + case TOK_REDIR_PORT: + case TOK_REDIR_PROTO: + switch (tok) { + case TOK_REDIR_ADDR: + i = setup_redir_addr(&buf[off], len, &ac, &av); + break; + case TOK_REDIR_PORT: + i = setup_redir_port(&buf[off], len, &ac, &av); + break; + case TOK_REDIR_PROTO: + i = setup_redir_proto(&buf[off], len, &ac, &av); + break; + } + n->redir_cnt++; + off += i; + len -= i; + break; + default: + errx(EX_DATAERR, "unrecognised option ``%s''", av[-1]); + } + } + + i = do_cmd(IP_FW_NAT_CFG, buf, off); + if (i) + err(1, "setsockopt(%s)", "IP_FW_NAT_CFG"); + + /* After every modification, we show the resultant rule. */ + int _ac = 3; + char *_av[] = {"show", "config", id}; + show_nat(_ac, _av); +} + static void config_pipe(int ac, char **av) { @@ -4052,6 +4863,14 @@ add(int ac, char *av[]) ac++; av--; /* go back... */ break; + case TOK_NAT: + action->opcode = O_NAT; + action->len = F_INSN_SIZE(ipfw_insn_nat); + NEED1("missing nat number"); + action->arg1 = strtoul(*av, NULL, 10); + ac--; av++; + break; + default: errx(EX_DATAERR, "invalid action %s\n", av[-1]); } @@ -5023,6 +5842,79 @@ table_handler(int ac, char *av[]) errx(EX_USAGE, "invalid table command %s", *av); } +static void +show_nat(int ac, char **av) { + struct cfg_nat *n; + struct cfg_redir *e; + int cmd, i, nbytes, do_cfg, do_rule, frule, lrule, nalloc, size; + int nat_cnt, r; + uint8_t *data, *p; + char **lav, *endptr; + + do_rule = 0; + nalloc = 1024; + size = 0; + data = NULL; + ac--; av++; + + /* Parse parameters. */ + for (cmd = IP_FW_NAT_GET_LOG, do_cfg = 0; ac != 0; ac--, av++) { + if (!strncmp(av[0], "config", strlen(av[0]))) { + cmd = IP_FW_NAT_GET_CONFIG, do_cfg = 1; + continue; + } + /* Convert command line rule #. */ + frule = lrule = strtoul(av[0], &endptr, 10); + if (*endptr == '-') + lrule = strtoul(endptr+1, &endptr, 10); + if (lrule == 0) + err(EX_USAGE, "invalid rule number: %s", av[0]); + do_rule = 1; + } + + nbytes = nalloc; + while (nbytes >= nalloc) { + nalloc = nalloc * 2; + nbytes = nalloc; + if ((data = realloc(data, nbytes)) == NULL) + err(EX_OSERR, "realloc"); + if (do_cmd(cmd, data, (uintptr_t)&nbytes) < 0) + err(EX_OSERR, "getsockopt(IP_FW_GET_%s)", + (cmd == IP_FW_NAT_GET_LOG) ? "LOG" : "CONFIG"); + } + if (nbytes == 0) + exit(0); + if (do_cfg) { + nat_cnt = *((int *)data); + for (i = sizeof(nat_cnt); nat_cnt; nat_cnt--) { + n = (struct cfg_nat *)&data[i]; + if (do_rule) { + if (!(frule <= n->id && lrule >= n->id)) + continue; + } + print_nat_config(&data[i]); + i += sizeof(struct cfg_nat); + e = (struct cfg_redir *)&data[i]; + if (e->mode == REDIR_ADDR || e->mode == REDIR_PORT || + e->mode == REDIR_PROTO) + i += sizeof(struct cfg_redir) + e->spool_cnt * + sizeof(struct cfg_spool); + } + } else { + for (i = 0; 1; i += LIBALIAS_BUF_SIZE + sizeof(int)) { + p = &data[i]; + if (p == data + nbytes) + break; + bcopy(p, &r, sizeof(int)); + if (do_rule) { + if (!(frule <= r && lrule >= r)) + continue; + } + printf("nat %u: %s\n", r, p+sizeof(int)); + } + } +} + /* * Called with the arguments (excluding program name). * Returns 0 if successful, 1 if empty command, errx() in case of errors. @@ -5211,25 +6103,28 @@ ipfw_main(int oldac, char **oldav) } /* - * optional: pipe or queue + * Optional: pipe, queue or nat. */ + do_nat = 0; do_pipe = 0; - if (_substrcmp(*av, "pipe") == 0) + if (!strncmp(*av, "nat", strlen(*av))) + do_nat = 1; + else if (!strncmp(*av, "pipe", strlen(*av))) do_pipe = 1; else if (_substrcmp(*av, "queue") == 0) do_pipe = 2; - if (do_pipe) { + if (do_pipe || do_nat) { ac--; av++; } NEED1("missing command"); /* - * For pipes and queues we normally say 'pipe NN config' - * but the code is easier to parse as 'pipe config NN' + * For pipes, queues and nats we normally say 'nat|pipe NN config' + * but the code is easier to parse as 'nat|pipe config NN' * so we swap the two arguments. */ - if (do_pipe > 0 && ac > 1 && isdigit(*av[0])) { + if ((do_pipe || do_nat) && ac > 1 && isdigit(*av[0])) { char *p = av[0]; av[0] = av[1]; @@ -5238,8 +6133,12 @@ ipfw_main(int oldac, char **oldav) if (_substrcmp(*av, "add") == 0) add(ac, av); + else if (do_nat && _substrcmp(*av, "show") == 0) + show_nat(ac, av); else if (do_pipe && _substrcmp(*av, "config") == 0) config_pipe(ac, av); + else if (do_nat && _substrcmp(*av, "config") == 0) + config_nat(ac, av); else if (_substrcmp(*av, "delete") == 0) delete(ac, av); else if (_substrcmp(*av, "flush") == 0) diff --git a/sys/netinet/in.h b/sys/netinet/in.h index 570ed339b76b..321877cafd8e 100644 --- a/sys/netinet/in.h +++ b/sys/netinet/in.h @@ -410,6 +410,11 @@ __END_DECLS #define IP_FW_GET 54 /* get entire firewall rule chain */ #define IP_FW_RESETLOG 55 /* reset logging counters */ +#define IP_FW_NAT_CFG 56 /* add/config a nat rule */ +#define IP_FW_NAT_DEL 57 /* delete a nat rule */ +#define IP_FW_NAT_GET_CONFIG 58 /* get configuration of a nat rule */ +#define IP_FW_NAT_GET_LOG 59 /* get log of a nat rule */ + #define IP_DUMMYNET_CONFIGURE 60 /* add/configure a dummynet pipe */ #define IP_DUMMYNET_DEL 61 /* delete a dummynet pipe from chain */ #define IP_DUMMYNET_FLUSH 62 /* flush dummynet */ diff --git a/sys/netinet/ip_fw.h b/sys/netinet/ip_fw.h index 1f50e56737e7..199d569aeb00 100644 --- a/sys/netinet/ip_fw.h +++ b/sys/netinet/ip_fw.h @@ -124,6 +124,7 @@ enum ipfw_opcodes { /* arguments (4 byte each) */ O_TEE, /* arg1=port number */ O_FORWARD_IP, /* fwd sockaddr */ O_FORWARD_MAC, /* fwd mac */ + O_NAT, /* nope */ /* * More opcodes. @@ -307,6 +308,64 @@ typedef struct _ipfw_insn_log { u_int32_t log_left; /* how many left to log */ } ipfw_insn_log; +/* Server pool support (LSNAT). */ +struct cfg_spool { + LIST_ENTRY(cfg_spool) _next; /* chain of spool instances */ + struct in_addr addr; + u_short port; +}; + +/* Redirect modes id. */ +#define REDIR_ADDR 0x01 +#define REDIR_PORT 0x02 +#define REDIR_PROTO 0x04 + +/* Nat redirect configuration. */ +struct cfg_redir { + LIST_ENTRY(cfg_redir) _next; /* chain of redir instances */ + u_int16_t mode; /* type of redirect mode */ + struct in_addr laddr; /* local ip address */ + struct in_addr paddr; /* public ip address */ + struct in_addr raddr; /* remote ip address */ + u_short lport; /* local port */ + u_short pport; /* public port */ + u_short rport; /* remote port */ + u_short pport_cnt; /* number of public ports */ + u_short rport_cnt; /* number of remote ports */ + int proto; /* protocol: tcp/udp */ + struct alias_link **alink; + /* num of entry in spool chain */ + u_int16_t spool_cnt; + /* chain of spool instances */ + LIST_HEAD(spool_chain, cfg_spool) spool_chain; +}; + +#define NAT_BUF_LEN 1024 +/* Nat configuration data struct. */ +struct cfg_nat { + /* chain of nat instances */ + LIST_ENTRY(cfg_nat) _next; + int id; /* nat id */ + struct in_addr ip; /* nat ip address */ + char if_name[IF_NAMESIZE]; /* interface name */ + int mode; /* aliasing mode */ + struct libalias *lib; /* libalias instance */ + /* number of entry in spool chain */ + int redir_cnt; + /* chain of redir instances */ + LIST_HEAD(redir_chain, cfg_redir) redir_chain; +}; + +#define SOF_NAT sizeof(struct cfg_nat) +#define SOF_REDIR sizeof(struct cfg_redir) +#define SOF_SPOOL sizeof(struct cfg_spool) + +/* Nat command. */ +typedef struct _ipfw_insn_nat { + ipfw_insn o; + struct cfg_nat *nat; +} ipfw_insn_nat; + /* Apply ipv6 mask on ipv6 addr */ #define APPLY_MASK(addr,mask) \ (addr)->__u6_addr.__u6_addr32[0] &= (mask)->__u6_addr.__u6_addr32[0]; \ @@ -483,6 +542,7 @@ enum { IP_FW_DUMMYNET, IP_FW_NETGRAPH, IP_FW_NGTEE, + IP_FW_NAT, }; /* flags for divert mtag */ diff --git a/sys/netinet/ip_fw2.c b/sys/netinet/ip_fw2.c index 591e6ff530aa..070de18cb3bd 100644 --- a/sys/netinet/ip_fw2.c +++ b/sys/netinet/ip_fw2.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -84,6 +85,8 @@ #include #include +#include +#include #include #include @@ -138,6 +141,7 @@ struct ip_fw_ugid { struct ip_fw_chain { struct ip_fw *rules; /* list of rules */ struct ip_fw *reap; /* list of rules to reap */ + LIST_HEAD(, cfg_nat) nat; /* list of nat entries */ struct radix_node_head *tables[IPFW_TABLES_MAX]; struct rwlock rwmtx; }; @@ -303,6 +307,7 @@ static struct sysctl_oid *ip6_fw_sysctl_tree; #endif /* INET6 */ #endif /* SYSCTL_NODE */ +MODULE_DEPEND(ipfw, libalias, 1, 1, 1); static int fw_deny_unknown_exthdrs = 1; @@ -861,6 +866,9 @@ ipfw_log(struct ip_fw *f, u_int hlen, struct ip_fw_args *args, snprintf(SNPARGS(action2, 0), "Ngtee %d", cmd->arg1); break; + case O_NAT: + action = "Nat"; + break; default: action = "UNKNOWN"; break; @@ -2028,6 +2036,178 @@ check_uidgid(ipfw_insn_u32 *insn, return match; } +static eventhandler_tag ifaddr_event_tag; + +static void +ifaddr_change(void *arg __unused, struct ifnet *ifp) { + struct cfg_nat *ptr; + struct ifaddr *ifa; + + IPFW_WLOCK(&layer3_chain); + /* Check every nat entry... */ + LIST_FOREACH(ptr, &layer3_chain.nat, _next) { + /* ...using nic 'ifp->if_xname' as dynamic alias address. */ + if (strncmp(ptr->if_name, ifp->if_xname, IF_NAMESIZE) == 0) { + mtx_lock(&ifp->if_addr_mtx); + TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) { + if (ifa->ifa_addr == NULL) + continue; + if (ifa->ifa_addr->sa_family != AF_INET) + continue; + ptr->ip = ((struct sockaddr_in *) + (ifa->ifa_addr))->sin_addr; + LibAliasSetAddress(ptr->lib, ptr->ip); + } + mtx_unlock(&ifp->if_addr_mtx); + } + } + IPFW_WUNLOCK(&layer3_chain); +} + +static void +flush_nat_ptrs(const int i) { + struct ip_fw *rule; + + IPFW_WLOCK_ASSERT(&layer3_chain); + for (rule = layer3_chain.rules; rule; rule = rule->next) { + ipfw_insn_nat *cmd = (ipfw_insn_nat *)ACTION_PTR(rule); + if (cmd->o.opcode != O_NAT) + continue; + if (cmd->nat != NULL && cmd->nat->id == i) + cmd->nat = NULL; + } +} + +static struct cfg_nat * +lookup_nat(const int i) { + struct cfg_nat *ptr; + + LIST_FOREACH(ptr, &layer3_chain.nat, _next) + if (ptr->id == i) + return(ptr); + return (NULL); +} + +#define HOOK_NAT(b, p) do { \ + IPFW_WLOCK_ASSERT(&layer3_chain); \ + LIST_INSERT_HEAD(b, p, _next); \ +} while (0) + +#define UNHOOK_NAT(p) do { \ + IPFW_WLOCK_ASSERT(&layer3_chain); \ + LIST_REMOVE(p, _next); \ +} while (0) + +#define HOOK_REDIR(b, p) do { \ + LIST_INSERT_HEAD(b, p, _next); \ +} while (0) + +#define HOOK_SPOOL(b, p) do { \ + LIST_INSERT_HEAD(b, p, _next); \ +} while (0) + +static void +del_redir_spool_cfg(struct cfg_nat *n, struct redir_chain *head) { + struct cfg_redir *r, *tmp_r; + struct cfg_spool *s, *tmp_s; + int i, num; + + LIST_FOREACH_SAFE(r, head, _next, tmp_r) { + num = 1; /* Number of alias_link to delete. */ + switch (r->mode) { + case REDIR_PORT: + num = r->pport_cnt; + /* FALLTHROUGH */ + case REDIR_ADDR: + case REDIR_PROTO: + /* Delete all libalias redirect entry. */ + for (i = 0; i < num; i++) + LibAliasRedirectDelete(n->lib, r->alink[i]); + /* Del spool cfg if any. */ + LIST_FOREACH_SAFE(s, &r->spool_chain, _next, tmp_s) { + LIST_REMOVE(s, _next); + free(s, M_IPFW); + } + free(r->alink, M_IPFW); + LIST_REMOVE(r, _next); + free(r, M_IPFW); + break; + default: + printf("unknown redirect mode: %u\n", r->mode); + /* XXX - panic?!?!? */ + break; + } + } +} + +static int +add_redir_spool_cfg(char *buf, struct cfg_nat *ptr) { + struct cfg_redir *r, *ser_r; + struct cfg_spool *s, *ser_s; + int cnt, off, i; + char *panic_err; + + for (cnt = 0, off = 0; cnt < ptr->redir_cnt; cnt++) { + ser_r = (struct cfg_redir *)&buf[off]; + r = malloc(SOF_REDIR, M_IPFW, M_WAITOK | M_ZERO); + memcpy(r, ser_r, SOF_REDIR); + LIST_INIT(&r->spool_chain); + off += SOF_REDIR; + r->alink = malloc(sizeof(struct alias_link *) * r->pport_cnt, + M_IPFW, M_WAITOK | M_ZERO); + switch (r->mode) { + case REDIR_ADDR: + r->alink[0] = LibAliasRedirectAddr(ptr->lib, r->laddr, + r->paddr); + break; + case REDIR_PORT: + for (i = 0 ; i < r->pport_cnt; i++) { + /* If remotePort is all ports, set it to 0. */ + u_short remotePortCopy = r->rport + i; + if (r->rport_cnt == 1 && r->rport == 0) + remotePortCopy = 0; + r->alink[i] = LibAliasRedirectPort(ptr->lib, + r->laddr, htons(r->lport + i), r->raddr, + htons(remotePortCopy), r->paddr, + htons(r->pport + i), r->proto); + if (r->alink[i] == NULL) { + r->alink[0] = NULL; + break; + } + } + break; + case REDIR_PROTO: + r->alink[0] = LibAliasRedirectProto(ptr->lib ,r->laddr, + r->raddr, r->paddr, r->proto); + break; + default: + printf("unknown redirect mode: %u\n", r->mode); + break; + } + if (r->alink[0] == NULL) { + panic_err = "LibAliasRedirect* returned NULL"; + goto bad; + } else /* LSNAT handling. */ + for (i = 0; i < r->spool_cnt; i++) { + ser_s = (struct cfg_spool *)&buf[off]; + s = malloc(SOF_REDIR, M_IPFW, + M_WAITOK | M_ZERO); + memcpy(s, ser_s, SOF_SPOOL); + LibAliasAddServer(ptr->lib, r->alink[0], + s->addr, htons(s->port)); + off += SOF_SPOOL; + /* Hook spool entry. */ + HOOK_SPOOL(&r->spool_chain, s); + } + /* And finally hook this redir entry. */ + HOOK_REDIR(&ptr->redir_chain, r); + } + return (1); +bad: + /* something really bad happened: panic! */ + panic("%s\n", panic_err); +} + /* * The main check routine for the firewall. * @@ -3257,6 +3437,177 @@ do { \ IP_FW_NETGRAPH : IP_FW_NGTEE; goto done; + case O_NAT: { + struct cfg_nat *t; + struct mbuf *mcl; + /* XXX - libalias duct tape */ + int ldt; + char *c; + + ldt = 0; + args->rule = f; /* Report matching rule. */ + retval = 0; + t = ((ipfw_insn_nat *)cmd)->nat; + if (t == NULL) { + t = lookup_nat(cmd->arg1); + if (t == NULL) { + retval = IP_FW_DENY; + goto done; + } else + ((ipfw_insn_nat *)cmd)->nat = + t; + } + if ((mcl = m_megapullup(m, m->m_pkthdr.len)) == + NULL) + goto badnat; + ip = mtod(mcl, struct ip *); + if (args->eh == NULL) { + ip->ip_len = htons(ip->ip_len); + ip->ip_off = htons(ip->ip_off); + } + + /* + * XXX - Libalias checksum offload 'duct tape': + * + * locally generated packets have only + * pseudo-header checksum calculated + * and libalias will screw it[1], so + * mark them for later fix. Moreover + * there are cases when libalias + * modify tcp packet data[2], mark it + * for later fix too. + * + * [1] libalias was never meant to run + * in kernel, so it doesn't have any + * knowledge about checksum + * offloading, and it expects a packet + * with a full internet + * checksum. Unfortunately, packets + * generated locally will have just the + * pseudo header calculated, and when + * libalias tries to adjust the + * checksum it will actually screw it. + * + * [2] when libalias modify tcp's data + * content, full TCP checksum has to + * be recomputed: the problem is that + * libalias doesn't have any idea + * about checksum offloading To + * workaround this, we do not do + * checksumming in LibAlias, but only + * mark the packets in th_x2 field. If + * we receive a marked packet, we + * calculate correct checksum for it + * aware of offloading. Why such a + * terrible hack instead of + * recalculating checksum for each + * packet? Because the previous + * checksum was not checked! + * Recalculating checksums for EVERY + * packet will hide ALL transmission + * errors. Yes, marked packets still + * suffer from this problem. But, + * sigh, natd(8) has this problem, + * too. + * + * TODO: -make libalias mbuf aware (so + * it can handle delayed checksum and tso) + */ + + if (mcl->m_pkthdr.rcvif == NULL && + mcl->m_pkthdr.csum_flags & + CSUM_DELAY_DATA) + ldt = 1; + + c = mtod(mcl, char *); + if (oif == NULL) + retval = LibAliasIn(t->lib, c, + MCLBYTES); + else + retval = LibAliasOut(t->lib, c, + MCLBYTES); + if (retval != PKT_ALIAS_OK) { + /* XXX - should i add some logging? */ + m_free(mcl); + badnat: + args->m = NULL; + retval = IP_FW_DENY; + goto done; + } + mcl->m_pkthdr.len = mcl->m_len = + ntohs(ip->ip_len); + + /* + * XXX - libalias checksum offload + * 'duct tape' (see above) + */ + + if ((ip->ip_off & htons(IP_OFFMASK)) == 0 && + ip->ip_p == IPPROTO_TCP) { + struct tcphdr *th; + + th = (struct tcphdr *)(ip + 1); + if (th->th_x2) + ldt = 1; + } + + if (ldt) { + struct tcphdr *th; + struct udphdr *uh; + u_short cksum; + + ip->ip_len = ntohs(ip->ip_len); + cksum = in_pseudo( + ip->ip_src.s_addr, + ip->ip_dst.s_addr, + htons(ip->ip_p + ip->ip_len - + (ip->ip_hl << 2)) + ); + + switch (ip->ip_p) { + case IPPROTO_TCP: + th = (struct tcphdr *)(ip + 1); + /* + * Maybe it was set in + * libalias... + */ + th->th_x2 = 0; + th->th_sum = cksum; + mcl->m_pkthdr.csum_data = + offsetof(struct tcphdr, + th_sum); + break; + case IPPROTO_UDP: + uh = (struct udphdr *)(ip + 1); + uh->uh_sum = cksum; + mcl->m_pkthdr.csum_data = + offsetof(struct udphdr, + uh_sum); + break; + } + /* + * No hw checksum offloading: do it + * by ourself. + */ + if ((mcl->m_pkthdr.csum_flags & + CSUM_DELAY_DATA) == 0) { + in_delayed_cksum(mcl); + mcl->m_pkthdr.csum_flags &= + ~CSUM_DELAY_DATA; + } + ip->ip_len = htons(ip->ip_len); + } + + if (args->eh == NULL) { + ip->ip_len = ntohs(ip->ip_len); + ip->ip_off = ntohs(ip->ip_off); + } + + args->m = mcl; + retval = IP_FW_NAT; + goto done; + } + default: panic("-- unknown opcode %d\n", cmd->opcode); } /* end of switch() on opcodes */ @@ -3826,6 +4177,10 @@ check_ipfw_struct(struct ip_fw *rule, int size) return EINVAL; else goto check_size; + case O_NAT: + if (cmdlen != F_INSN_SIZE(ipfw_insn_nat)) + goto bad_size; + goto check_action; case O_FORWARD_MAC: /* XXX not implemented yet */ case O_CHECK_STATE: case O_COUNT: @@ -4201,6 +4556,185 @@ ipfw_ctl(struct sockopt *sopt) } break; + case IP_FW_NAT_CFG: + { + struct cfg_nat *ptr, *ser_n; + char *buf; + + buf = malloc(NAT_BUF_LEN, M_IPFW, M_WAITOK | M_ZERO); + error = sooptcopyin(sopt, buf, NAT_BUF_LEN, + sizeof(struct cfg_nat)); + ser_n = (struct cfg_nat *)buf; + + /* + * Find/create nat rule. + */ + IPFW_WLOCK(&layer3_chain); + ptr = lookup_nat(ser_n->id); + if (ptr == NULL) { + /* New rule: allocate and init new instance. */ + ptr = malloc(sizeof(struct cfg_nat), + M_IPFW, M_NOWAIT | M_ZERO); + if (ptr == NULL) { + IPFW_WUNLOCK(&layer3_chain); + free(buf, M_IPFW); + return (ENOSPC); + } + ptr->lib = LibAliasInit(NULL); + if (ptr->lib == NULL) { + IPFW_WUNLOCK(&layer3_chain); + free(ptr, M_IPFW); + free(buf, M_IPFW); + return (EINVAL); + } + LIST_INIT(&ptr->redir_chain); + } else { + /* Entry already present: temporarly unhook it. */ + UNHOOK_NAT(ptr); + flush_nat_ptrs(ser_n->id); + } + IPFW_WUNLOCK(&layer3_chain); + + /* + * Basic nat configuration. + */ + ptr->id = ser_n->id; + /* + * XXX - what if this rule doesn't nat any ip and just + * redirect? + * do we set aliasaddress to 0.0.0.0? + */ + ptr->ip = ser_n->ip; + ptr->redir_cnt = ser_n->redir_cnt; + ptr->mode = ser_n->mode; + LibAliasSetMode(ptr->lib, ser_n->mode, ser_n->mode); + LibAliasSetAddress(ptr->lib, ptr->ip); + memcpy(ptr->if_name, ser_n->if_name, IF_NAMESIZE); + + /* + * Redir and LSNAT configuration. + */ + /* Delete old cfgs. */ + del_redir_spool_cfg(ptr, &ptr->redir_chain); + /* Add new entries. */ + add_redir_spool_cfg(&buf[(sizeof(struct cfg_nat))], ptr); + free(buf, M_IPFW); + IPFW_WLOCK(&layer3_chain); + HOOK_NAT(&layer3_chain.nat, ptr); + IPFW_WUNLOCK(&layer3_chain); + } + break; + + case IP_FW_NAT_DEL: + { + struct cfg_nat *ptr; + int i; + + error = sooptcopyin(sopt, &i, sizeof i, sizeof i); + IPFW_WLOCK(&layer3_chain); + ptr = lookup_nat(i); + if (ptr == NULL) { + error = EINVAL; + IPFW_WUNLOCK(&layer3_chain); + break; + } + UNHOOK_NAT(ptr); + flush_nat_ptrs(i); + IPFW_WUNLOCK(&layer3_chain); + del_redir_spool_cfg(ptr, &ptr->redir_chain); + LibAliasUninit(ptr->lib); + free(ptr, M_IPFW); + } + break; + + case IP_FW_NAT_GET_CONFIG: + { + uint8_t *data; + struct cfg_nat *n; + struct cfg_redir *r; + struct cfg_spool *s; + int nat_cnt, off; + + nat_cnt = 0; + off = sizeof(nat_cnt); + + data = malloc(NAT_BUF_LEN, M_IPFW, M_WAITOK | M_ZERO); + IPFW_RLOCK(&layer3_chain); + /* Serialize all the data. */ + LIST_FOREACH(n, &layer3_chain.nat, _next) { + nat_cnt++; + if (off + SOF_NAT < NAT_BUF_LEN) { + bcopy(n, &data[off], SOF_NAT); + off += SOF_NAT; + LIST_FOREACH(r, &n->redir_chain, _next) { + if (off + SOF_REDIR < NAT_BUF_LEN) { + bcopy(r, &data[off], + SOF_REDIR); + off += SOF_REDIR; + LIST_FOREACH(s, &r->spool_chain, + _next) { + if (off + SOF_SPOOL < + NAT_BUF_LEN) { + bcopy(s, + &data[off], + SOF_SPOOL); + off += + SOF_SPOOL; + } else + goto nospace; + } + } else + goto nospace; + } + } else + goto nospace; + } + bcopy(&nat_cnt, data, sizeof(nat_cnt)); + IPFW_RUNLOCK(&layer3_chain); + error = sooptcopyout(sopt, data, NAT_BUF_LEN); + free(data, M_IPFW); + break; + nospace: + IPFW_RUNLOCK(&layer3_chain); + printf("serialized data buffer not big enough:" + "please increase NAT_BUF_LEN\n"); + free(data, M_IPFW); + } + break; + + case IP_FW_NAT_GET_LOG: + { + uint8_t *data; + struct cfg_nat *ptr; + int i, size, cnt, sof; + + data = NULL; + sof = LIBALIAS_BUF_SIZE; + cnt = 0; + + IPFW_RLOCK(&layer3_chain); + size = i = 0; + LIST_FOREACH(ptr, &layer3_chain.nat, _next) { + if (ptr->lib->logDesc == NULL) + continue; + cnt++; + size = cnt * (sof + sizeof(int)); + data = realloc(data, size, M_IPFW, M_NOWAIT | M_ZERO); + if (data == NULL) { + IPFW_RUNLOCK(&layer3_chain); + return (ENOSPC); + } + bcopy(&ptr->id, &data[i], sizeof(int)); + i += sizeof(int); + bcopy(ptr->lib->logDesc, &data[i], sof); + i += sof; + } + IPFW_RUNLOCK(&layer3_chain); + error = sooptcopyout(sopt, data, size); + free(data, M_IPFW); + } + break; + default: printf("ipfw: ipfw_ctl invalid option %d\n", sopt->sopt_name); error = EINVAL; @@ -4372,8 +4906,10 @@ ipfw_init(void) } ip_fw_ctl_ptr = ipfw_ctl; ip_fw_chk_ptr = ipfw_chk; - callout_reset(&ipfw_timeout, hz, ipfw_tick, NULL); - + callout_reset(&ipfw_timeout, hz, ipfw_tick, NULL); + LIST_INIT(&layer3_chain.nat); + ifaddr_event_tag = EVENTHANDLER_REGISTER(ifaddr_event, ifaddr_change, + NULL, EVENTHANDLER_PRI_ANY); return (0); } @@ -4381,12 +4917,20 @@ void ipfw_destroy(void) { struct ip_fw *reap; + struct cfg_nat *ptr, *ptr_temp; ip_fw_chk_ptr = NULL; ip_fw_ctl_ptr = NULL; callout_drain(&ipfw_timeout); IPFW_WLOCK(&layer3_chain); flush_tables(&layer3_chain); + LIST_FOREACH_SAFE(ptr, &layer3_chain.nat, _next, ptr_temp) { + LIST_REMOVE(ptr, _next); + del_redir_spool_cfg(ptr, &ptr->redir_chain); + LibAliasUninit(ptr->lib); + free(ptr, M_IPFW); + } + EVENTHANDLER_DEREGISTER(ifaddr_event, ifaddr_event_tag); layer3_chain.reap = NULL; free_chain(&layer3_chain, 1 /* kill default rule */); reap = layer3_chain.reap, layer3_chain.reap = NULL; diff --git a/sys/netinet/ip_fw_pfil.c b/sys/netinet/ip_fw_pfil.c index a58f96fe9040..d8285b541f9a 100644 --- a/sys/netinet/ip_fw_pfil.c +++ b/sys/netinet/ip_fw_pfil.c @@ -189,6 +189,9 @@ ipfw_check_in(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, if (!NG_IPFW_LOADED) goto drop; return ng_ipfw_input_p(m0, NG_IPFW_IN, &args, 0); + + case IP_FW_NAT: + goto again; /* continue with packet */ default: KASSERT(0, ("%s: unknown retval", __func__)); @@ -315,6 +318,9 @@ ipfw_check_out(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, goto drop; return ng_ipfw_input_p(m0, NG_IPFW_OUT, &args, 0); + case IP_FW_NAT: + goto again; /* continue with packet */ + default: KASSERT(0, ("%s: unknown retval", __func__)); } diff --git a/sys/netinet/raw_ip.c b/sys/netinet/raw_ip.c index c6900b25849b..51fe333c2f84 100644 --- a/sys/netinet/raw_ip.c +++ b/sys/netinet/raw_ip.c @@ -389,6 +389,8 @@ rip_ctloutput(struct socket *so, struct sockopt *sopt) case IP_FW_GET: case IP_FW_TABLE_GETSIZE: case IP_FW_TABLE_LIST: + case IP_FW_NAT_GET_CONFIG: + case IP_FW_NAT_GET_LOG: /* * XXXRW: Isn't this checked one layer down? Yes, it * is. @@ -458,6 +460,8 @@ rip_ctloutput(struct socket *so, struct sockopt *sopt) case IP_FW_TABLE_ADD: case IP_FW_TABLE_DEL: case IP_FW_TABLE_FLUSH: + case IP_FW_NAT_CFG: + case IP_FW_NAT_DEL: /* * XXXRW: Isn't this checked one layer down? */