diff --git a/lib/Makefile b/lib/Makefile index b97386d3e..be1e91b86 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -17,6 +17,8 @@ X86_INCLUDES=0 HOST_OS:=$(shell uname -s) #DEBUG=-O0 -gdwarf-2 -g3 +#FF_NETGRAPH=1 +#FF_IPFW=1 ifeq ($(FF_DPDK),) FF_DPDK=${TOPDIR}/dpdk/x86_64-native-linuxapp-gcc @@ -50,6 +52,10 @@ ifdef FF_NETGRAPH HOST_CFLAGS+= -DFF_NETGRAPH endif +ifdef FF_IPFW +HOST_CFLAGS+= -DFF_IPFW +endif + HOST_C= ${CC} -c $(HOST_CFLAGS) ${HOST_INCLUDES} ${WERROR} ${PROF} $< @@ -122,6 +128,9 @@ VPATH+= $S/netinet VPATH+= $S/netinet/libalias VPATH+= $S/netinet/cc VPATH+= $S/netipsec +ifdef FF_IPFW +VPATH+= $S/netpfil/ipfw +endif VPATH+= $S/opencrypto VPATH+= $S/vm VPATH+= $S/libkern @@ -373,6 +382,21 @@ NETINET_SRCS+= \ alias_sctp.c \ alias_util.c +ifdef FF_IPFW +NETIPFW_SRCS+= \ + ip_fw_dynamic.c \ + ip_fw_eaction.c \ + ip_fw_iface.c \ + ip_fw_log.c \ + ip_fw_nat.c \ + ip_fw_pfil.c \ + ip_fw_sockopt.c \ + ip_fw_table.c \ + ip_fw_table_algo.c \ + ip_fw_table_value.c \ + ip_fw2.c +endif + ifdef FF_IPSEC NETINET_SRCS+= \ ip_ipsec.c @@ -431,7 +455,7 @@ ASM_SRCS = ${CRYPTO_ASM_SRCS} SRCS= ${FF_SRCS} ${CRYPTO_SRCS} ${KERN_SRCS} ${LIBKERN_SRCS} ${MACHINE_SRCS} SRCS+= ${MSRCS} ${NET_SRCS} ${NETGRAPH_SRCS} ${NETINET_SRCS} ${NETINET6_SRCS} -SRCS+= ${NETIPSEC_SRCS} ${OPENCRYPTO_SRCS} ${VM_SRCS} +SRCS+= ${NETIPSEC_SRCS} ${NETIPFW_SRCS} ${OPENCRYPTO_SRCS} ${VM_SRCS} # If witness is enabled. # SRCS+= ${KERN_WITNESS_SRCS} diff --git a/lib/ff_api.h b/lib/ff_api.h index 898f23cb9..97f460662 100644 --- a/lib/ff_api.h +++ b/lib/ff_api.h @@ -148,9 +148,11 @@ int ff_route_ctl(enum FF_ROUTE_CTL req, enum FF_ROUTE_FLAG flag, * Implemented by user. * * @param data - * The data pointer of the packet. + * The data pointer of this packet. * @param len - * The length of the packet. + * The length of this packet. + * @param queue_id + * Current queue of this packet. * @param nb_queues * Number of queues to be dispatched. * @@ -160,7 +162,8 @@ int ff_route_ctl(enum FF_ROUTE_CTL req, enum FF_ROUTE_FLAG flag, * Error occurs or packet is handled by user, packet will be freed. * */ -typedef int (*dispatch_func_t)(void *data, uint16_t len, uint16_t nb_queues); +typedef int (*dispatch_func_t)(void *data, uint16_t len, + uint16_t queue_id, uint16_t nb_queues); /* regist a packet dispath function */ void ff_regist_packet_dispatcher(dispatch_func_t func); diff --git a/lib/ff_dpdk_if.c b/lib/ff_dpdk_if.c index 880a6ff80..46e893b57 100644 --- a/lib/ff_dpdk_if.c +++ b/lib/ff_dpdk_if.c @@ -919,7 +919,7 @@ process_packets(uint8_t port_id, uint16_t queue_id, struct rte_mbuf **bufs, uint16_t len = rte_pktmbuf_data_len(rtem); if (!pkts_from_ring && packet_dispatcher) { - int ret = (*packet_dispatcher)(data, len, nb_queues); + int ret = (*packet_dispatcher)(data, len, queue_id, nb_queues); if (ret < 0 || ret >= nb_queues) { rte_pktmbuf_free(rtem); continue; @@ -994,7 +994,7 @@ process_dispatch_ring(uint8_t port_id, uint16_t queue_id, } static inline void -handle_sysctl_msg(struct ff_msg *msg, uint16_t proc_id) +handle_sysctl_msg(struct ff_msg *msg) { int ret = ff_sysctl(msg->sysctl.name, msg->sysctl.namelen, msg->sysctl.old, msg->sysctl.oldlenp, msg->sysctl.new, @@ -1005,12 +1005,10 @@ handle_sysctl_msg(struct ff_msg *msg, uint16_t proc_id) } else { msg->result = 0; } - - rte_ring_enqueue(msg_ring[proc_id].ring[1], msg); } static inline void -handle_ioctl_msg(struct ff_msg *msg, uint16_t proc_id) +handle_ioctl_msg(struct ff_msg *msg) { int fd, ret; fd = ff_socket(AF_INET, SOCK_DGRAM, 0); @@ -1029,12 +1027,10 @@ done: } else { msg->result = 0; } - - rte_ring_enqueue(msg_ring[proc_id].ring[1], msg); } static inline void -handle_route_msg(struct ff_msg *msg, uint16_t proc_id) +handle_route_msg(struct ff_msg *msg) { int ret = ff_rtioctl(msg->route.fib, msg->route.data, &msg->route.len, msg->route.maxlen); @@ -1043,23 +1039,19 @@ handle_route_msg(struct ff_msg *msg, uint16_t proc_id) } else { msg->result = 0; } - - rte_ring_enqueue(msg_ring[proc_id].ring[1], msg); } static struct ff_top_args ff_status; static inline void -handle_top_msg(struct ff_msg *msg, uint16_t proc_id) +handle_top_msg(struct ff_msg *msg) { msg->top = ff_status; msg->result = 0; - - rte_ring_enqueue(msg_ring[proc_id].ring[1], msg); } #ifdef FF_NETGRAPH static inline void -handle_ngctl_msg(struct ff_msg *msg, uint16_t proc_id) +handle_ngctl_msg(struct ff_msg *msg) { int ret = ff_ngctl(msg->ngctl.cmd, msg->ngctl.data); if (ret < 0) { @@ -1068,16 +1060,52 @@ handle_ngctl_msg(struct ff_msg *msg, uint16_t proc_id) msg->result = 0; msg->ngctl.ret = ret; } +} +#endif - rte_ring_enqueue(msg_ring[proc_id].ring[1], msg); +#ifdef FF_IPFW +static inline void +handle_ipfw_msg(struct ff_msg *msg) +{ + int fd, ret; + fd = ff_socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (fd < 0) { + ret = -1; + goto done; + } + + switch (msg->ipfw.cmd) { + case FF_IPFW_GET: + ret = ff_getsockopt(fd, msg->ipfw.level, + msg->ipfw.optname, msg->ipfw.optval, + msg->ipfw.optlen); + break; + case FF_IPFW_SET: + ret = ff_setsockopt(fd, msg->ipfw.level, + msg->ipfw.optname, msg->ipfw.optval, + *(msg->ipfw.optlen)); + break; + default: + ret = -1; + errno = ENOTSUP; + break; + } + + ff_close(fd); + +done: + if (ret < 0) { + msg->result = errno; + } else { + msg->result = 0; + } } #endif static inline void -handle_default_msg(struct ff_msg *msg, uint16_t proc_id) +handle_default_msg(struct ff_msg *msg) { msg->result = ENOTSUP; - rte_ring_enqueue(msg_ring[proc_id].ring[1], msg); } static inline void @@ -1085,26 +1113,32 @@ handle_msg(struct ff_msg *msg, uint16_t proc_id) { switch (msg->msg_type) { case FF_SYSCTL: - handle_sysctl_msg(msg, proc_id); + handle_sysctl_msg(msg); break; case FF_IOCTL: - handle_ioctl_msg(msg, proc_id); + handle_ioctl_msg(msg); break; case FF_ROUTE: - handle_route_msg(msg, proc_id); + handle_route_msg(msg); break; case FF_TOP: - handle_top_msg(msg, proc_id); + handle_top_msg(msg); break; #ifdef FF_NETGRAPH case FF_NGCTL: - handle_ngctl_msg(msg, proc_id); + handle_ngctl_msg(msg); + break; +#endif +#ifdef FF_IPFW + case FF_IPFW_CTL: + handle_ipfw_msg(msg); break; #endif default: - handle_default_msg(msg, proc_id); + handle_default_msg(msg); break; } + rte_ring_enqueue(msg_ring[proc_id].ring[1], msg); } static inline int diff --git a/lib/ff_glue.c b/lib/ff_glue.c index a48965cfd..cd4db952c 100644 --- a/lib/ff_glue.c +++ b/lib/ff_glue.c @@ -896,6 +896,11 @@ securelevel_gt(struct ucred *cr, int level) return (0); } +int +securelevel_ge(struct ucred *cr, int level) +{ + return (0); +} /** * @brief Send a 'notification' to userland, using standard ways @@ -1140,3 +1145,37 @@ getcredhostid(struct ucred *cred, unsigned long *hostid) { *hostid = 0; } + +/* + * Check if gid is a member of the group set. + */ +int +groupmember(gid_t gid, struct ucred *cred) +{ + int l; + int h; + int m; + + if (cred->cr_groups[0] == gid) + return(1); + + /* + * If gid was not our primary group, perform a binary search + * of the supplemental groups. This is possible because we + * sort the groups in crsetgroups(). + */ + l = 1; + h = cred->cr_ngroups; + while (l < h) { + m = l + ((h - l) / 2); + if (cred->cr_groups[m] < gid) + l = m + 1; + else + h = m; + } + if ((l < cred->cr_ngroups) && (cred->cr_groups[l] == gid)) + return (1); + + return (0); +} + diff --git a/lib/ff_msg.h b/lib/ff_msg.h index 921ae7767..7e91b7114 100644 --- a/lib/ff_msg.h +++ b/lib/ff_msg.h @@ -41,6 +41,7 @@ enum FF_MSG_TYPE { FF_ROUTE, FF_TOP, FF_NGCTL, + FF_IPFW_CTL, }; struct ff_sysctl_args { @@ -78,6 +79,19 @@ struct ff_ngctl_args { void *data; }; +enum FF_IPFW_CMD { + FF_IPFW_GET, + FF_IPFW_SET, +}; + +struct ff_ipfw_args { + int cmd; + int level; + int optname; + void *optval; + socklen_t *optlen; +}; + #define MAX_MSG_BUF_SIZE 10240 /* structure of ipc msg */ @@ -96,6 +110,7 @@ struct ff_msg { struct ff_route_args route; struct ff_top_args top; struct ff_ngctl_args ngctl; + struct ff_ipfw_args ipfw; }; } __attribute__((packed)) __rte_cache_aligned; diff --git a/lib/ff_syscall_wrapper.c b/lib/ff_syscall_wrapper.c index aadda2752..a0d7b388f 100644 --- a/lib/ff_syscall_wrapper.c +++ b/lib/ff_syscall_wrapper.c @@ -384,7 +384,7 @@ ff_getsockopt(int s, int level, int optname, void *optval, } if ((rc = kern_getsockopt(curthread, s, level, optname, - optval, UIO_SYSSPACE, optlen))) + optval, UIO_USERSPACE, optlen))) goto kern_fail; return (rc); @@ -410,7 +410,7 @@ ff_setsockopt(int s, int level, int optname, const void *optval, } if ((rc = kern_setsockopt(curthread, s, level, optname, - __DECONST(void *, optval), UIO_SYSSPACE, optlen))) + __DECONST(void *, optval), UIO_USERSPACE, optlen))) goto kern_fail; return (rc); diff --git a/lib/opt/opt_bpf.h b/lib/opt/opt_bpf.h index e69de29bb..1c5d3ad54 100644 --- a/lib/opt/opt_bpf.h +++ b/lib/opt/opt_bpf.h @@ -0,0 +1 @@ +#define WITHOUT_BPF 1 diff --git a/lib/opt/opt_ipdivert.h b/lib/opt/opt_ipdivert.h new file mode 100644 index 000000000..e43225680 --- /dev/null +++ b/lib/opt/opt_ipdivert.h @@ -0,0 +1 @@ +//#define IPDIVERT 1 diff --git a/lib/opt/opt_ipfw.h b/lib/opt/opt_ipfw.h index e69de29bb..6eb79d0fe 100644 --- a/lib/opt/opt_ipfw.h +++ b/lib/opt/opt_ipfw.h @@ -0,0 +1,12 @@ +//sets default policy to pass what is not explicitly denied +#define IPFIREWALL_DEFAULT_TO_ACCEPT 1 + +//enables logging for rules with log keyword +//#define IPFIREWALL_VERBOSE 1 + +//limits number of logged packets per-entry +//#define IPFIREWALL_VERBOSE_LIMIT 5 + +//enables NAT +//#define IPFIREWALL_NAT 1 + diff --git a/tools/Makefile b/tools/Makefile index 87c4ab029..f145a8292 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -1,4 +1,4 @@ -SUBDIRS=compat libutil libmemstat libxo libnetgraph sysctl ifconfig route top netstat ngctl +SUBDIRS=compat libutil libmemstat libxo libnetgraph sysctl ifconfig route top netstat ngctl ipfw all: for d in $(SUBDIRS); do ( cd $$d; $(MAKE) all ) ; done diff --git a/tools/README.md b/tools/README.md index 384423fb8..b95b4cd96 100644 --- a/tools/README.md +++ b/tools/README.md @@ -152,6 +152,52 @@ About interactive mode: For more details, see [Manual page](https://www.freebsd.org/cgi/man.cgi?ngctl). +# ipfw +Usage: +``` +ipfw -P [-abcdefhnNqStTv] + +where is one of the following: + +add [num] [set N] [prob x] RULE-BODY +{pipe|queue} N config PIPE-BODY +[pipe|queue] {zero|delete|show} [N{,N}] +nat N config {ip IPADDR|if IFNAME|log|deny_in|same_ports|unreg_only|reset| + reverse|proxy_only|redirect_addr linkspec| + redirect_port linkspec|redirect_proto linkspec} +set [disable N... enable N...] | move [rule] X to Y | swap X Y | show +set N {show|list|zero|resetlog|delete} [N{,N}] | flush +table N {add ip[/bits] [value] | delete ip[/bits] | flush | list} +table all {flush | list} + +RULE-BODY: check-state [PARAMS] | ACTION [PARAMS] ADDR [OPTION_LIST] +ACTION: check-state | allow | count | deny | unreach{,6} CODE | + skipto N | {divert|tee} PORT | forward ADDR | + pipe N | queue N | nat N | setfib FIB | reass +PARAMS: [log [logamount LOGLIMIT]] [altq QUEUE_NAME] +ADDR: [ MAC dst src ether_type ] + [ ip from IPADDR [ PORT ] to IPADDR [ PORTLIST ] ] + [ ipv6|ip6 from IP6ADDR [ PORT ] to IP6ADDR [ PORTLIST ] ] +IPADDR: [not] { any | me | ip/bits{x,y,z} | table(t[,v]) | IPLIST } +IP6ADDR: [not] { any | me | me6 | ip6/bits | IP6LIST } +IP6LIST: { ip6 | ip6/bits }[,IP6LIST] +IPLIST: { ip | ip/bits | ip:mask }[,IPLIST] +OPTION_LIST: OPTION [OPTION_LIST] +OPTION: bridged | diverted | diverted-loopback | diverted-output | + {dst-ip|src-ip} IPADDR | {dst-ip6|src-ip6|dst-ipv6|src-ipv6} IP6ADDR | + {dst-port|src-port} LIST | + estab | frag | {gid|uid} N | icmptypes LIST | in | out | ipid LIST | + iplen LIST | ipoptions SPEC | ipprecedence | ipsec | iptos SPEC | + ipttl LIST | ipversion VER | keep-state | layer2 | limit ... | + icmp6types LIST | ext6hdr LIST | flow-id N[,N] | fib FIB | + mac ... | mac-type LIST | proto LIST | {recv|xmit|via} {IF|IPADDR} | + setup | {tcpack|tcpseq|tcpwin} NN | tcpflags SPEC | tcpoptions SPEC | + tcpdatalen LIST | verrevpath | versrcreach | antispoof +``` +Note [dummynet](https://www.freebsd.org/cgi/man.cgi?query=dummynet) is not supported yet. + +For more details, see [Manual page](https://www.freebsd.org/cgi/man.cgi?ipfw) or [handbook](https://www.freebsd.org/doc/handbook/firewalls-ipfw.html). + # how to implement a custom tool for communicating with F-Stack process Add a new FF_MSG_TYPE in ff_msg.h: diff --git a/tools/compat/compat.h b/tools/compat/compat.h index d814d65f1..21b1f004f 100644 --- a/tools/compat/compat.h +++ b/tools/compat/compat.h @@ -41,6 +41,26 @@ #define nitems(x) (sizeof((x)) / sizeof((x)[0])) #endif +#ifndef rounddown +#define rounddown(x, y) (((x)/(y))*(y)) +#endif + +#ifndef rounddown2 +#define rounddown2(x, y) ((x)&(~((y)-1))) /* if y is power of two */ +#endif + +#ifndef roundup +#define roundup(x, y) ((((x)+((y)-1))/(y))*(y)) /* to any y */ +#endif + +#ifndef roundup2 +#define roundup2(x, y) (((x)+((y)-1))&(~((y)-1))) /* if y is powers of two */ +#endif + +#ifndef powerof2 +#define powerof2(x) ((((x)-1)&(x))==0) +#endif + #ifndef __FBSDID #define __FBSDID(s) /* nothing */ #endif @@ -49,6 +69,18 @@ #define _PATH_ETC "/etc" #endif +#ifndef __PAST_END +/* + * Access a variable length array that has been declared as a fixed + * length array. + */ +#define __PAST_END(array, offset) (((__typeof__(*(array)) *)(array))[offset]) +#endif + +#ifndef ishexnumber +#define ishexnumber(x) isxdigit(x) +#endif + void *reallocf(void *ptr, size_t size); int feature_present(const char *feature); @@ -63,4 +95,6 @@ long long strtonum(const char *numstr, long long minval, const char *getprogname(void); +extern int optreset; + #endif diff --git a/tools/compat/include/alias.h b/tools/compat/include/alias.h new file mode 100644 index 000000000..b12b353a0 --- /dev/null +++ b/tools/compat/include/alias.h @@ -0,0 +1,238 @@ +/* lint -save -library Flexelint comment for external headers */ + +/*- + * Copyright (c) 2001 Charles Mott + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Alias.h defines the outside world interfaces for the packet aliasing + * software. + * + * This software is placed into the public domain with no restrictions on its + * distribution. + */ + +#ifndef _ALIAS_H_ +#define _ALIAS_H_ + +#include +#include +#include + +#define LIBALIAS_BUF_SIZE 128 +#ifdef _KERNEL +/* + * The kernel version of libalias does not support these features. + */ +#define NO_FW_PUNCH +#define NO_USE_SOCKETS +#endif + +/* + * The external interface to libalias, the packet aliasing engine. + * + * There are two sets of functions: + * + * PacketAlias*() the old API which doesn't take an instance pointer + * and therefore can only have one packet engine at a time. + * + * LibAlias*() the new API which takes as first argument a pointer to + * the instance of the packet aliasing engine. + * + * The functions otherwise correspond to each other one for one, except + * for the LibAliasUnaliasOut()/PacketUnaliasOut() function which were + * were misnamed in the old API. + */ + +/* + * The instance structure + */ +struct libalias; + +/* + * An anonymous structure, a pointer to which is returned from + * PacketAliasRedirectAddr(), PacketAliasRedirectPort() or + * PacketAliasRedirectProto(), passed to PacketAliasAddServer(), + * and freed by PacketAliasRedirectDelete(). + */ +struct alias_link; + +/* Initialization and control functions. */ +struct libalias *LibAliasInit(struct libalias *); +void LibAliasSetAddress(struct libalias *, struct in_addr _addr); +void LibAliasSetFWBase(struct libalias *, unsigned int _base, unsigned int _num); +void LibAliasSetSkinnyPort(struct libalias *, unsigned int _port); +unsigned int + LibAliasSetMode(struct libalias *, unsigned int _flags, unsigned int _mask); +void LibAliasUninit(struct libalias *); + +/* Packet Handling functions. */ +int LibAliasIn (struct libalias *, char *_ptr, int _maxpacketsize); +int LibAliasOut(struct libalias *, char *_ptr, int _maxpacketsize); +int LibAliasOutTry(struct libalias *, char *_ptr, int _maxpacketsize, int _create); +int LibAliasUnaliasOut(struct libalias *, char *_ptr, int _maxpacketsize); + +/* Port and address redirection functions. */ + +int +LibAliasAddServer(struct libalias *, struct alias_link *_lnk, + struct in_addr _addr, unsigned short _port); +struct alias_link * +LibAliasRedirectAddr(struct libalias *, struct in_addr _src_addr, + struct in_addr _alias_addr); +int LibAliasRedirectDynamic(struct libalias *, struct alias_link *_lnk); +void LibAliasRedirectDelete(struct libalias *, struct alias_link *_lnk); +struct alias_link * +LibAliasRedirectPort(struct libalias *, struct in_addr _src_addr, + unsigned short _src_port, struct in_addr _dst_addr, + unsigned short _dst_port, struct in_addr _alias_addr, + unsigned short _alias_port, unsigned char _proto); +struct alias_link * +LibAliasRedirectProto(struct libalias *, struct in_addr _src_addr, + struct in_addr _dst_addr, struct in_addr _alias_addr, + unsigned char _proto); + +/* Fragment Handling functions. */ +void LibAliasFragmentIn(struct libalias *, char *_ptr, char *_ptr_fragment); +char *LibAliasGetFragment(struct libalias *, char *_ptr); +int LibAliasSaveFragment(struct libalias *, char *_ptr); + +/* Miscellaneous functions. */ +int LibAliasCheckNewLink(struct libalias *); +unsigned short + LibAliasInternetChecksum(struct libalias *, unsigned short *_ptr, int _nbytes); +void LibAliasSetTarget(struct libalias *, struct in_addr _target_addr); + +/* Transparent proxying routines. */ +int LibAliasProxyRule(struct libalias *, const char *_cmd); + +/* Module handling API */ +int LibAliasLoadModule(char *); +int LibAliasUnLoadAllModule(void); +int LibAliasRefreshModules(void); + +/* Mbuf helper function. */ +struct mbuf *m_megapullup(struct mbuf *, int); + +/* + * Mode flags and other constants. + */ + + +/* Mode flags, set using PacketAliasSetMode() */ + +/* + * If PKT_ALIAS_LOG is set, a message will be printed to /var/log/alias.log + * every time a link is created or deleted. This is useful for debugging. + */ +#define PKT_ALIAS_LOG 0x01 + +/* + * If PKT_ALIAS_DENY_INCOMING is set, then incoming connections (e.g. to ftp, + * telnet or web servers will be prevented by the aliasing mechanism. + */ +#define PKT_ALIAS_DENY_INCOMING 0x02 + +/* + * If PKT_ALIAS_SAME_PORTS is set, packets will be attempted sent from the + * same port as they originated on. This allows e.g. rsh to work *99% of the + * time*, but _not_ 100% (it will be slightly flakey instead of not working + * at all). This mode bit is set by PacketAliasInit(), so it is a default + * mode of operation. + */ +#define PKT_ALIAS_SAME_PORTS 0x04 + +/* + * If PKT_ALIAS_USE_SOCKETS is set, then when partially specified links (e.g. + * destination port and/or address is zero), the packet aliasing engine will + * attempt to allocate a socket for the aliasing port it chooses. This will + * avoid interference with the host machine. Fully specified links do not + * require this. This bit is set after a call to PacketAliasInit(), so it is + * a default mode of operation. + */ +#ifndef NO_USE_SOCKETS +#define PKT_ALIAS_USE_SOCKETS 0x08 +#endif +/*- + * If PKT_ALIAS_UNREGISTERED_ONLY is set, then only packets with + * unregistered source addresses will be aliased. Private + * addresses are those in the following ranges: + * + * 10.0.0.0 -> 10.255.255.255 + * 172.16.0.0 -> 172.31.255.255 + * 192.168.0.0 -> 192.168.255.255 + */ +#define PKT_ALIAS_UNREGISTERED_ONLY 0x10 + +/* + * If PKT_ALIAS_RESET_ON_ADDR_CHANGE is set, then the table of dynamic + * aliasing links will be reset whenever PacketAliasSetAddress() changes the + * default aliasing address. If the default aliasing address is left + * unchanged by this function call, then the table of dynamic aliasing links + * will be left intact. This bit is set after a call to PacketAliasInit(). + */ +#define PKT_ALIAS_RESET_ON_ADDR_CHANGE 0x20 + +/* + * If PKT_ALIAS_PROXY_ONLY is set, then NAT will be disabled and only + * transparent proxying is performed. + */ +#define PKT_ALIAS_PROXY_ONLY 0x40 + +/* + * If PKT_ALIAS_REVERSE is set, the actions of PacketAliasIn() and + * PacketAliasOut() are reversed. + */ +#define PKT_ALIAS_REVERSE 0x80 + +#ifndef NO_FW_PUNCH +/* + * If PKT_ALIAS_PUNCH_FW is set, active FTP and IRC DCC connections will + * create a 'hole' in the firewall to allow the transfers to work. The + * ipfw rule number that the hole is created with is controlled by + * PacketAliasSetFWBase(). The hole will be attached to that + * particular alias_link, so when the link goes away the hole is deleted. + */ +#define PKT_ALIAS_PUNCH_FW 0x100 +#endif + +/* + * If PKT_ALIAS_SKIP_GLOBAL is set, nat instance is not checked for matching + * states in 'ipfw nat global' rule. + */ +#define PKT_ALIAS_SKIP_GLOBAL 0x200 + +/* Function return codes. */ +#define PKT_ALIAS_ERROR -1 +#define PKT_ALIAS_OK 1 +#define PKT_ALIAS_IGNORED 2 +#define PKT_ALIAS_UNRESOLVED_FRAGMENT 3 +#define PKT_ALIAS_FOUND_HEADER_FRAGMENT 4 + +#endif /* !_ALIAS_H_ */ + +/* lint -restore */ diff --git a/tools/compat/include/netdb.h b/tools/compat/include/netdb.h index 3a1ae2d07..64e181231 100644 --- a/tools/compat/include/netdb.h +++ b/tools/compat/include/netdb.h @@ -235,9 +235,20 @@ struct protoent *getprotobynumber(int proto); void setprotoent(int stayopen); void endprotoent(void); +struct servent *getservent(void); +struct servent *getservbyname(const char *name, const char *proto); +struct servent *getservbyport(int port, const char *proto); +void setservent(int stayopen); +void endservent(void); + int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags); struct netent *getnetbyaddr(uint32_t net, int type); +struct hostent *gethostbyaddr(const void *addr, + socklen_t len, int type); + +struct hostent *gethostbyname2(const char *name, int af); + #endif /* !_NETDB_H_ */ diff --git a/tools/compat/include/netinet/icmp6.h b/tools/compat/include/netinet/icmp6.h new file mode 100644 index 000000000..cd7a2afd9 --- /dev/null +++ b/tools/compat/include/netinet/icmp6.h @@ -0,0 +1,784 @@ +/* $FreeBSD$ */ +/* $KAME: icmp6.h,v 1.46 2001/04/27 15:09:48 itojun Exp $ */ + +/*- + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ip_icmp.h 8.1 (Berkeley) 6/10/93 + */ + +#ifndef _NETINET_ICMP6_H_ +#define _NETINET_ICMP6_H_ + +#ifdef FSTACK +#define __packed __attribute__((__packed__)) +#endif + +#define ICMPV6_PLD_MAXLEN 1232 /* IPV6_MMTU - sizeof(struct ip6_hdr) + - sizeof(struct icmp6_hdr) */ + +struct icmp6_hdr { + u_int8_t icmp6_type; /* type field */ + u_int8_t icmp6_code; /* code field */ + u_int16_t icmp6_cksum; /* checksum field */ + union { + u_int32_t icmp6_un_data32[1]; /* type-specific field */ + u_int16_t icmp6_un_data16[2]; /* type-specific field */ + u_int8_t icmp6_un_data8[4]; /* type-specific field */ + } icmp6_dataun; +} __packed; + +#define icmp6_data32 icmp6_dataun.icmp6_un_data32 +#define icmp6_data16 icmp6_dataun.icmp6_un_data16 +#define icmp6_data8 icmp6_dataun.icmp6_un_data8 +#define icmp6_pptr icmp6_data32[0] /* parameter prob */ +#define icmp6_mtu icmp6_data32[0] /* packet too big */ +#define icmp6_id icmp6_data16[0] /* echo request/reply */ +#define icmp6_seq icmp6_data16[1] /* echo request/reply */ +#define icmp6_maxdelay icmp6_data16[0] /* mcast group membership */ + +#define ICMP6_DST_UNREACH 1 /* dest unreachable, codes: */ +#define ICMP6_PACKET_TOO_BIG 2 /* packet too big */ +#define ICMP6_TIME_EXCEEDED 3 /* time exceeded, code: */ +#define ICMP6_PARAM_PROB 4 /* ip6 header bad */ + +#define ICMP6_ECHO_REQUEST 128 /* echo service */ +#define ICMP6_ECHO_REPLY 129 /* echo reply */ +#define MLD_LISTENER_QUERY 130 /* multicast listener query */ +#define MLD_LISTENER_REPORT 131 /* multicast listener report */ +#define MLD_LISTENER_DONE 132 /* multicast listener done */ +#define MLD_LISTENER_REDUCTION MLD_LISTENER_DONE /* RFC3542 definition */ + +/* RFC2292 decls */ +#define ICMP6_MEMBERSHIP_QUERY 130 /* group membership query */ +#define ICMP6_MEMBERSHIP_REPORT 131 /* group membership report */ +#define ICMP6_MEMBERSHIP_REDUCTION 132 /* group membership termination */ + +#ifndef _KERNEL +/* the followings are for backward compatibility to old KAME apps. */ +#define MLD6_LISTENER_QUERY MLD_LISTENER_QUERY +#define MLD6_LISTENER_REPORT MLD_LISTENER_REPORT +#define MLD6_LISTENER_DONE MLD_LISTENER_DONE +#endif + +#define ND_ROUTER_SOLICIT 133 /* router solicitation */ +#define ND_ROUTER_ADVERT 134 /* router advertisement */ +#define ND_NEIGHBOR_SOLICIT 135 /* neighbor solicitation */ +#define ND_NEIGHBOR_ADVERT 136 /* neighbor advertisement */ +#define ND_REDIRECT 137 /* redirect */ + +#define ICMP6_ROUTER_RENUMBERING 138 /* router renumbering */ + +#define ICMP6_WRUREQUEST 139 /* who are you request */ +#define ICMP6_WRUREPLY 140 /* who are you reply */ +#define ICMP6_FQDN_QUERY 139 /* FQDN query */ +#define ICMP6_FQDN_REPLY 140 /* FQDN reply */ +#define ICMP6_NI_QUERY 139 /* node information request */ +#define ICMP6_NI_REPLY 140 /* node information reply */ +#define MLDV2_LISTENER_REPORT 143 /* RFC3810 listener report */ + +/* The definitions below are experimental. TBA */ +#define MLD_MTRACE_RESP 200 /* mtrace resp (to sender) */ +#define MLD_MTRACE 201 /* mtrace messages */ + +#ifndef _KERNEL +#define MLD6_MTRACE_RESP MLD_MTRACE_RESP +#define MLD6_MTRACE MLD_MTRACE +#endif + +#define ICMP6_MAXTYPE 201 + +#define ICMP6_DST_UNREACH_NOROUTE 0 /* no route to destination */ +#define ICMP6_DST_UNREACH_ADMIN 1 /* administratively prohibited */ +#define ICMP6_DST_UNREACH_NOTNEIGHBOR 2 /* not a neighbor(obsolete) */ +#define ICMP6_DST_UNREACH_BEYONDSCOPE 2 /* beyond scope of source address */ +#define ICMP6_DST_UNREACH_ADDR 3 /* address unreachable */ +#define ICMP6_DST_UNREACH_NOPORT 4 /* port unreachable */ +#define ICMP6_DST_UNREACH_POLICY 5 /* failed ingress/egress policy */ +#define ICMP6_DST_UNREACH_REJECT 6 /* Reject route to destination */ +#define ICMP6_DST_UNREACH_SRCROUTE 7 /* Error in source routing header */ + +#define ICMP6_TIME_EXCEED_TRANSIT 0 /* ttl==0 in transit */ +#define ICMP6_TIME_EXCEED_REASSEMBLY 1 /* ttl==0 in reass */ + +#define ICMP6_PARAMPROB_HEADER 0 /* erroneous header field */ +#define ICMP6_PARAMPROB_NEXTHEADER 1 /* unrecognized next header */ +#define ICMP6_PARAMPROB_OPTION 2 /* unrecognized option */ + +#define ICMP6_INFOMSG_MASK 0x80 /* all informational messages */ + +#define ICMP6_NI_SUBJ_IPV6 0 /* Query Subject is an IPv6 address */ +#define ICMP6_NI_SUBJ_FQDN 1 /* Query Subject is a Domain name */ +#define ICMP6_NI_SUBJ_IPV4 2 /* Query Subject is an IPv4 address */ + +#define ICMP6_NI_SUCCESS 0 /* node information successful reply */ +#define ICMP6_NI_REFUSED 1 /* node information request is refused */ +#define ICMP6_NI_UNKNOWN 2 /* unknown Qtype */ + +#define ICMP6_ROUTER_RENUMBERING_COMMAND 0 /* rr command */ +#define ICMP6_ROUTER_RENUMBERING_RESULT 1 /* rr result */ +#define ICMP6_ROUTER_RENUMBERING_SEQNUM_RESET 255 /* rr seq num reset */ + +/* Used in kernel only */ +#define ND_REDIRECT_ONLINK 0 /* redirect to an on-link node */ +#define ND_REDIRECT_ROUTER 1 /* redirect to a better router */ + +/* + * Multicast Listener Discovery + */ +struct mld_hdr { + struct icmp6_hdr mld_icmp6_hdr; + struct in6_addr mld_addr; /* multicast address */ +} __packed; + +/* definitions to provide backward compatibility to old KAME applications */ +#ifndef _KERNEL +#define mld6_hdr mld_hdr +#define mld6_type mld_type +#define mld6_code mld_code +#define mld6_cksum mld_cksum +#define mld6_maxdelay mld_maxdelay +#define mld6_reserved mld_reserved +#define mld6_addr mld_addr +#endif + +/* shortcut macro definitions */ +#define mld_type mld_icmp6_hdr.icmp6_type +#define mld_code mld_icmp6_hdr.icmp6_code +#define mld_cksum mld_icmp6_hdr.icmp6_cksum +#define mld_maxdelay mld_icmp6_hdr.icmp6_data16[0] +#define mld_reserved mld_icmp6_hdr.icmp6_data16[1] +#define mld_v2_reserved mld_icmp6_hdr.icmp6_data16[0] +#define mld_v2_numrecs mld_icmp6_hdr.icmp6_data16[1] + +/* + * Neighbor Discovery + */ + +struct nd_router_solicit { /* router solicitation */ + struct icmp6_hdr nd_rs_hdr; + /* could be followed by options */ +} __packed; + +#define nd_rs_type nd_rs_hdr.icmp6_type +#define nd_rs_code nd_rs_hdr.icmp6_code +#define nd_rs_cksum nd_rs_hdr.icmp6_cksum +#define nd_rs_reserved nd_rs_hdr.icmp6_data32[0] + +struct nd_router_advert { /* router advertisement */ + struct icmp6_hdr nd_ra_hdr; + u_int32_t nd_ra_reachable; /* reachable time */ + u_int32_t nd_ra_retransmit; /* retransmit timer */ + /* could be followed by options */ +} __packed; + +#define nd_ra_type nd_ra_hdr.icmp6_type +#define nd_ra_code nd_ra_hdr.icmp6_code +#define nd_ra_cksum nd_ra_hdr.icmp6_cksum +#define nd_ra_curhoplimit nd_ra_hdr.icmp6_data8[0] +#define nd_ra_flags_reserved nd_ra_hdr.icmp6_data8[1] +#define ND_RA_FLAG_MANAGED 0x80 +#define ND_RA_FLAG_OTHER 0x40 +#define ND_RA_FLAG_HA 0x20 + +/* + * Router preference values based on draft-draves-ipngwg-router-selection-01. + * These are non-standard definitions. + */ +#define ND_RA_FLAG_RTPREF_MASK 0x18 /* 00011000 */ + +#define ND_RA_FLAG_RTPREF_HIGH 0x08 /* 00001000 */ +#define ND_RA_FLAG_RTPREF_MEDIUM 0x00 /* 00000000 */ +#define ND_RA_FLAG_RTPREF_LOW 0x18 /* 00011000 */ +#define ND_RA_FLAG_RTPREF_RSV 0x10 /* 00010000 */ + +#define nd_ra_router_lifetime nd_ra_hdr.icmp6_data16[1] + +struct nd_neighbor_solicit { /* neighbor solicitation */ + struct icmp6_hdr nd_ns_hdr; + struct in6_addr nd_ns_target; /*target address */ + /* could be followed by options */ +} __packed; + +#define nd_ns_type nd_ns_hdr.icmp6_type +#define nd_ns_code nd_ns_hdr.icmp6_code +#define nd_ns_cksum nd_ns_hdr.icmp6_cksum +#define nd_ns_reserved nd_ns_hdr.icmp6_data32[0] + +struct nd_neighbor_advert { /* neighbor advertisement */ + struct icmp6_hdr nd_na_hdr; + struct in6_addr nd_na_target; /* target address */ + /* could be followed by options */ +} __packed; + +#define nd_na_type nd_na_hdr.icmp6_type +#define nd_na_code nd_na_hdr.icmp6_code +#define nd_na_cksum nd_na_hdr.icmp6_cksum +#define nd_na_flags_reserved nd_na_hdr.icmp6_data32[0] +#if BYTE_ORDER == BIG_ENDIAN +#define ND_NA_FLAG_ROUTER 0x80000000 +#define ND_NA_FLAG_SOLICITED 0x40000000 +#define ND_NA_FLAG_OVERRIDE 0x20000000 +#else +#if BYTE_ORDER == LITTLE_ENDIAN +#define ND_NA_FLAG_ROUTER 0x80 +#define ND_NA_FLAG_SOLICITED 0x40 +#define ND_NA_FLAG_OVERRIDE 0x20 +#endif +#endif + +struct nd_redirect { /* redirect */ + struct icmp6_hdr nd_rd_hdr; + struct in6_addr nd_rd_target; /* target address */ + struct in6_addr nd_rd_dst; /* destination address */ + /* could be followed by options */ +} __packed; + +#define nd_rd_type nd_rd_hdr.icmp6_type +#define nd_rd_code nd_rd_hdr.icmp6_code +#define nd_rd_cksum nd_rd_hdr.icmp6_cksum +#define nd_rd_reserved nd_rd_hdr.icmp6_data32[0] + +struct nd_opt_hdr { /* Neighbor discovery option header */ + u_int8_t nd_opt_type; + u_int8_t nd_opt_len; + /* followed by option specific data*/ +} __packed; + +#define ND_OPT_SOURCE_LINKADDR 1 +#define ND_OPT_TARGET_LINKADDR 2 +#define ND_OPT_PREFIX_INFORMATION 3 +#define ND_OPT_REDIRECTED_HEADER 4 +#define ND_OPT_MTU 5 +#define ND_OPT_NONCE 14 /* RFC 3971 */ +#define ND_OPT_ROUTE_INFO 24 /* RFC 4191 */ +#define ND_OPT_RDNSS 25 /* RFC 6106 */ +#define ND_OPT_DNSSL 31 /* RFC 6106 */ +#define ND_OPT_MAX 31 + +struct nd_opt_prefix_info { /* prefix information */ + u_int8_t nd_opt_pi_type; + u_int8_t nd_opt_pi_len; + u_int8_t nd_opt_pi_prefix_len; + u_int8_t nd_opt_pi_flags_reserved; + u_int32_t nd_opt_pi_valid_time; + u_int32_t nd_opt_pi_preferred_time; + u_int32_t nd_opt_pi_reserved2; + struct in6_addr nd_opt_pi_prefix; +} __packed; + +#define ND_OPT_PI_FLAG_ONLINK 0x80 +#define ND_OPT_PI_FLAG_AUTO 0x40 + +struct nd_opt_rd_hdr { /* redirected header */ + u_int8_t nd_opt_rh_type; + u_int8_t nd_opt_rh_len; + u_int16_t nd_opt_rh_reserved1; + u_int32_t nd_opt_rh_reserved2; + /* followed by IP header and data */ +} __packed; + +struct nd_opt_mtu { /* MTU option */ + u_int8_t nd_opt_mtu_type; + u_int8_t nd_opt_mtu_len; + u_int16_t nd_opt_mtu_reserved; + u_int32_t nd_opt_mtu_mtu; +} __packed; + +#define ND_OPT_NONCE_LEN ((1 * 8) - 2) +#if ((ND_OPT_NONCE_LEN + 2) % 8) != 0 +#error "(ND_OPT_NONCE_LEN + 2) must be a multiple of 8." +#endif +struct nd_opt_nonce { /* nonce option */ + u_int8_t nd_opt_nonce_type; + u_int8_t nd_opt_nonce_len; + u_int8_t nd_opt_nonce[ND_OPT_NONCE_LEN]; +} __packed; + +struct nd_opt_route_info { /* route info */ + u_int8_t nd_opt_rti_type; + u_int8_t nd_opt_rti_len; + u_int8_t nd_opt_rti_prefixlen; + u_int8_t nd_opt_rti_flags; + u_int32_t nd_opt_rti_lifetime; + /* prefix follows */ +} __packed; + +struct nd_opt_rdnss { /* RDNSS option (RFC 6106) */ + u_int8_t nd_opt_rdnss_type; + u_int8_t nd_opt_rdnss_len; + u_int16_t nd_opt_rdnss_reserved; + u_int32_t nd_opt_rdnss_lifetime; + /* followed by list of recursive DNS servers */ +} __packed; + +struct nd_opt_dnssl { /* DNSSL option (RFC 6106) */ + u_int8_t nd_opt_dnssl_type; + u_int8_t nd_opt_dnssl_len; + u_int16_t nd_opt_dnssl_reserved; + u_int32_t nd_opt_dnssl_lifetime; + /* followed by list of DNS search domains */ +} __packed; + +/* + * icmp6 namelookup + */ + +struct icmp6_namelookup { + struct icmp6_hdr icmp6_nl_hdr; + u_int8_t icmp6_nl_nonce[8]; + int32_t icmp6_nl_ttl; +#if 0 + u_int8_t icmp6_nl_len; + u_int8_t icmp6_nl_name[3]; +#endif + /* could be followed by options */ +} __packed; + +/* + * icmp6 node information + */ +struct icmp6_nodeinfo { + struct icmp6_hdr icmp6_ni_hdr; + u_int8_t icmp6_ni_nonce[8]; + /* could be followed by reply data */ +} __packed; + +#define ni_type icmp6_ni_hdr.icmp6_type +#define ni_code icmp6_ni_hdr.icmp6_code +#define ni_cksum icmp6_ni_hdr.icmp6_cksum +#define ni_qtype icmp6_ni_hdr.icmp6_data16[0] +#define ni_flags icmp6_ni_hdr.icmp6_data16[1] + +#define NI_QTYPE_NOOP 0 /* NOOP */ +#define NI_QTYPE_SUPTYPES 1 /* Supported Qtypes */ +#define NI_QTYPE_FQDN 2 /* FQDN (draft 04) */ +#define NI_QTYPE_DNSNAME 2 /* DNS Name */ +#define NI_QTYPE_NODEADDR 3 /* Node Addresses */ +#define NI_QTYPE_IPV4ADDR 4 /* IPv4 Addresses */ + +#if BYTE_ORDER == BIG_ENDIAN +#define NI_SUPTYPE_FLAG_COMPRESS 0x1 +#define NI_FQDN_FLAG_VALIDTTL 0x1 +#elif BYTE_ORDER == LITTLE_ENDIAN +#define NI_SUPTYPE_FLAG_COMPRESS 0x0100 +#define NI_FQDN_FLAG_VALIDTTL 0x0100 +#endif + +#ifdef NAME_LOOKUPS_04 +#if BYTE_ORDER == BIG_ENDIAN +#define NI_NODEADDR_FLAG_LINKLOCAL 0x1 +#define NI_NODEADDR_FLAG_SITELOCAL 0x2 +#define NI_NODEADDR_FLAG_GLOBAL 0x4 +#define NI_NODEADDR_FLAG_ALL 0x8 +#define NI_NODEADDR_FLAG_TRUNCATE 0x10 +#define NI_NODEADDR_FLAG_ANYCAST 0x20 /* just experimental. not in spec */ +#elif BYTE_ORDER == LITTLE_ENDIAN +#define NI_NODEADDR_FLAG_LINKLOCAL 0x0100 +#define NI_NODEADDR_FLAG_SITELOCAL 0x0200 +#define NI_NODEADDR_FLAG_GLOBAL 0x0400 +#define NI_NODEADDR_FLAG_ALL 0x0800 +#define NI_NODEADDR_FLAG_TRUNCATE 0x1000 +#define NI_NODEADDR_FLAG_ANYCAST 0x2000 /* just experimental. not in spec */ +#endif +#else /* draft-ietf-ipngwg-icmp-name-lookups-05 (and later?) */ +#if BYTE_ORDER == BIG_ENDIAN +#define NI_NODEADDR_FLAG_TRUNCATE 0x1 +#define NI_NODEADDR_FLAG_ALL 0x2 +#define NI_NODEADDR_FLAG_COMPAT 0x4 +#define NI_NODEADDR_FLAG_LINKLOCAL 0x8 +#define NI_NODEADDR_FLAG_SITELOCAL 0x10 +#define NI_NODEADDR_FLAG_GLOBAL 0x20 +#define NI_NODEADDR_FLAG_ANYCAST 0x40 /* just experimental. not in spec */ +#elif BYTE_ORDER == LITTLE_ENDIAN +#define NI_NODEADDR_FLAG_TRUNCATE 0x0100 +#define NI_NODEADDR_FLAG_ALL 0x0200 +#define NI_NODEADDR_FLAG_COMPAT 0x0400 +#define NI_NODEADDR_FLAG_LINKLOCAL 0x0800 +#define NI_NODEADDR_FLAG_SITELOCAL 0x1000 +#define NI_NODEADDR_FLAG_GLOBAL 0x2000 +#define NI_NODEADDR_FLAG_ANYCAST 0x4000 /* just experimental. not in spec */ +#endif +#endif + +struct ni_reply_fqdn { + u_int32_t ni_fqdn_ttl; /* TTL */ + u_int8_t ni_fqdn_namelen; /* length in octets of the FQDN */ + u_int8_t ni_fqdn_name[3]; /* XXX: alignment */ +} __packed; + +/* + * Router Renumbering. as router-renum-08.txt + */ +struct icmp6_router_renum { /* router renumbering header */ + struct icmp6_hdr rr_hdr; + u_int8_t rr_segnum; + u_int8_t rr_flags; + u_int16_t rr_maxdelay; + u_int32_t rr_reserved; +} __packed; + +#define ICMP6_RR_FLAGS_TEST 0x80 +#define ICMP6_RR_FLAGS_REQRESULT 0x40 +#define ICMP6_RR_FLAGS_FORCEAPPLY 0x20 +#define ICMP6_RR_FLAGS_SPECSITE 0x10 +#define ICMP6_RR_FLAGS_PREVDONE 0x08 + +#define rr_type rr_hdr.icmp6_type +#define rr_code rr_hdr.icmp6_code +#define rr_cksum rr_hdr.icmp6_cksum +#define rr_seqnum rr_hdr.icmp6_data32[0] + +struct rr_pco_match { /* match prefix part */ + u_int8_t rpm_code; + u_int8_t rpm_len; + u_int8_t rpm_ordinal; + u_int8_t rpm_matchlen; + u_int8_t rpm_minlen; + u_int8_t rpm_maxlen; + u_int16_t rpm_reserved; + struct in6_addr rpm_prefix; +} __packed; + +#define RPM_PCO_ADD 1 +#define RPM_PCO_CHANGE 2 +#define RPM_PCO_SETGLOBAL 3 +#define RPM_PCO_MAX 4 + +struct rr_pco_use { /* use prefix part */ + u_int8_t rpu_uselen; + u_int8_t rpu_keeplen; + u_int8_t rpu_ramask; + u_int8_t rpu_raflags; + u_int32_t rpu_vltime; + u_int32_t rpu_pltime; + u_int32_t rpu_flags; + struct in6_addr rpu_prefix; +} __packed; +#define ICMP6_RR_PCOUSE_RAFLAGS_ONLINK 0x80 +#define ICMP6_RR_PCOUSE_RAFLAGS_AUTO 0x40 + +#if BYTE_ORDER == BIG_ENDIAN +#define ICMP6_RR_PCOUSE_FLAGS_DECRVLTIME 0x80000000 +#define ICMP6_RR_PCOUSE_FLAGS_DECRPLTIME 0x40000000 +#elif BYTE_ORDER == LITTLE_ENDIAN +#define ICMP6_RR_PCOUSE_FLAGS_DECRVLTIME 0x80 +#define ICMP6_RR_PCOUSE_FLAGS_DECRPLTIME 0x40 +#endif + +struct rr_result { /* router renumbering result message */ + u_int16_t rrr_flags; + u_int8_t rrr_ordinal; + u_int8_t rrr_matchedlen; + u_int32_t rrr_ifid; + struct in6_addr rrr_prefix; +} __packed; +#if BYTE_ORDER == BIG_ENDIAN +#define ICMP6_RR_RESULT_FLAGS_OOB 0x0002 +#define ICMP6_RR_RESULT_FLAGS_FORBIDDEN 0x0001 +#elif BYTE_ORDER == LITTLE_ENDIAN +#define ICMP6_RR_RESULT_FLAGS_OOB 0x0200 +#define ICMP6_RR_RESULT_FLAGS_FORBIDDEN 0x0100 +#endif + +/* + * icmp6 filter structures. + */ + +struct icmp6_filter { + u_int32_t icmp6_filt[8]; +}; + +#ifdef _KERNEL +#define ICMP6_FILTER_SETPASSALL(filterp) \ +do { \ + int i; u_char *p; \ + p = (u_char *)filterp; \ + for (i = 0; i < sizeof(struct icmp6_filter); i++) \ + p[i] = 0xff; \ +} while (/*CONSTCOND*/ 0) +#define ICMP6_FILTER_SETBLOCKALL(filterp) \ + bzero(filterp, sizeof(struct icmp6_filter)) +#else /* _KERNEL */ +#define ICMP6_FILTER_SETPASSALL(filterp) \ + memset(filterp, 0xff, sizeof(struct icmp6_filter)) +#define ICMP6_FILTER_SETBLOCKALL(filterp) \ + memset(filterp, 0x00, sizeof(struct icmp6_filter)) +#endif /* _KERNEL */ + +#define ICMP6_FILTER_SETPASS(type, filterp) \ + (((filterp)->icmp6_filt[(type) >> 5]) |= (1 << ((type) & 31))) +#define ICMP6_FILTER_SETBLOCK(type, filterp) \ + (((filterp)->icmp6_filt[(type) >> 5]) &= ~(1 << ((type) & 31))) +#define ICMP6_FILTER_WILLPASS(type, filterp) \ + ((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) != 0) +#define ICMP6_FILTER_WILLBLOCK(type, filterp) \ + ((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) == 0) + +/* + * Variables related to this implementation + * of the internet control message protocol version 6. + */ +struct icmp6errstat { + uint64_t icp6errs_dst_unreach_noroute; + uint64_t icp6errs_dst_unreach_admin; + uint64_t icp6errs_dst_unreach_beyondscope; + uint64_t icp6errs_dst_unreach_addr; + uint64_t icp6errs_dst_unreach_noport; + uint64_t icp6errs_packet_too_big; + uint64_t icp6errs_time_exceed_transit; + uint64_t icp6errs_time_exceed_reassembly; + uint64_t icp6errs_paramprob_header; + uint64_t icp6errs_paramprob_nextheader; + uint64_t icp6errs_paramprob_option; + uint64_t icp6errs_redirect; /* we regard redirect as an error here */ + uint64_t icp6errs_unknown; +}; + +struct icmp6stat { +/* statistics related to icmp6 packets generated */ + uint64_t icp6s_error; /* # of calls to icmp6_error */ + uint64_t icp6s_canterror; /* no error 'cuz old was icmp */ + uint64_t icp6s_toofreq; /* no error 'cuz rate limitation */ + uint64_t icp6s_outhist[256]; +/* statistics related to input message processed */ + uint64_t icp6s_badcode; /* icmp6_code out of range */ + uint64_t icp6s_tooshort; /* packet < sizeof(struct icmp6_hdr) */ + uint64_t icp6s_checksum; /* bad checksum */ + uint64_t icp6s_badlen; /* calculated bound mismatch */ + /* + * number of responses: this member is inherited from netinet code, but + * for netinet6 code, it is already available in icp6s_outhist[]. + */ + uint64_t icp6s_reflect; + uint64_t icp6s_inhist[256]; + uint64_t icp6s_nd_toomanyopt; /* too many ND options */ + struct icmp6errstat icp6s_outerrhist; +#define icp6s_odst_unreach_noroute \ + icp6s_outerrhist.icp6errs_dst_unreach_noroute +#define icp6s_odst_unreach_admin icp6s_outerrhist.icp6errs_dst_unreach_admin +#define icp6s_odst_unreach_beyondscope \ + icp6s_outerrhist.icp6errs_dst_unreach_beyondscope +#define icp6s_odst_unreach_addr icp6s_outerrhist.icp6errs_dst_unreach_addr +#define icp6s_odst_unreach_noport icp6s_outerrhist.icp6errs_dst_unreach_noport +#define icp6s_opacket_too_big icp6s_outerrhist.icp6errs_packet_too_big +#define icp6s_otime_exceed_transit \ + icp6s_outerrhist.icp6errs_time_exceed_transit +#define icp6s_otime_exceed_reassembly \ + icp6s_outerrhist.icp6errs_time_exceed_reassembly +#define icp6s_oparamprob_header icp6s_outerrhist.icp6errs_paramprob_header +#define icp6s_oparamprob_nextheader \ + icp6s_outerrhist.icp6errs_paramprob_nextheader +#define icp6s_oparamprob_option icp6s_outerrhist.icp6errs_paramprob_option +#define icp6s_oredirect icp6s_outerrhist.icp6errs_redirect +#define icp6s_ounknown icp6s_outerrhist.icp6errs_unknown + uint64_t icp6s_pmtuchg; /* path MTU changes */ + uint64_t icp6s_nd_badopt; /* bad ND options */ + uint64_t icp6s_badns; /* bad neighbor solicitation */ + uint64_t icp6s_badna; /* bad neighbor advertisement */ + uint64_t icp6s_badrs; /* bad router advertisement */ + uint64_t icp6s_badra; /* bad router advertisement */ + uint64_t icp6s_badredirect; /* bad redirect message */ +}; + +#ifdef _KERNEL +#include + +VNET_PCPUSTAT_DECLARE(struct icmp6stat, icmp6stat); +/* + * In-kernel consumers can use these accessor macros directly to update + * stats. + */ +#define ICMP6STAT_ADD(name, val) \ + VNET_PCPUSTAT_ADD(struct icmp6stat, icmp6stat, name, (val)) +#define ICMP6STAT_INC(name) ICMP6STAT_ADD(name, 1) + +/* + * Kernel module consumers must use this accessor macro. + */ +void kmod_icmp6stat_inc(int statnum); +#define KMOD_ICMP6STAT_INC(name) \ + kmod_icmp6stat_inc(offsetof(struct icmp6stat, name) / sizeof(uint64_t)) +#endif + +/* + * Names for ICMP sysctl objects + */ +#define ICMPV6CTL_STATS 1 +#define ICMPV6CTL_REDIRACCEPT 2 /* accept/process redirects */ +#define ICMPV6CTL_REDIRTIMEOUT 3 /* redirect cache time */ +#if 0 /*obsoleted*/ +#define ICMPV6CTL_ERRRATELIMIT 5 /* ICMPv6 error rate limitation */ +#endif +#define ICMPV6CTL_ND6_PRUNE 6 +#define ICMPV6CTL_ND6_DELAY 8 +#define ICMPV6CTL_ND6_UMAXTRIES 9 +#define ICMPV6CTL_ND6_MMAXTRIES 10 +#define ICMPV6CTL_ND6_USELOOPBACK 11 +/*#define ICMPV6CTL_ND6_PROXYALL 12 obsoleted, do not reuse here */ +#define ICMPV6CTL_NODEINFO 13 +#define ICMPV6CTL_ERRPPSLIMIT 14 /* ICMPv6 error pps limitation */ +#define ICMPV6CTL_ND6_MAXNUDHINT 15 +#define ICMPV6CTL_MTUDISC_HIWAT 16 +#define ICMPV6CTL_MTUDISC_LOWAT 17 +#define ICMPV6CTL_ND6_DEBUG 18 +#define ICMPV6CTL_ND6_DRLIST 19 +#define ICMPV6CTL_ND6_PRLIST 20 +#define ICMPV6CTL_MLD_MAXSRCFILTER 21 +#define ICMPV6CTL_MLD_SOMAXSRC 22 +#define ICMPV6CTL_MLD_VERSION 23 +#define ICMPV6CTL_ND6_MAXQLEN 24 +#define ICMPV6CTL_NODEINFO_OLDMCPREFIX 25 +#define ICMPV6CTL_MAXID 26 + +#define RTF_PROBEMTU RTF_PROTO1 + +#ifdef _KERNEL +# ifdef __STDC__ +struct rtentry; +struct rttimer; +struct in6_multi; +# endif +void icmp6_paramerror(struct mbuf *, int); +void icmp6_error(struct mbuf *, int, int, int); +void icmp6_error2(struct mbuf *, int, int, int, struct ifnet *); +int icmp6_input(struct mbuf **, int *, int); +void icmp6_fasttimo(void); +void icmp6_slowtimo(void); +void icmp6_reflect(struct mbuf *, size_t); +void icmp6_prepare(struct mbuf *); +void icmp6_redirect_input(struct mbuf *, int); +void icmp6_redirect_output(struct mbuf *, struct rtentry *); + +struct ip6ctlparam; +void icmp6_mtudisc_update(struct ip6ctlparam *, int); + +/* XXX: is this the right place for these macros? */ +#define icmp6_ifstat_inc(ifp, tag) \ +do { \ + if (ifp) \ + counter_u64_add(((struct in6_ifextra *) \ + ((ifp)->if_afdata[AF_INET6]))->icmp6_ifstat[\ + offsetof(struct icmp6_ifstat, tag) / sizeof(uint64_t)], 1);\ +} while (/*CONSTCOND*/ 0) + +#define icmp6_ifoutstat_inc(ifp, type, code) \ +do { \ + icmp6_ifstat_inc(ifp, ifs6_out_msg); \ + if (type < ICMP6_INFOMSG_MASK) \ + icmp6_ifstat_inc(ifp, ifs6_out_error); \ + switch (type) { \ + case ICMP6_DST_UNREACH: \ + icmp6_ifstat_inc(ifp, ifs6_out_dstunreach); \ + if (code == ICMP6_DST_UNREACH_ADMIN) \ + icmp6_ifstat_inc(ifp, ifs6_out_adminprohib); \ + break; \ + case ICMP6_PACKET_TOO_BIG: \ + icmp6_ifstat_inc(ifp, ifs6_out_pkttoobig); \ + break; \ + case ICMP6_TIME_EXCEEDED: \ + icmp6_ifstat_inc(ifp, ifs6_out_timeexceed); \ + break; \ + case ICMP6_PARAM_PROB: \ + icmp6_ifstat_inc(ifp, ifs6_out_paramprob); \ + break; \ + case ICMP6_ECHO_REQUEST: \ + icmp6_ifstat_inc(ifp, ifs6_out_echo); \ + break; \ + case ICMP6_ECHO_REPLY: \ + icmp6_ifstat_inc(ifp, ifs6_out_echoreply); \ + break; \ + case MLD_LISTENER_QUERY: \ + icmp6_ifstat_inc(ifp, ifs6_out_mldquery); \ + break; \ + case MLD_LISTENER_REPORT: \ + icmp6_ifstat_inc(ifp, ifs6_out_mldreport); \ + break; \ + case MLD_LISTENER_DONE: \ + icmp6_ifstat_inc(ifp, ifs6_out_mlddone); \ + break; \ + case ND_ROUTER_SOLICIT: \ + icmp6_ifstat_inc(ifp, ifs6_out_routersolicit); \ + break; \ + case ND_ROUTER_ADVERT: \ + icmp6_ifstat_inc(ifp, ifs6_out_routeradvert); \ + break; \ + case ND_NEIGHBOR_SOLICIT: \ + icmp6_ifstat_inc(ifp, ifs6_out_neighborsolicit); \ + break; \ + case ND_NEIGHBOR_ADVERT: \ + icmp6_ifstat_inc(ifp, ifs6_out_neighboradvert); \ + break; \ + case ND_REDIRECT: \ + icmp6_ifstat_inc(ifp, ifs6_out_redirect); \ + break; \ + } \ +} while (/*CONSTCOND*/ 0) + +VNET_DECLARE(int, icmp6_rediraccept); /* accept/process redirects */ +VNET_DECLARE(int, icmp6_redirtimeout); /* cache time for redirect routes */ + +#define V_icmp6_rediraccept VNET(icmp6_rediraccept) +#define V_icmp6_redirtimeout VNET(icmp6_redirtimeout) + +#define ICMP6_NODEINFO_FQDNOK 0x1 +#define ICMP6_NODEINFO_NODEADDROK 0x2 +#define ICMP6_NODEINFO_TMPADDROK 0x4 +#define ICMP6_NODEINFO_GLOBALOK 0x8 +#endif /* _KERNEL */ + +#endif /* not _NETINET_ICMP6_H_ */ diff --git a/tools/compat/include/netinet/ip_fw.h b/tools/compat/include/netinet/ip_fw.h new file mode 100644 index 000000000..6475fe088 --- /dev/null +++ b/tools/compat/include/netinet/ip_fw.h @@ -0,0 +1,1019 @@ +/*- + * Copyright (c) 2002-2009 Luigi Rizzo, Universita` di Pisa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _IPFW2_H +#define _IPFW2_H + +/* + * The default rule number. By the design of ip_fw, the default rule + * is the last one, so its number can also serve as the highest number + * allowed for a rule. The ip_fw code relies on both meanings of this + * constant. + */ +#define IPFW_DEFAULT_RULE 65535 + +#define RESVD_SET 31 /*set for default and persistent rules*/ +#define IPFW_MAX_SETS 32 /* Number of sets supported by ipfw*/ + +/* + * Compat values for old clients + */ +#ifndef _KERNEL +#define IPFW_TABLES_MAX 65535 +#define IPFW_TABLES_DEFAULT 128 +#endif + +/* + * Most commands (queue, pipe, tag, untag, limit...) can have a 16-bit + * argument between 1 and 65534. The value 0 (IP_FW_TARG) is used + * to represent 'tablearg' value, e.g. indicate the use of a 'tablearg' + * result of the most recent table() lookup. + * Note that 16bit is only a historical limit, resulting from + * the use of a 16-bit fields for that value. In reality, we can have + * 2^32 pipes, queues, tag values and so on. + */ +#define IPFW_ARG_MIN 1 +#define IPFW_ARG_MAX 65534 +#define IP_FW_TABLEARG 65535 /* Compat value for old clients */ +#define IP_FW_TARG 0 /* Current tablearg value */ +#define IP_FW_NAT44_GLOBAL 65535 /* arg1 value for "nat global" */ + +/* + * Number of entries in the call stack of the call/return commands. + * Call stack currently is an uint16_t array with rule numbers. + */ +#define IPFW_CALLSTACK_SIZE 16 + +/* IP_FW3 header/opcodes */ +typedef struct _ip_fw3_opheader { + uint16_t opcode; /* Operation opcode */ + uint16_t version; /* Opcode version */ + uint16_t reserved[2]; /* Align to 64-bit boundary */ +} ip_fw3_opheader; + +/* IP_FW3 opcodes */ +#define IP_FW_TABLE_XADD 86 /* add entry */ +#define IP_FW_TABLE_XDEL 87 /* delete entry */ +#define IP_FW_TABLE_XGETSIZE 88 /* get table size (deprecated) */ +#define IP_FW_TABLE_XLIST 89 /* list table contents */ +#define IP_FW_TABLE_XDESTROY 90 /* destroy table */ +#define IP_FW_TABLES_XLIST 92 /* list all tables */ +#define IP_FW_TABLE_XINFO 93 /* request info for one table */ +#define IP_FW_TABLE_XFLUSH 94 /* flush table data */ +#define IP_FW_TABLE_XCREATE 95 /* create new table */ +#define IP_FW_TABLE_XMODIFY 96 /* modify existing table */ +#define IP_FW_XGET 97 /* Retrieve configuration */ +#define IP_FW_XADD 98 /* add rule */ +#define IP_FW_XDEL 99 /* del rule */ +#define IP_FW_XMOVE 100 /* move rules to different set */ +#define IP_FW_XZERO 101 /* clear accounting */ +#define IP_FW_XRESETLOG 102 /* zero rules logs */ +#define IP_FW_SET_SWAP 103 /* Swap between 2 sets */ +#define IP_FW_SET_MOVE 104 /* Move one set to another one */ +#define IP_FW_SET_ENABLE 105 /* Enable/disable sets */ +#define IP_FW_TABLE_XFIND 106 /* finds an entry */ +#define IP_FW_XIFLIST 107 /* list tracked interfaces */ +#define IP_FW_TABLES_ALIST 108 /* list table algorithms */ +#define IP_FW_TABLE_XSWAP 109 /* swap two tables */ +#define IP_FW_TABLE_VLIST 110 /* dump table value hash */ + +#define IP_FW_NAT44_XCONFIG 111 /* Create/modify NAT44 instance */ +#define IP_FW_NAT44_DESTROY 112 /* Destroys NAT44 instance */ +#define IP_FW_NAT44_XGETCONFIG 113 /* Get NAT44 instance config */ +#define IP_FW_NAT44_LIST_NAT 114 /* List all NAT44 instances */ +#define IP_FW_NAT44_XGETLOG 115 /* Get log from NAT44 instance */ + +#define IP_FW_DUMP_SOPTCODES 116 /* Dump available sopts/versions */ +#define IP_FW_DUMP_SRVOBJECTS 117 /* Dump existing named objects */ + +/* + * The kernel representation of ipfw rules is made of a list of + * 'instructions' (for all practical purposes equivalent to BPF + * instructions), which specify which fields of the packet + * (or its metadata) should be analysed. + * + * Each instruction is stored in a structure which begins with + * "ipfw_insn", and can contain extra fields depending on the + * instruction type (listed below). + * Note that the code is written so that individual instructions + * have a size which is a multiple of 32 bits. This means that, if + * such structures contain pointers or other 64-bit entities, + * (there is just one instance now) they may end up unaligned on + * 64-bit architectures, so the must be handled with care. + * + * "enum ipfw_opcodes" are the opcodes supported. We can have up + * to 256 different opcodes. When adding new opcodes, they should + * be appended to the end of the opcode list before O_LAST_OPCODE, + * this will prevent the ABI from being broken, otherwise users + * will have to recompile ipfw(8) when they update the kernel. + */ + +enum ipfw_opcodes { /* arguments (4 byte each) */ + O_NOP, + + O_IP_SRC, /* u32 = IP */ + O_IP_SRC_MASK, /* ip = IP/mask */ + O_IP_SRC_ME, /* none */ + O_IP_SRC_SET, /* u32=base, arg1=len, bitmap */ + + O_IP_DST, /* u32 = IP */ + O_IP_DST_MASK, /* ip = IP/mask */ + O_IP_DST_ME, /* none */ + O_IP_DST_SET, /* u32=base, arg1=len, bitmap */ + + O_IP_SRCPORT, /* (n)port list:mask 4 byte ea */ + O_IP_DSTPORT, /* (n)port list:mask 4 byte ea */ + O_PROTO, /* arg1=protocol */ + + O_MACADDR2, /* 2 mac addr:mask */ + O_MAC_TYPE, /* same as srcport */ + + O_LAYER2, /* none */ + O_IN, /* none */ + O_FRAG, /* none */ + + O_RECV, /* none */ + O_XMIT, /* none */ + O_VIA, /* none */ + + O_IPOPT, /* arg1 = 2*u8 bitmap */ + O_IPLEN, /* arg1 = len */ + O_IPID, /* arg1 = id */ + + O_IPTOS, /* arg1 = id */ + O_IPPRECEDENCE, /* arg1 = precedence << 5 */ + O_IPTTL, /* arg1 = TTL */ + + O_IPVER, /* arg1 = version */ + O_UID, /* u32 = id */ + O_GID, /* u32 = id */ + O_ESTAB, /* none (tcp established) */ + O_TCPFLAGS, /* arg1 = 2*u8 bitmap */ + O_TCPWIN, /* arg1 = desired win */ + O_TCPSEQ, /* u32 = desired seq. */ + O_TCPACK, /* u32 = desired seq. */ + O_ICMPTYPE, /* u32 = icmp bitmap */ + O_TCPOPTS, /* arg1 = 2*u8 bitmap */ + + O_VERREVPATH, /* none */ + O_VERSRCREACH, /* none */ + + O_PROBE_STATE, /* none */ + O_KEEP_STATE, /* none */ + O_LIMIT, /* ipfw_insn_limit */ + O_LIMIT_PARENT, /* dyn_type, not an opcode. */ + + /* + * These are really 'actions'. + */ + + O_LOG, /* ipfw_insn_log */ + O_PROB, /* u32 = match probability */ + + O_CHECK_STATE, /* none */ + O_ACCEPT, /* none */ + O_DENY, /* none */ + O_REJECT, /* arg1=icmp arg (same as deny) */ + O_COUNT, /* none */ + O_SKIPTO, /* arg1=next rule number */ + O_PIPE, /* arg1=pipe number */ + O_QUEUE, /* arg1=queue number */ + O_DIVERT, /* arg1=port number */ + O_TEE, /* arg1=port number */ + O_FORWARD_IP, /* fwd sockaddr */ + O_FORWARD_MAC, /* fwd mac */ + O_NAT, /* nope */ + O_REASS, /* none */ + + /* + * More opcodes. + */ + O_IPSEC, /* has ipsec history */ + O_IP_SRC_LOOKUP, /* arg1=table number, u32=value */ + O_IP_DST_LOOKUP, /* arg1=table number, u32=value */ + O_ANTISPOOF, /* none */ + O_JAIL, /* u32 = id */ + O_ALTQ, /* u32 = altq classif. qid */ + O_DIVERTED, /* arg1=bitmap (1:loop, 2:out) */ + O_TCPDATALEN, /* arg1 = tcp data len */ + O_IP6_SRC, /* address without mask */ + O_IP6_SRC_ME, /* my addresses */ + O_IP6_SRC_MASK, /* address with the mask */ + O_IP6_DST, + O_IP6_DST_ME, + O_IP6_DST_MASK, + O_FLOW6ID, /* for flow id tag in the ipv6 pkt */ + O_ICMP6TYPE, /* icmp6 packet type filtering */ + O_EXT_HDR, /* filtering for ipv6 extension header */ + O_IP6, + + /* + * actions for ng_ipfw + */ + O_NETGRAPH, /* send to ng_ipfw */ + O_NGTEE, /* copy to ng_ipfw */ + + O_IP4, + + O_UNREACH6, /* arg1=icmpv6 code arg (deny) */ + + O_TAG, /* arg1=tag number */ + O_TAGGED, /* arg1=tag number */ + + O_SETFIB, /* arg1=FIB number */ + O_FIB, /* arg1=FIB desired fib number */ + + O_SOCKARG, /* socket argument */ + + O_CALLRETURN, /* arg1=called rule number */ + + O_FORWARD_IP6, /* fwd sockaddr_in6 */ + + O_DSCP, /* 2 u32 = DSCP mask */ + O_SETDSCP, /* arg1=DSCP value */ + O_IP_FLOW_LOOKUP, /* arg1=table number, u32=value */ + + O_EXTERNAL_ACTION, /* arg1=id of external action handler */ + O_EXTERNAL_INSTANCE, /* arg1=id of eaction handler instance */ + + O_LAST_OPCODE /* not an opcode! */ +}; + +/* + * The extension header are filtered only for presence using a bit + * vector with a flag for each header. + */ +#define EXT_FRAGMENT 0x1 +#define EXT_HOPOPTS 0x2 +#define EXT_ROUTING 0x4 +#define EXT_AH 0x8 +#define EXT_ESP 0x10 +#define EXT_DSTOPTS 0x20 +#define EXT_RTHDR0 0x40 +#define EXT_RTHDR2 0x80 + +/* + * Template for instructions. + * + * ipfw_insn is used for all instructions which require no operands, + * a single 16-bit value (arg1), or a couple of 8-bit values. + * + * For other instructions which require different/larger arguments + * we have derived structures, ipfw_insn_*. + * + * The size of the instruction (in 32-bit words) is in the low + * 6 bits of "len". The 2 remaining bits are used to implement + * NOT and OR on individual instructions. Given a type, you can + * compute the length to be put in "len" using F_INSN_SIZE(t) + * + * F_NOT negates the match result of the instruction. + * + * F_OR is used to build or blocks. By default, instructions + * are evaluated as part of a logical AND. An "or" block + * { X or Y or Z } contains F_OR set in all but the last + * instruction of the block. A match will cause the code + * to skip past the last instruction of the block. + * + * NOTA BENE: in a couple of places we assume that + * sizeof(ipfw_insn) == sizeof(u_int32_t) + * this needs to be fixed. + * + */ +typedef struct _ipfw_insn { /* template for instructions */ + u_int8_t opcode; + u_int8_t len; /* number of 32-bit words */ +#define F_NOT 0x80 +#define F_OR 0x40 +#define F_LEN_MASK 0x3f +#define F_LEN(cmd) ((cmd)->len & F_LEN_MASK) + + u_int16_t arg1; +} ipfw_insn; + +/* + * The F_INSN_SIZE(type) computes the size, in 4-byte words, of + * a given type. + */ +#define F_INSN_SIZE(t) ((sizeof (t))/sizeof(u_int32_t)) + +/* + * This is used to store an array of 16-bit entries (ports etc.) + */ +typedef struct _ipfw_insn_u16 { + ipfw_insn o; + u_int16_t ports[2]; /* there may be more */ +} ipfw_insn_u16; + +/* + * This is used to store an array of 32-bit entries + * (uid, single IPv4 addresses etc.) + */ +typedef struct _ipfw_insn_u32 { + ipfw_insn o; + u_int32_t d[1]; /* one or more */ +} ipfw_insn_u32; + +/* + * This is used to store IP addr-mask pairs. + */ +typedef struct _ipfw_insn_ip { + ipfw_insn o; + struct in_addr addr; + struct in_addr mask; +} ipfw_insn_ip; + +/* + * This is used to forward to a given address (ip). + */ +typedef struct _ipfw_insn_sa { + ipfw_insn o; + struct sockaddr_in sa; +} ipfw_insn_sa; + +/* + * This is used to forward to a given address (ipv6). + */ +typedef struct _ipfw_insn_sa6 { + ipfw_insn o; + struct sockaddr_in6 sa; +} ipfw_insn_sa6; + +/* + * This is used for MAC addr-mask pairs. + */ +typedef struct _ipfw_insn_mac { + ipfw_insn o; + u_char addr[12]; /* dst[6] + src[6] */ + u_char mask[12]; /* dst[6] + src[6] */ +} ipfw_insn_mac; + +/* + * This is used for interface match rules (recv xx, xmit xx). + */ +typedef struct _ipfw_insn_if { + ipfw_insn o; + union { + struct in_addr ip; + int glob; + uint16_t kidx; + } p; + char name[IFNAMSIZ]; +} ipfw_insn_if; + +/* + * This is used for storing an altq queue id number. + */ +typedef struct _ipfw_insn_altq { + ipfw_insn o; + u_int32_t qid; +} ipfw_insn_altq; + +/* + * This is used for limit rules. + */ +typedef struct _ipfw_insn_limit { + ipfw_insn o; + u_int8_t _pad; + u_int8_t limit_mask; /* combination of DYN_* below */ +#define DYN_SRC_ADDR 0x1 +#define DYN_SRC_PORT 0x2 +#define DYN_DST_ADDR 0x4 +#define DYN_DST_PORT 0x8 + + u_int16_t conn_limit; +} ipfw_insn_limit; + +/* + * This is used for log instructions. + */ +typedef struct _ipfw_insn_log { + ipfw_insn o; + u_int32_t max_log; /* how many do we log -- 0 = all */ + u_int32_t log_left; /* how many left to log */ +} ipfw_insn_log; + +/* Legacy NAT structures, compat only */ +#ifndef _KERNEL +/* + * Data structures required by both ipfw(8) and ipfw(4) but not part of the + * management API are protected by IPFW_INTERNAL. + */ +#ifdef IPFW_INTERNAL +/* Server pool support (LSNAT). */ +struct cfg_spool { + LIST_ENTRY(cfg_spool) _next; /* chain of spool instances */ + struct in_addr addr; + u_short port; +}; +#endif + +/* Redirect modes id. */ +#define REDIR_ADDR 0x01 +#define REDIR_PORT 0x02 +#define REDIR_PROTO 0x04 + +#ifdef IPFW_INTERNAL +/* 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; +}; +#endif + +#ifdef IPFW_INTERNAL +/* 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; +}; +#endif + +#define SOF_NAT sizeof(struct cfg_nat) +#define SOF_REDIR sizeof(struct cfg_redir) +#define SOF_SPOOL sizeof(struct cfg_spool) + +#endif /* ifndef _KERNEL */ + + +struct nat44_cfg_spool { + struct in_addr addr; + uint16_t port; + uint16_t spare; +}; +#define NAT44_REDIR_ADDR 0x01 +#define NAT44_REDIR_PORT 0x02 +#define NAT44_REDIR_PROTO 0x04 + +/* Nat redirect configuration. */ +struct nat44_cfg_redir { + struct in_addr laddr; /* local ip address */ + struct in_addr paddr; /* public ip address */ + struct in_addr raddr; /* remote ip address */ + uint16_t lport; /* local port */ + uint16_t pport; /* public port */ + uint16_t rport; /* remote port */ + uint16_t pport_cnt; /* number of public ports */ + uint16_t rport_cnt; /* number of remote ports */ + uint16_t mode; /* type of redirect mode */ + uint16_t spool_cnt; /* num of entry in spool chain */ + uint16_t spare; + uint32_t proto; /* protocol: tcp/udp */ +}; + +/* Nat configuration data struct. */ +struct nat44_cfg_nat { + char name[64]; /* nat name */ + char if_name[64]; /* interface name */ + uint32_t size; /* structure size incl. redirs */ + struct in_addr ip; /* nat IPv4 address */ + uint32_t mode; /* aliasing mode */ + uint32_t redir_cnt; /* number of entry in spool chain */ +}; + +/* 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]; \ + (addr)->__u6_addr.__u6_addr32[1] &= (mask)->__u6_addr.__u6_addr32[1]; \ + (addr)->__u6_addr.__u6_addr32[2] &= (mask)->__u6_addr.__u6_addr32[2]; \ + (addr)->__u6_addr.__u6_addr32[3] &= (mask)->__u6_addr.__u6_addr32[3]; + +/* Structure for ipv6 */ +typedef struct _ipfw_insn_ip6 { + ipfw_insn o; + struct in6_addr addr6; + struct in6_addr mask6; +} ipfw_insn_ip6; + +/* Used to support icmp6 types */ +typedef struct _ipfw_insn_icmp6 { + ipfw_insn o; + uint32_t d[7]; /* XXX This number si related to the netinet/icmp6.h + * define ICMP6_MAXTYPE + * as follows: n = ICMP6_MAXTYPE/32 + 1 + * Actually is 203 + */ +} ipfw_insn_icmp6; + +/* + * Here we have the structure representing an ipfw rule. + * + * Layout: + * struct ip_fw_rule + * [ counter block, size = rule->cntr_len ] + * [ one or more instructions, size = rule->cmd_len * 4 ] + * + * It starts with a general area (with link fields). + * Counter block may be next (if rule->cntr_len > 0), + * followed by an array of one or more instructions, which the code + * accesses as an array of 32-bit values. rule->cmd_len represents + * the total instructions legth in u32 worrd, while act_ofs represents + * rule action offset in u32 words. + * + * When assembling instruction, remember the following: + * + * + if a rule has a "keep-state" (or "limit") option, then the + * first instruction (at r->cmd) MUST BE an O_PROBE_STATE + * + if a rule has a "log" option, then the first action + * (at ACTION_PTR(r)) MUST be O_LOG + * + if a rule has an "altq" option, it comes after "log" + * + if a rule has an O_TAG option, it comes after "log" and "altq" + * + * + * All structures (excluding instructions) are u64-aligned. + * Please keep this. + */ + +struct ip_fw_rule { + uint16_t act_ofs; /* offset of action in 32-bit units */ + uint16_t cmd_len; /* # of 32-bit words in cmd */ + uint16_t spare; + uint8_t set; /* rule set (0..31) */ + uint8_t flags; /* rule flags */ + uint32_t rulenum; /* rule number */ + uint32_t id; /* rule id */ + + ipfw_insn cmd[1]; /* storage for commands */ +}; +#define IPFW_RULE_NOOPT 0x01 /* Has no options in body */ + +/* Unaligned version */ + +/* Base ipfw rule counter block. */ +struct ip_fw_bcounter { + uint16_t size; /* Size of counter block, bytes */ + uint8_t flags; /* flags for given block */ + uint8_t spare; + uint32_t timestamp; /* tv_sec of last match */ + uint64_t pcnt; /* Packet counter */ + uint64_t bcnt; /* Byte counter */ +}; + + +#ifndef _KERNEL +/* + * Legacy rule format + */ +struct ip_fw { + struct ip_fw *x_next; /* linked list of rules */ + struct ip_fw *next_rule; /* ptr to next [skipto] rule */ + /* 'next_rule' is used to pass up 'set_disable' status */ + + uint16_t act_ofs; /* offset of action in 32-bit units */ + uint16_t cmd_len; /* # of 32-bit words in cmd */ + uint16_t rulenum; /* rule number */ + uint8_t set; /* rule set (0..31) */ + uint8_t _pad; /* padding */ + uint32_t id; /* rule id */ + + /* These fields are present in all rules. */ + uint64_t pcnt; /* Packet counter */ + uint64_t bcnt; /* Byte counter */ + uint32_t timestamp; /* tv_sec of last match */ + + ipfw_insn cmd[1]; /* storage for commands */ +}; +#endif + +#define ACTION_PTR(rule) \ + (ipfw_insn *)( (u_int32_t *)((rule)->cmd) + ((rule)->act_ofs) ) + +#define RULESIZE(rule) (sizeof(*(rule)) + (rule)->cmd_len * 4 - 4) + + +#if 1 // should be moved to in.h +/* + * This structure is used as a flow mask and a flow id for various + * parts of the code. + * addr_type is used in userland and kernel to mark the address type. + * fib is used in the kernel to record the fib in use. + * _flags is used in the kernel to store tcp flags for dynamic rules. + */ +struct ipfw_flow_id { + uint32_t dst_ip; + uint32_t src_ip; + uint16_t dst_port; + uint16_t src_port; + uint8_t fib; + uint8_t proto; + uint8_t _flags; /* protocol-specific flags */ + uint8_t addr_type; /* 4=ip4, 6=ip6, 1=ether ? */ + struct in6_addr dst_ip6; + struct in6_addr src_ip6; + uint32_t flow_id6; + uint32_t extra; /* queue/pipe or frag_id */ +}; +#endif + +#define IS_IP6_FLOW_ID(id) ((id)->addr_type == 6) + +/* + * Dynamic ipfw rule. + */ +typedef struct _ipfw_dyn_rule ipfw_dyn_rule; + +struct _ipfw_dyn_rule { + ipfw_dyn_rule *next; /* linked list of rules. */ + struct ip_fw *rule; /* pointer to rule */ + /* 'rule' is used to pass up the rule number (from the parent) */ + + ipfw_dyn_rule *parent; /* pointer to parent rule */ + u_int64_t pcnt; /* packet match counter */ + u_int64_t bcnt; /* byte match counter */ + struct ipfw_flow_id id; /* (masked) flow id */ + u_int32_t expire; /* expire time */ + u_int32_t bucket; /* which bucket in hash table */ + u_int32_t state; /* state of this rule (typically a + * combination of TCP flags) + */ + u_int32_t ack_fwd; /* most recent ACKs in forward */ + u_int32_t ack_rev; /* and reverse directions (used */ + /* to generate keepalives) */ + u_int16_t dyn_type; /* rule type */ + u_int16_t count; /* refcount */ +}; + +/* + * Definitions for IP option names. + */ +#define IP_FW_IPOPT_LSRR 0x01 +#define IP_FW_IPOPT_SSRR 0x02 +#define IP_FW_IPOPT_RR 0x04 +#define IP_FW_IPOPT_TS 0x08 + +/* + * Definitions for TCP option names. + */ +#define IP_FW_TCPOPT_MSS 0x01 +#define IP_FW_TCPOPT_WINDOW 0x02 +#define IP_FW_TCPOPT_SACK 0x04 +#define IP_FW_TCPOPT_TS 0x08 +#define IP_FW_TCPOPT_CC 0x10 + +#define ICMP_REJECT_RST 0x100 /* fake ICMP code (send a TCP RST) */ +#define ICMP6_UNREACH_RST 0x100 /* fake ICMPv6 code (send a TCP RST) */ + +/* + * These are used for lookup tables. + */ + +#define IPFW_TABLE_ADDR 1 /* Table for holding IPv4/IPv6 prefixes */ +#define IPFW_TABLE_INTERFACE 2 /* Table for holding interface names */ +#define IPFW_TABLE_NUMBER 3 /* Table for holding ports/uid/gid/etc */ +#define IPFW_TABLE_FLOW 4 /* Table for holding flow data */ +#define IPFW_TABLE_MAXTYPE 4 /* Maximum valid number */ + +#define IPFW_TABLE_CIDR IPFW_TABLE_ADDR /* compat */ + +/* Value types */ +#define IPFW_VTYPE_LEGACY 0xFFFFFFFF /* All data is filled in */ +#define IPFW_VTYPE_SKIPTO 0x00000001 /* skipto/call/callreturn */ +#define IPFW_VTYPE_PIPE 0x00000002 /* pipe/queue */ +#define IPFW_VTYPE_FIB 0x00000004 /* setfib */ +#define IPFW_VTYPE_NAT 0x00000008 /* nat */ +#define IPFW_VTYPE_DSCP 0x00000010 /* dscp */ +#define IPFW_VTYPE_TAG 0x00000020 /* tag/untag */ +#define IPFW_VTYPE_DIVERT 0x00000040 /* divert/tee */ +#define IPFW_VTYPE_NETGRAPH 0x00000080 /* netgraph/ngtee */ +#define IPFW_VTYPE_LIMIT 0x00000100 /* limit */ +#define IPFW_VTYPE_NH4 0x00000200 /* IPv4 nexthop */ +#define IPFW_VTYPE_NH6 0x00000400 /* IPv6 nexthop */ + +typedef struct _ipfw_table_entry { + in_addr_t addr; /* network address */ + u_int32_t value; /* value */ + u_int16_t tbl; /* table number */ + u_int8_t masklen; /* mask length */ +} ipfw_table_entry; + +typedef struct _ipfw_table_xentry { + uint16_t len; /* Total entry length */ + uint8_t type; /* entry type */ + uint8_t masklen; /* mask length */ + uint16_t tbl; /* table number */ + uint16_t flags; /* record flags */ + uint32_t value; /* value */ + union { + /* Longest field needs to be aligned by 4-byte boundary */ + struct in6_addr addr6; /* IPv6 address */ + char iface[IF_NAMESIZE]; /* interface name */ + } k; +} ipfw_table_xentry; +#define IPFW_TCF_INET 0x01 /* CIDR flags: IPv4 record */ + +typedef struct _ipfw_table { + u_int32_t size; /* size of entries in bytes */ + u_int32_t cnt; /* # of entries */ + u_int16_t tbl; /* table number */ + ipfw_table_entry ent[0]; /* entries */ +} ipfw_table; + +typedef struct _ipfw_xtable { + ip_fw3_opheader opheader; /* IP_FW3 opcode */ + uint32_t size; /* size of entries in bytes */ + uint32_t cnt; /* # of entries */ + uint16_t tbl; /* table number */ + uint8_t type; /* table type */ + ipfw_table_xentry xent[0]; /* entries */ +} ipfw_xtable; + +typedef struct _ipfw_obj_tlv { + uint16_t type; /* TLV type */ + uint16_t flags; /* TLV-specific flags */ + uint32_t length; /* Total length, aligned to u64 */ +} ipfw_obj_tlv; +#define IPFW_TLV_TBL_NAME 1 +#define IPFW_TLV_TBLNAME_LIST 2 +#define IPFW_TLV_RULE_LIST 3 +#define IPFW_TLV_DYNSTATE_LIST 4 +#define IPFW_TLV_TBL_ENT 5 +#define IPFW_TLV_DYN_ENT 6 +#define IPFW_TLV_RULE_ENT 7 +#define IPFW_TLV_TBLENT_LIST 8 +#define IPFW_TLV_RANGE 9 +#define IPFW_TLV_EACTION 10 + +#define IPFW_TLV_EACTION_BASE 1000 +#define IPFW_TLV_EACTION_NAME(arg) (IPFW_TLV_EACTION_BASE + (arg)) + +/* Object name TLV */ +typedef struct _ipfw_obj_ntlv { + ipfw_obj_tlv head; /* TLV header */ + uint16_t idx; /* Name index */ + uint8_t set; /* set, if applicable */ + uint8_t type; /* object type, if applicable */ + uint32_t spare; /* unused */ + char name[64]; /* Null-terminated name */ +} ipfw_obj_ntlv; + +/* IPv4/IPv6 L4 flow description */ +struct tflow_entry { + uint8_t af; + uint8_t proto; + uint16_t spare; + uint16_t sport; + uint16_t dport; + union { + struct { + struct in_addr sip; + struct in_addr dip; + } a4; + struct { + struct in6_addr sip6; + struct in6_addr dip6; + } a6; + } a; +}; + +typedef struct _ipfw_table_value { + uint32_t tag; /* O_TAG/O_TAGGED */ + uint32_t pipe; /* O_PIPE/O_QUEUE */ + uint16_t divert; /* O_DIVERT/O_TEE */ + uint16_t skipto; /* skipto, CALLRET */ + uint32_t netgraph; /* O_NETGRAPH/O_NGTEE */ + uint32_t fib; /* O_SETFIB */ + uint32_t nat; /* O_NAT */ + uint32_t nh4; + uint8_t dscp; + uint8_t spare0; + uint16_t spare1; + struct in6_addr nh6; + uint32_t limit; /* O_LIMIT */ + uint32_t zoneid; /* scope zone id for nh6 */ + uint64_t reserved; +} ipfw_table_value; + +/* Table entry TLV */ +typedef struct _ipfw_obj_tentry { + ipfw_obj_tlv head; /* TLV header */ + uint8_t subtype; /* subtype (IPv4,IPv6) */ + uint8_t masklen; /* mask length */ + uint8_t result; /* request result */ + uint8_t spare0; + uint16_t idx; /* Table name index */ + uint16_t spare1; + union { + /* Longest field needs to be aligned by 8-byte boundary */ + struct in_addr addr; /* IPv4 address */ + uint32_t key; /* uid/gid/port */ + struct in6_addr addr6; /* IPv6 address */ + char iface[IF_NAMESIZE]; /* interface name */ + struct tflow_entry flow; + } k; + union { + ipfw_table_value value; /* value data */ + uint32_t kidx; /* value kernel index */ + } v; +} ipfw_obj_tentry; +#define IPFW_TF_UPDATE 0x01 /* Update record if exists */ +/* Container TLV */ +#define IPFW_CTF_ATOMIC 0x01 /* Perform atomic operation */ +/* Operation results */ +#define IPFW_TR_IGNORED 0 /* Entry was ignored (rollback) */ +#define IPFW_TR_ADDED 1 /* Entry was successfully added */ +#define IPFW_TR_UPDATED 2 /* Entry was successfully updated*/ +#define IPFW_TR_DELETED 3 /* Entry was successfully deleted*/ +#define IPFW_TR_LIMIT 4 /* Entry was ignored (limit) */ +#define IPFW_TR_NOTFOUND 5 /* Entry was not found */ +#define IPFW_TR_EXISTS 6 /* Entry already exists */ +#define IPFW_TR_ERROR 7 /* Request has failed (unknown) */ + +typedef struct _ipfw_obj_dyntlv { + ipfw_obj_tlv head; + ipfw_dyn_rule state; +} ipfw_obj_dyntlv; +#define IPFW_DF_LAST 0x01 /* Last state in chain */ + +/* Containter TLVs */ +typedef struct _ipfw_obj_ctlv { + ipfw_obj_tlv head; /* TLV header */ + uint32_t count; /* Number of sub-TLVs */ + uint16_t objsize; /* Single object size */ + uint8_t version; /* TLV version */ + uint8_t flags; /* TLV-specific flags */ +} ipfw_obj_ctlv; + +/* Range TLV */ +typedef struct _ipfw_range_tlv { + ipfw_obj_tlv head; /* TLV header */ + uint32_t flags; /* Range flags */ + uint16_t start_rule; /* Range start */ + uint16_t end_rule; /* Range end */ + uint32_t set; /* Range set to match */ + uint32_t new_set; /* New set to move/swap to */ +} ipfw_range_tlv; +#define IPFW_RCFLAG_RANGE 0x01 /* rule range is set */ +#define IPFW_RCFLAG_ALL 0x02 /* match ALL rules */ +#define IPFW_RCFLAG_SET 0x04 /* match rules in given set */ +/* User-settable flags */ +#define IPFW_RCFLAG_USER (IPFW_RCFLAG_RANGE | IPFW_RCFLAG_ALL | \ + IPFW_RCFLAG_SET) +/* Internally used flags */ +#define IPFW_RCFLAG_DEFAULT 0x0100 /* Do not skip defaul rule */ + +typedef struct _ipfw_ta_tinfo { + uint32_t flags; /* Format flags */ + uint32_t spare; + uint8_t taclass4; /* algorithm class */ + uint8_t spare4; + uint16_t itemsize4; /* item size in runtime */ + uint32_t size4; /* runtime structure size */ + uint32_t count4; /* number of items in runtime */ + uint8_t taclass6; /* algorithm class */ + uint8_t spare6; + uint16_t itemsize6; /* item size in runtime */ + uint32_t size6; /* runtime structure size */ + uint32_t count6; /* number of items in runtime */ +} ipfw_ta_tinfo; +#define IPFW_TACLASS_HASH 1 /* algo is based on hash */ +#define IPFW_TACLASS_ARRAY 2 /* algo is based on array */ +#define IPFW_TACLASS_RADIX 3 /* algo is based on radix tree */ + +#define IPFW_TATFLAGS_DATA 0x0001 /* Has data filled in */ +#define IPFW_TATFLAGS_AFDATA 0x0002 /* Separate data per AF */ +#define IPFW_TATFLAGS_AFITEM 0x0004 /* diff. items per AF */ + +typedef struct _ipfw_xtable_info { + uint8_t type; /* table type (addr,iface,..) */ + uint8_t tflags; /* type flags */ + uint16_t mflags; /* modification flags */ + uint16_t flags; /* generic table flags */ + uint16_t spare[3]; + uint32_t vmask; /* bitmask with value types */ + uint32_t set; /* set table is in */ + uint32_t kidx; /* kernel index */ + uint32_t refcnt; /* number of references */ + uint32_t count; /* Number of records */ + uint32_t size; /* Total size of records(export)*/ + uint32_t limit; /* Max number of records */ + char tablename[64]; /* table name */ + char algoname[64]; /* algorithm name */ + ipfw_ta_tinfo ta_info; /* additional algo stats */ +} ipfw_xtable_info; +/* Generic table flags */ +#define IPFW_TGFLAGS_LOCKED 0x01 /* Tables is locked from changes*/ +/* Table type-specific flags */ +#define IPFW_TFFLAG_SRCIP 0x01 +#define IPFW_TFFLAG_DSTIP 0x02 +#define IPFW_TFFLAG_SRCPORT 0x04 +#define IPFW_TFFLAG_DSTPORT 0x08 +#define IPFW_TFFLAG_PROTO 0x10 +/* Table modification flags */ +#define IPFW_TMFLAGS_LIMIT 0x0002 /* Change limit value */ +#define IPFW_TMFLAGS_LOCK 0x0004 /* Change table lock state */ + +typedef struct _ipfw_iface_info { + char ifname[64]; /* interface name */ + uint32_t ifindex; /* interface index */ + uint32_t flags; /* flags */ + uint32_t refcnt; /* number of references */ + uint32_t gencnt; /* number of changes */ + uint64_t spare; +} ipfw_iface_info; +#define IPFW_IFFLAG_RESOLVED 0x01 /* Interface exists */ + +typedef struct _ipfw_ta_info { + char algoname[64]; /* algorithm name */ + uint32_t type; /* lookup type */ + uint32_t flags; + uint32_t refcnt; + uint32_t spare0; + uint64_t spare1; +} ipfw_ta_info; + +typedef struct _ipfw_obj_header { + ip_fw3_opheader opheader; /* IP_FW3 opcode */ + uint32_t spare; + uint16_t idx; /* object name index */ + uint8_t objtype; /* object type */ + uint8_t objsubtype; /* object subtype */ + ipfw_obj_ntlv ntlv; /* object name tlv */ +} ipfw_obj_header; + +typedef struct _ipfw_obj_lheader { + ip_fw3_opheader opheader; /* IP_FW3 opcode */ + uint32_t set_mask; /* disabled set mask */ + uint32_t count; /* Total objects count */ + uint32_t size; /* Total size (incl. header) */ + uint32_t objsize; /* Size of one object */ +} ipfw_obj_lheader; + +#define IPFW_CFG_GET_STATIC 0x01 +#define IPFW_CFG_GET_STATES 0x02 +#define IPFW_CFG_GET_COUNTERS 0x04 +typedef struct _ipfw_cfg_lheader { + ip_fw3_opheader opheader; /* IP_FW3 opcode */ + uint32_t set_mask; /* enabled set mask */ + uint32_t spare; + uint32_t flags; /* Request flags */ + uint32_t size; /* neded buffer size */ + uint32_t start_rule; + uint32_t end_rule; +} ipfw_cfg_lheader; + +typedef struct _ipfw_range_header { + ip_fw3_opheader opheader; /* IP_FW3 opcode */ + ipfw_range_tlv range; +} ipfw_range_header; + +typedef struct _ipfw_sopt_info { + uint16_t opcode; + uint8_t version; + uint8_t dir; + uint8_t spare; + uint64_t refcnt; +} ipfw_sopt_info; + +#endif /* _IPFW2_H */ diff --git a/tools/compat/include/timeconv.h b/tools/compat/include/timeconv.h new file mode 100644 index 000000000..147db1509 --- /dev/null +++ b/tools/compat/include/timeconv.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * (c) UNIX System Laboratories, Inc. + * All or some portions of this file are derived from material licensed + * to the University of California by American Telephone and Telegraph + * Co. or Unix System Laboratories, Inc. and are reproduced herein with + * the permission of UNIX System Laboratories, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)time.h 8.3 (Berkeley) 1/21/94 + */ + +/* + * $FreeBSD$ + */ + +#ifndef _TIMECONV_H_ +#define _TIMECONV_H_ + +#include +#include + +#ifndef _TIME_T_DECLARED +typedef __time_t time_t; +#define _TIME_T_DECLARED +#endif + +time_t _time32_to_time(__int32_t t32); +__int32_t _time_to_time32(time_t t); +time_t _time64_to_time(__int64_t t64); +__int64_t _time_to_time64(time_t t); +long _time_to_long(time_t t); +time_t _long_to_time(long tlong); +int _time_to_int(time_t t); +time_t _int_to_time(int tint); + +#endif /* _TIMECONV_H_ */ diff --git a/tools/compat/ioctl.c b/tools/compat/ioctl.c index acbca4a8d..7682d3118 100644 --- a/tools/compat/ioctl.c +++ b/tools/compat/ioctl.c @@ -26,7 +26,8 @@ #include #include -#include "sys/ioctl.h" +#include +#include #include "ff_ipc.h" /* diff --git a/tools/compat/sysctl.c b/tools/compat/sysctl.c index 0282dd2a9..f74d65fb9 100644 --- a/tools/compat/sysctl.c +++ b/tools/compat/sysctl.c @@ -25,6 +25,7 @@ */ #include +#include #include #include "ff_ipc.h" diff --git a/tools/compat/time32.c b/tools/compat/time32.c new file mode 100644 index 000000000..2cf1baaa2 --- /dev/null +++ b/tools/compat/time32.c @@ -0,0 +1,102 @@ +/*- + * Copyright (c) 2001 FreeBSD Inc. + * All rights reserved. + * + * These routines are for converting time_t to fixed-bit representations + * for use in protocols or storage. When converting time to a larger + * representation of time_t these routines are expected to assume temporal + * locality and use the 50-year rule to properly set the msb bits. XXX + * + * Redistribution and use under the terms of the COPYRIGHT file at the + * base of the source tree. + */ + +#include +#ifndef FSTACK +__FBSDID("$FreeBSD$"); +#endif + +#include +#include + +/* + * Convert a 32 bit representation of time_t into time_t. XXX needs to + * implement the 50-year rule to handle post-2038 conversions. + */ +time_t +_time32_to_time(__int32_t t32) +{ + return((time_t)t32); +} + +/* + * Convert time_t to a 32 bit representation. If time_t is 64 bits we can + * simply chop it down. The resulting 32 bit representation can be + * converted back to a temporally local 64 bit time_t using time32_to_time. + */ +__int32_t +_time_to_time32(time_t t) +{ + return((__int32_t)t); +} + +/* + * Convert a 64 bit representation of time_t into time_t. If time_t is + * represented as 32 bits we can simply chop it and not support times + * past 2038. + */ +time_t +_time64_to_time(__int64_t t64) +{ + return((time_t)t64); +} + +/* + * Convert time_t to a 64 bit representation. If time_t is represented + * as 32 bits we simply sign-extend and do not support times past 2038. + */ +__int64_t +_time_to_time64(time_t t) +{ + return((__int64_t)t); +} + +/* + * Convert to/from 'long'. Depending on the sizeof(long) this may or + * may not require using the 50-year rule. + */ +long +_time_to_long(time_t t) +{ + if (sizeof(long) == sizeof(__int64_t)) + return(_time_to_time64(t)); + return((long)t); +} + +time_t +_long_to_time(long tlong) +{ + if (sizeof(long) == sizeof(__int32_t)) + return(_time32_to_time(tlong)); + return((time_t)tlong); +} + +/* + * Convert to/from 'int'. Depending on the sizeof(int) this may or + * may not require using the 50-year rule. + */ +int +_time_to_int(time_t t) +{ + if (sizeof(int) == sizeof(__int64_t)) + return(_time_to_time64(t)); + return((int)t); +} + +time_t +_int_to_time(int tint) +{ + if (sizeof(int) == sizeof(__int32_t)) + return(_time32_to_time(tint)); + return((time_t)tint); +} diff --git a/tools/ipfw/Makefile b/tools/ipfw/Makefile new file mode 100644 index 000000000..32183f62d --- /dev/null +++ b/tools/ipfw/Makefile @@ -0,0 +1,25 @@ +# $FreeBSD$ + +TOPDIR?=${CURDIR}/../.. +include ${TOPDIR}/tools/opts.mk + +PACKAGE=ipfw +PROG= ipfw +SRCS= ipfw2.c ipv6.c main.c nat.c tables.c compat.c +WARNS?= 2 + +ifneq (${MK_DUMMYNET},"no") +SRCS+= dummynet.c +CFLAGS+= -DDUMMYNET +endif + +ifneq (${MK_PF},"no") +SRCS+= altq.c +CFLAGS+=-DPF +endif + +LIBADD= util +MAN= ipfw.8 + +include ${TOPDIR}/tools/prog.mk + diff --git a/tools/ipfw/altq.c b/tools/ipfw/altq.c new file mode 100644 index 000000000..8398ab611 --- /dev/null +++ b/tools/ipfw/altq.c @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2002-2003 Luigi Rizzo + * Copyright (c) 1996 Alex Nash, Paul Traina, Poul-Henning Kamp + * Copyright (c) 1994 Ugen J.S.Antsilevich + * + * Idea and grammar partially left from: + * Copyright (c) 1993 Daniel Boulet + * + * Redistribution and use in source forms, with and without modification, + * are permitted provided that this entire comment appears intact. + * + * Redistribution in binary form may occur without any restrictions. + * Obviously, it would be nice if you gave credit where credit is due + * but requiring it would be too onerous. + * + * This software is provided ``AS IS'' without any warranties of any kind. + * + * NEW command line interface for IP firewall facility + * + * $FreeBSD$ + * + * altq interface + */ + +#include +#include +#include + +#include "ipfw2.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* IFNAMSIZ */ +#include +#include /* in_addr */ +#include + +/* + * Map between current altq queue id numbers and names. + */ +static TAILQ_HEAD(, pf_altq) altq_entries = + TAILQ_HEAD_INITIALIZER(altq_entries); + +void +altq_set_enabled(int enabled) +{ + int pffd; + + pffd = open("/dev/pf", O_RDWR); + if (pffd == -1) + err(EX_UNAVAILABLE, + "altq support opening pf(4) control device"); + if (enabled) { + if (ioctl(pffd, DIOCSTARTALTQ) != 0 && errno != EEXIST) + err(EX_UNAVAILABLE, "enabling altq"); + } else { + if (ioctl(pffd, DIOCSTOPALTQ) != 0 && errno != ENOENT) + err(EX_UNAVAILABLE, "disabling altq"); + } + close(pffd); +} + +static void +altq_fetch(void) +{ + struct pfioc_altq pfioc; + struct pf_altq *altq; + int pffd; + unsigned int mnr; + static int altq_fetched = 0; + + if (altq_fetched) + return; + altq_fetched = 1; + pffd = open("/dev/pf", O_RDONLY); + if (pffd == -1) { + warn("altq support opening pf(4) control device"); + return; + } + bzero(&pfioc, sizeof(pfioc)); + if (ioctl(pffd, DIOCGETALTQS, &pfioc) != 0) { + warn("altq support getting queue list"); + close(pffd); + return; + } + mnr = pfioc.nr; + for (pfioc.nr = 0; pfioc.nr < mnr; pfioc.nr++) { + if (ioctl(pffd, DIOCGETALTQ, &pfioc) != 0) { + if (errno == EBUSY) + break; + warn("altq support getting queue list"); + close(pffd); + return; + } + if (pfioc.altq.qid == 0) + continue; + altq = safe_calloc(1, sizeof(*altq)); + *altq = pfioc.altq; + TAILQ_INSERT_TAIL(&altq_entries, altq, entries); + } + close(pffd); +} + +u_int32_t +altq_name_to_qid(const char *name) +{ + struct pf_altq *altq; + + altq_fetch(); + TAILQ_FOREACH(altq, &altq_entries, entries) + if (strcmp(name, altq->qname) == 0) + break; + if (altq == NULL) + errx(EX_DATAERR, "altq has no queue named `%s'", name); + return altq->qid; +} + +static const char * +altq_qid_to_name(u_int32_t qid) +{ + struct pf_altq *altq; + + altq_fetch(); + TAILQ_FOREACH(altq, &altq_entries, entries) + if (qid == altq->qid) + break; + if (altq == NULL) + return NULL; + return altq->qname; +} + +void +print_altq_cmd(struct buf_pr *bp, ipfw_insn_altq *altqptr) +{ + if (altqptr) { + const char *qname; + + qname = altq_qid_to_name(altqptr->qid); + if (qname == NULL) + bprintf(bp, " altq ?<%u>", altqptr->qid); + else + bprintf(bp, " altq %s", qname); + } +} diff --git a/tools/ipfw/compat.c b/tools/ipfw/compat.c new file mode 100644 index 000000000..d7afa1e9f --- /dev/null +++ b/tools/ipfw/compat.c @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2017 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + */ + + +#include +#include +#include + +#include "ff_ipc.h" + +static int +ipfw_ctl(int cmd, int level, int optname, void *optval, socklen_t *optlen) +{ + struct ff_msg *msg, *retmsg = NULL; + int len; + + switch (cmd) { + case FF_IPFW_GET: + if (optval == NULL || optlen == NULL) { + return EINVAL; + } + break; + case FF_IPFW_SET: + break; + default: + return EINVAL; + } + + msg = ff_ipc_msg_alloc(); + if (msg == NULL) { + errno = ENOMEM; + return -1; + } + + len = sizeof(struct ff_ipfw_args) + *optlen + sizeof(socklen_t); + if (len > msg->buf_len) { + errno = EINVAL; + ff_ipc_msg_free(msg); + return -1; + } + + msg->msg_type = FF_IPFW_CTL; + msg->ipfw.cmd = cmd; + msg->ipfw.level = level; + msg->ipfw.optname = optname; + msg->ipfw.optval = (void *)msg->buf_addr; + msg->ipfw.optlen = (socklen_t *)(msg->buf_addr + (*optlen)); + + memcpy(msg->ipfw.optval, optval, *optlen); + memcpy(msg->ipfw.optlen, optlen, sizeof(socklen_t)); + + int ret = ff_ipc_send(msg); + if (ret < 0) { + errno = EPIPE; + ff_ipc_msg_free(msg); + return -1; + } + + do { + if (retmsg != NULL) { + ff_ipc_msg_free(retmsg); + } + ret = ff_ipc_recv(&retmsg); + if (ret < 0) { + errno = EPIPE; + ff_ipc_msg_free(msg); + return -1; + } + } while (msg != retmsg); + + if (retmsg->result != 0) { + ret = -1; + errno = retmsg->result; + } else { + ret = 0; + + if (cmd == FF_IPFW_GET) { + memcpy(optval, retmsg->ipfw.optval, *(retmsg->ipfw.optlen)); + memcpy(optlen, retmsg->ipfw.optlen, sizeof(socklen_t)); + } + } + + ff_ipc_msg_free(msg); + + return ret; +} + +int +ff_socket(int domain, int type, int protocol) +{ + return 0; +} + +int ff_getsockopt(int sockfd, int level, int optname, + void *optval, socklen_t *optlen) +{ + return ipfw_ctl(FF_IPFW_GET, level, optname, optval, optlen); +} + +int ff_setsockopt(int sockfd, int level, int optname, + const void *optval, socklen_t optlen) +{ + return ipfw_ctl(FF_IPFW_SET, level, optname, (void *)optval, &optlen); +} + diff --git a/tools/ipfw/dummynet.c b/tools/ipfw/dummynet.c new file mode 100644 index 000000000..20c563ba0 --- /dev/null +++ b/tools/ipfw/dummynet.c @@ -0,0 +1,1989 @@ +/* + * Codel/FQ_Codel and PIE/FQ_PIE Code: + * Copyright (C) 2016 Centre for Advanced Internet Architectures, + * Swinburne University of Technology, Melbourne, Australia. + * Portions of this code were made possible in part by a gift from + * The Comcast Innovation Fund. + * Implemented by Rasool Al-Saadi + * + * Copyright (c) 2002-2003,2010 Luigi Rizzo + * + * Redistribution and use in source forms, with and without modification, + * are permitted provided that this entire comment appears intact. + * + * Redistribution in binary form may occur without any restrictions. + * Obviously, it would be nice if you gave credit where credit is due + * but requiring it would be too onerous. + * + * This software is provided ``AS IS'' without any warranties of any kind. + * + * $FreeBSD$ + * + * dummynet support + */ + +#define NEW_AQM +#include +#include +/* XXX there are several sysctl leftover here */ +#include + +#include "ipfw2.h" + +#ifdef NEW_AQM +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include /* inet_ntoa */ + + +static struct _s_x dummynet_params[] = { + { "plr", TOK_PLR }, + { "noerror", TOK_NOERROR }, + { "buckets", TOK_BUCKETS }, + { "dst-ip", TOK_DSTIP }, + { "src-ip", TOK_SRCIP }, + { "dst-port", TOK_DSTPORT }, + { "src-port", TOK_SRCPORT }, + { "proto", TOK_PROTO }, + { "weight", TOK_WEIGHT }, + { "lmax", TOK_LMAX }, + { "maxlen", TOK_LMAX }, + { "all", TOK_ALL }, + { "mask", TOK_MASK }, /* alias for both */ + { "sched_mask", TOK_SCHED_MASK }, + { "flow_mask", TOK_FLOW_MASK }, + { "droptail", TOK_DROPTAIL }, + { "ecn", TOK_ECN }, + { "red", TOK_RED }, + { "gred", TOK_GRED }, +#ifdef NEW_AQM + { "codel", TOK_CODEL}, /* Codel AQM */ + { "fq_codel", TOK_FQ_CODEL}, /* FQ-Codel */ + { "pie", TOK_PIE}, /* PIE AQM */ + { "fq_pie", TOK_FQ_PIE}, /* FQ-PIE */ +#endif + { "bw", TOK_BW }, + { "bandwidth", TOK_BW }, + { "delay", TOK_DELAY }, + { "link", TOK_LINK }, + { "pipe", TOK_PIPE }, + { "queue", TOK_QUEUE }, + { "flowset", TOK_FLOWSET }, + { "sched", TOK_SCHED }, + { "pri", TOK_PRI }, + { "priority", TOK_PRI }, + { "type", TOK_TYPE }, + { "flow-id", TOK_FLOWID}, + { "dst-ipv6", TOK_DSTIP6}, + { "dst-ip6", TOK_DSTIP6}, + { "src-ipv6", TOK_SRCIP6}, + { "src-ip6", TOK_SRCIP6}, + { "profile", TOK_PROFILE}, + { "burst", TOK_BURST}, + { "dummynet-params", TOK_NULL }, + { NULL, 0 } /* terminator */ +}; + +#ifdef NEW_AQM +/* AQM/extra sched parameters tokens*/ +static struct _s_x aqm_params[] = { + { "target", TOK_TARGET}, + { "interval", TOK_INTERVAL}, + { "limit", TOK_LIMIT}, + { "flows", TOK_FLOWS}, + { "quantum", TOK_QUANTUM}, + { "ecn", TOK_ECN}, + { "noecn", TOK_NO_ECN}, + { "tupdate", TOK_TUPDATE}, + { "max_burst", TOK_MAX_BURST}, + { "max_ecnth", TOK_MAX_ECNTH}, + { "alpha", TOK_ALPHA}, + { "beta", TOK_BETA}, + { "capdrop", TOK_CAPDROP}, + { "nocapdrop", TOK_NO_CAPDROP}, + { "onoff", TOK_ONOFF}, + { "dre", TOK_DRE}, + { "ts", TOK_TS}, + { "derand", TOK_DERAND}, + { "noderand", TOK_NO_DERAND}, + { NULL, 0 } /* terminator */ +}; +#endif + +#define O_NEXT(p, len) ((void *)((char *)p + len)) + +static void +oid_fill(struct dn_id *oid, int len, int type, uintptr_t id) +{ + oid->len = len; + oid->type = type; + oid->subtype = 0; + oid->id = id; +} + +/* make room in the buffer and move the pointer forward */ +static void * +o_next(struct dn_id **o, int len, int type) +{ + struct dn_id *ret = *o; + oid_fill(ret, len, type, 0); + *o = O_NEXT(*o, len); + return ret; +} + +#ifdef NEW_AQM + +/* Codel flags */ +enum { + CODEL_ECN_ENABLED = 1 +}; + +/* PIE flags, from PIE kernel module */ +enum { + PIE_ECN_ENABLED = 1, + PIE_CAPDROP_ENABLED = 2, + PIE_ON_OFF_MODE_ENABLED = 4, + PIE_DEPRATEEST_ENABLED = 8, + PIE_DERAND_ENABLED = 16 +}; + +#define PIE_FIX_POINT_BITS 13 +#define PIE_SCALE (1L<15) + return -1; + for (i = 0; ioid, l, DN_CMD_GET, DN_API_VERSION); + ep->oid.len = l; + ep->oid.subtype = subtype; + ep->nr = nr; + + ret = do_cmd(-IP_DUMMYNET3, ep, (uintptr_t)&l); + if (ret) { + free(ep); + errx(EX_DATAERR, "Error getting extra parameters\n"); + } + + switch (subtype) { + case DN_AQM_PARAMS: + if( !strcasecmp(ep->name, "codel")) { + us_to_time(ep->par[0], strt1); + us_to_time(ep->par[1], strt2); + l = sprintf(out, " AQM CoDel target %s interval %s", + strt1, strt2); + if (ep->par[2] & CODEL_ECN_ENABLED) + l = sprintf(out + l, " ECN"); + else + l += sprintf(out + l, " NoECN"); + } else if( !strcasecmp(ep->name, "pie")) { + us_to_time(ep->par[0], strt1); + us_to_time(ep->par[1], strt2); + us_to_time(ep->par[2], strt3); + l = sprintf(out, " AQM type PIE target %s tupdate %s alpha " + "%g beta %g max_burst %s max_ecnth %.3g", + strt1, + strt2, + ep->par[4] / (float) PIE_SCALE, + ep->par[5] / (float) PIE_SCALE, + strt3, + ep->par[3] / (float) PIE_SCALE + ); + + if (ep->par[6] & PIE_ECN_ENABLED) + l += sprintf(out + l, " ECN"); + else + l += sprintf(out + l, " NoECN"); + if (ep->par[6] & PIE_CAPDROP_ENABLED) + l += sprintf(out + l, " CapDrop"); + else + l += sprintf(out + l, " NoCapDrop"); + if (ep->par[6] & PIE_ON_OFF_MODE_ENABLED) + l += sprintf(out + l, " OnOff"); + if (ep->par[6] & PIE_DEPRATEEST_ENABLED) + l += sprintf(out + l, " DRE"); + else + l += sprintf(out + l, " TS"); + if (ep->par[6] & PIE_DERAND_ENABLED) + l += sprintf(out + l, " Derand"); + else + l += sprintf(out + l, " NoDerand"); + } + break; + + case DN_SCH_PARAMS: + if (!strcasecmp(ep->name,"FQ_CODEL")) { + us_to_time(ep->par[0], strt1); + us_to_time(ep->par[1], strt2); + l = sprintf(out," FQ_CODEL target %s interval %s" + " quantum %jd limit %jd flows %jd", + strt1, strt2, + (intmax_t) ep->par[3], + (intmax_t) ep->par[4], + (intmax_t) ep->par[5] + ); + if (ep->par[2] & CODEL_ECN_ENABLED) + l += sprintf(out + l, " ECN"); + else + l += sprintf(out + l, " NoECN"); + l += sprintf(out + l, "\n"); + } else if (!strcasecmp(ep->name,"FQ_PIE")) { + us_to_time(ep->par[0], strt1); + us_to_time(ep->par[1], strt2); + us_to_time(ep->par[2], strt3); + l = sprintf(out, " FQ_PIE target %s tupdate %s alpha " + "%g beta %g max_burst %s max_ecnth %.3g" + " quantum %jd limit %jd flows %jd", + strt1, + strt2, + ep->par[4] / (float) PIE_SCALE, + ep->par[5] / (float) PIE_SCALE, + strt3, + ep->par[3] / (float) PIE_SCALE, + (intmax_t) ep->par[7], + (intmax_t) ep->par[8], + (intmax_t) ep->par[9] + ); + + if (ep->par[6] & PIE_ECN_ENABLED) + l += sprintf(out + l, " ECN"); + else + l += sprintf(out + l, " NoECN"); + if (ep->par[6] & PIE_CAPDROP_ENABLED) + l += sprintf(out + l, " CapDrop"); + else + l += sprintf(out + l, " NoCapDrop"); + if (ep->par[6] & PIE_ON_OFF_MODE_ENABLED) + l += sprintf(out + l, " OnOff"); + if (ep->par[6] & PIE_DEPRATEEST_ENABLED) + l += sprintf(out + l, " DRE"); + else + l += sprintf(out + l, " TS"); + if (ep->par[6] & PIE_DERAND_ENABLED) + l += sprintf(out + l, " Derand"); + else + l += sprintf(out + l, " NoDerand"); + l += sprintf(out + l, "\n"); + } + break; + } + + free(ep); +} +#endif + + +#if 0 +static int +sort_q(void *arg, const void *pa, const void *pb) +{ + int rev = (co.do_sort < 0); + int field = rev ? -co.do_sort : co.do_sort; + long long res = 0; + const struct dn_flow_queue *a = pa; + const struct dn_flow_queue *b = pb; + + switch (field) { + case 1: /* pkts */ + res = a->len - b->len; + break; + case 2: /* bytes */ + res = a->len_bytes - b->len_bytes; + break; + + case 3: /* tot pkts */ + res = a->tot_pkts - b->tot_pkts; + break; + + case 4: /* tot bytes */ + res = a->tot_bytes - b->tot_bytes; + break; + } + if (res < 0) + res = -1; + if (res > 0) + res = 1; + return (int)(rev ? res : -res); +} +#endif + +/* print a mask and header for the subsequent list of flows */ +static void +print_mask(struct ipfw_flow_id *id) +{ + if (!IS_IP6_FLOW_ID(id)) { + printf(" " + "mask: %s 0x%02x 0x%08x/0x%04x -> 0x%08x/0x%04x\n", + id->extra ? "queue," : "", + id->proto, + id->src_ip, id->src_port, + id->dst_ip, id->dst_port); + } else { + char buf[255]; + printf("\n mask: %sproto: 0x%02x, flow_id: 0x%08x, ", + id->extra ? "queue," : "", + id->proto, id->flow_id6); + inet_ntop(AF_INET6, &(id->src_ip6), buf, sizeof(buf)); + printf("%s/0x%04x -> ", buf, id->src_port); + inet_ntop(AF_INET6, &(id->dst_ip6), buf, sizeof(buf)); + printf("%s/0x%04x\n", buf, id->dst_port); + } +} + +static void +print_header(struct ipfw_flow_id *id) +{ + if (!IS_IP6_FLOW_ID(id)) + printf("BKT Prot ___Source IP/port____ " + "____Dest. IP/port____ " + "Tot_pkt/bytes Pkt/Byte Drp\n"); + else + printf("BKT ___Prot___ _flow-id_ " + "______________Source IPv6/port_______________ " + "_______________Dest. IPv6/port_______________ " + "Tot_pkt/bytes Pkt/Byte Drp\n"); +} + +static void +list_flow(struct buf_pr *bp, struct dn_flow *ni) +{ + char buff[255]; + struct protoent *pe = NULL; + struct in_addr ina; + struct ipfw_flow_id *id = &ni->fid; + + pe = getprotobynumber(id->proto); + /* XXX: Should check for IPv4 flows */ + bprintf(bp, "%3u%c", (ni->oid.id) & 0xff, + id->extra ? '*' : ' '); + if (!IS_IP6_FLOW_ID(id)) { + if (pe) + bprintf(bp, "%-4s ", pe->p_name); + else + bprintf(bp, "%4u ", id->proto); + ina.s_addr = htonl(id->src_ip); + bprintf(bp, "%15s/%-5d ", + inet_ntoa(ina), id->src_port); + ina.s_addr = htonl(id->dst_ip); + bprintf(bp, "%15s/%-5d ", + inet_ntoa(ina), id->dst_port); + } else { + /* Print IPv6 flows */ + if (pe != NULL) + bprintf(bp, "%9s ", pe->p_name); + else + bprintf(bp, "%9u ", id->proto); + bprintf(bp, "%7d %39s/%-5d ", id->flow_id6, + inet_ntop(AF_INET6, &(id->src_ip6), buff, sizeof(buff)), + id->src_port); + bprintf(bp, " %39s/%-5d ", + inet_ntop(AF_INET6, &(id->dst_ip6), buff, sizeof(buff)), + id->dst_port); + } + pr_u64(bp, &ni->tot_pkts, 4); + pr_u64(bp, &ni->tot_bytes, 8); + bprintf(bp, "%2u %4u %3u", + ni->length, ni->len_bytes, ni->drops); +} + +static void +print_flowset_parms(struct dn_fs *fs, char *prefix) +{ + int l; + char qs[30]; + char plr[30]; + char red[200]; /* Display RED parameters */ + + l = fs->qsize; + if (fs->flags & DN_QSIZE_BYTES) { + if (l >= 8192) + sprintf(qs, "%d KB", l / 1024); + else + sprintf(qs, "%d B", l); + } else + sprintf(qs, "%3d sl.", l); + if (fs->plr) + sprintf(plr, "plr %f", 1.0 * fs->plr / (double)(0x7fffffff)); + else + plr[0] = '\0'; + + if (fs->flags & DN_IS_RED) { /* RED parameters */ + sprintf(red, + "\n\t %cRED w_q %f min_th %d max_th %d max_p %f", + (fs->flags & DN_IS_GENTLE_RED) ? 'G' : ' ', + 1.0 * fs->w_q / (double)(1 << SCALE_RED), + fs->min_th, + fs->max_th, + 1.0 * fs->max_p / (double)(1 << SCALE_RED)); + if (fs->flags & DN_IS_ECN) + strncat(red, " (ecn)", 6); +#ifdef NEW_AQM + /* get AQM parameters */ + } else if (fs->flags & DN_IS_AQM) { + get_extra_parms(fs->fs_nr, red, DN_AQM_PARAMS); +#endif + } else + sprintf(red, "droptail"); + + if (prefix[0]) { + printf("%s %s%s %d queues (%d buckets) %s\n", + prefix, qs, plr, fs->oid.id, fs->buckets, red); + prefix[0] = '\0'; + } else { + printf("q%05d %s%s %d flows (%d buckets) sched %d " + "weight %d lmax %d pri %d %s\n", + fs->fs_nr, qs, plr, fs->oid.id, fs->buckets, + fs->sched_nr, fs->par[0], fs->par[1], fs->par[2], red); + if (fs->flags & DN_HAVE_MASK) + print_mask(&fs->flow_mask); + } +} + +static void +print_extra_delay_parms(struct dn_profile *p) +{ + double loss; + if (p->samples_no <= 0) + return; + + loss = p->loss_level; + loss /= p->samples_no; + printf("\t profile: name \"%s\" loss %f samples %d\n", + p->name, loss, p->samples_no); +} + +static void +flush_buf(char *buf) +{ + if (buf[0]) + printf("%s\n", buf); + buf[0] = '\0'; +} + +/* + * generic list routine. We expect objects in a specific order, i.e. + * PIPES AND SCHEDULERS: + * link; scheduler; internal flowset if any; instances + * we can tell a pipe from the number. + * + * FLOWSETS: + * flowset; queues; + * link i (int queue); scheduler i; si(i) { flowsets() : queues } + */ +static void +list_pipes(struct dn_id *oid, struct dn_id *end) +{ + char buf[160]; /* pending buffer */ + int toPrint = 1; /* print header */ + struct buf_pr bp; + + buf[0] = '\0'; + bp_alloc(&bp, 4096); + for (; oid != end; oid = O_NEXT(oid, oid->len)) { + if (oid->len < sizeof(*oid)) + errx(1, "invalid oid len %d\n", oid->len); + + switch (oid->type) { + default: + flush_buf(buf); + printf("unrecognized object %d size %d\n", oid->type, oid->len); + break; + case DN_TEXT: /* list of attached flowsets */ + { + int i, l; + struct { + struct dn_id id; + uint32_t p[0]; + } *d = (void *)oid; + l = (oid->len - sizeof(*oid))/sizeof(d->p[0]); + if (l == 0) + break; + printf(" Children flowsets: "); + for (i = 0; i < l; i++) + printf("%u ", d->p[i]); + printf("\n"); + break; + } + case DN_CMD_GET: + if (co.verbose) + printf("answer for cmd %d, len %d\n", oid->type, oid->id); + break; + case DN_SCH: { + struct dn_sch *s = (struct dn_sch *)oid; + flush_buf(buf); + printf(" sched %d type %s flags 0x%x %d buckets %d active\n", + s->sched_nr, + s->name, s->flags, s->buckets, s->oid.id); +#ifdef NEW_AQM + char parms[200]; + get_extra_parms(s->sched_nr, parms, DN_SCH_PARAMS); + printf("%s",parms); +#endif + if (s->flags & DN_HAVE_MASK) + print_mask(&s->sched_mask); + } + break; + + case DN_FLOW: + if (toPrint != 0) { + print_header(&((struct dn_flow *)oid)->fid); + toPrint = 0; + } + list_flow(&bp, (struct dn_flow *)oid); + printf("%s\n", bp.buf); + bp_flush(&bp); + break; + + case DN_LINK: { + struct dn_link *p = (struct dn_link *)oid; + double b = p->bandwidth; + char bwbuf[30]; + char burst[5 + 7]; + + /* This starts a new object so flush buffer */ + flush_buf(buf); + /* data rate */ + if (b == 0) + sprintf(bwbuf, "unlimited "); + else if (b >= 1000000) + sprintf(bwbuf, "%7.3f Mbit/s", b/1000000); + else if (b >= 1000) + sprintf(bwbuf, "%7.3f Kbit/s", b/1000); + else + sprintf(bwbuf, "%7.3f bit/s ", b); + + if (humanize_number(burst, sizeof(burst), p->burst, + "", HN_AUTOSCALE, 0) < 0 || co.verbose) + sprintf(burst, "%d", (int)p->burst); + sprintf(buf, "%05d: %s %4d ms burst %s", + p->link_nr % DN_MAX_ID, bwbuf, p->delay, burst); + } + break; + + case DN_FS: + print_flowset_parms((struct dn_fs *)oid, buf); + break; + case DN_PROFILE: + flush_buf(buf); + print_extra_delay_parms((struct dn_profile *)oid); + } + flush_buf(buf); // XXX does it really go here ? + } + + bp_free(&bp); +} + +/* + * Delete pipe, queue or scheduler i + */ +int +ipfw_delete_pipe(int do_pipe, int i) +{ + struct { + struct dn_id oid; + uintptr_t a[1]; /* add more if we want a list */ + } cmd; + oid_fill((void *)&cmd, sizeof(cmd), DN_CMD_DELETE, DN_API_VERSION); + cmd.oid.subtype = (do_pipe == 1) ? DN_LINK : + ( (do_pipe == 2) ? DN_FS : DN_SCH); + cmd.a[0] = i; + i = do_cmd(IP_DUMMYNET3, &cmd, cmd.oid.len); + if (i) { + i = 1; + warn("rule %u: setsockopt(IP_DUMMYNET_DEL)", i); + } + return i; +} + +/* + * Code to parse delay profiles. + * + * Some link types introduce extra delays in the transmission + * of a packet, e.g. because of MAC level framing, contention on + * the use of the channel, MAC level retransmissions and so on. + * From our point of view, the channel is effectively unavailable + * for this extra time, which is constant or variable depending + * on the link type. Additionally, packets may be dropped after this + * time (e.g. on a wireless link after too many retransmissions). + * We can model the additional delay with an empirical curve + * that represents its distribution. + * + * cumulative probability + * 1.0 ^ + * | + * L +-- loss-level x + * | ****** + * | * + * | ***** + * | * + * | ** + * | * + * +-------*-------------------> + * delay + * + * The empirical curve may have both vertical and horizontal lines. + * Vertical lines represent constant delay for a range of + * probabilities; horizontal lines correspond to a discontinuty + * in the delay distribution: the link will use the largest delay + * for a given probability. + * + * To pass the curve to dummynet, we must store the parameters + * in a file as described below, and issue the command + * + * ipfw pipe config ... bw XXX profile ... + * + * The file format is the following, with whitespace acting as + * a separator and '#' indicating the beginning a comment: + * + * samples N + * the number of samples used in the internal + * representation (2..1024; default 100); + * + * loss-level L + * The probability above which packets are lost. + * (0.0 <= L <= 1.0, default 1.0 i.e. no loss); + * + * name identifier + * Optional a name (listed by "ipfw pipe show") + * to identify the distribution; + * + * "delay prob" | "prob delay" + * One of these two lines is mandatory and defines + * the format of the following lines with data points. + * + * XXX YYY + * 2 or more lines representing points in the curve, + * with either delay or probability first, according + * to the chosen format. + * The unit for delay is milliseconds. + * + * Data points does not need to be ordered or equal to the number + * specified in the "samples" line. ipfw will sort and interpolate + * the curve as needed. + * + * Example of a profile file: + + name bla_bla_bla + samples 100 + loss-level 0.86 + prob delay + 0 200 # minimum overhead is 200ms + 0.5 200 + 0.5 300 + 0.8 1000 + 0.9 1300 + 1 1300 + + * Internally, we will convert the curve to a fixed number of + * samples, and when it is time to transmit a packet we will + * model the extra delay as extra bits in the packet. + * + */ + +#define ED_MAX_LINE_LEN 256+ED_MAX_NAME_LEN +#define ED_TOK_SAMPLES "samples" +#define ED_TOK_LOSS "loss-level" +#define ED_TOK_NAME "name" +#define ED_TOK_DELAY "delay" +#define ED_TOK_PROB "prob" +#define ED_TOK_BW "bw" +#define ED_SEPARATORS " \t\n" +#define ED_MIN_SAMPLES_NO 2 + +/* + * returns 1 if s is a non-negative number, with at least one '.' + */ +static int +is_valid_number(const char *s) +{ + int i, dots_found = 0; + int len = strlen(s); + + for (i = 0; i 1)) + return 0; + return 1; +} + +/* + * Take as input a string describing a bandwidth value + * and return the numeric bandwidth value. + * set clocking interface or bandwidth value + */ +static void +read_bandwidth(char *arg, int *bandwidth, char *if_name, int namelen) +{ + if (*bandwidth != -1) + warnx("duplicate token, override bandwidth value!"); + + if (arg[0] >= 'a' && arg[0] <= 'z') { + if (!if_name) { + errx(1, "no if support"); + } + if (namelen >= IFNAMSIZ) + warn("interface name truncated"); + namelen--; + /* interface name */ + strncpy(if_name, arg, namelen); + if_name[namelen] = '\0'; + *bandwidth = 0; + } else { /* read bandwidth value */ + int bw; + char *end = NULL; + + bw = strtoul(arg, &end, 0); + if (*end == 'K' || *end == 'k') { + end++; + bw *= 1000; + } else if (*end == 'M' || *end == 'm') { + end++; + bw *= 1000000; + } + if ((*end == 'B' && + _substrcmp2(end, "Bi", "Bit/s") != 0) || + _substrcmp2(end, "by", "bytes") == 0) + bw *= 8; + + if (bw < 0) + errx(EX_DATAERR, "bandwidth too large"); + + *bandwidth = bw; + if (if_name) + if_name[0] = '\0'; + } +} + +struct point { + double prob; + double delay; +}; + +static int +compare_points(const void *vp1, const void *vp2) +{ + const struct point *p1 = vp1; + const struct point *p2 = vp2; + double res = 0; + + res = p1->prob - p2->prob; + if (res == 0) + res = p1->delay - p2->delay; + if (res < 0) + return -1; + else if (res > 0) + return 1; + else + return 0; +} + +#define ED_EFMT(s) EX_DATAERR,"error in %s at line %d: "#s,filename,lineno + +static void +load_extra_delays(const char *filename, struct dn_profile *p, + struct dn_link *link) +{ + char line[ED_MAX_LINE_LEN]; + FILE *f; + int lineno = 0; + int i; + + int samples = -1; + double loss = -1.0; + char profile_name[ED_MAX_NAME_LEN]; + int delay_first = -1; + int do_points = 0; + struct point points[ED_MAX_SAMPLES_NO]; + int points_no = 0; + + /* XXX link never NULL? */ + p->link_nr = link->link_nr; + + profile_name[0] = '\0'; + f = fopen(filename, "r"); + if (f == NULL) + err(EX_UNAVAILABLE, "fopen: %s", filename); + + while (fgets(line, ED_MAX_LINE_LEN, f)) { /* read commands */ + char *s, *cur = line, *name = NULL, *arg = NULL; + + ++lineno; + + /* parse the line */ + while (cur) { + s = strsep(&cur, ED_SEPARATORS); + if (s == NULL || *s == '#') + break; + if (*s == '\0') + continue; + if (arg) + errx(ED_EFMT("too many arguments")); + if (name == NULL) + name = s; + else + arg = s; + } + if (name == NULL) /* empty line */ + continue; + if (arg == NULL) + errx(ED_EFMT("missing arg for %s"), name); + + if (!strcasecmp(name, ED_TOK_SAMPLES)) { + if (samples > 0) + errx(ED_EFMT("duplicate ``samples'' line")); + if (atoi(arg) <=0) + errx(ED_EFMT("invalid number of samples")); + samples = atoi(arg); + if (samples>ED_MAX_SAMPLES_NO) + errx(ED_EFMT("too many samples, maximum is %d"), + ED_MAX_SAMPLES_NO); + do_points = 0; + } else if (!strcasecmp(name, ED_TOK_BW)) { + char buf[IFNAMSIZ]; + read_bandwidth(arg, &link->bandwidth, buf, sizeof(buf)); + } else if (!strcasecmp(name, ED_TOK_LOSS)) { + if (loss != -1.0) + errx(ED_EFMT("duplicated token: %s"), name); + if (!is_valid_number(arg)) + errx(ED_EFMT("invalid %s"), arg); + loss = atof(arg); + if (loss > 1) + errx(ED_EFMT("%s greater than 1.0"), name); + do_points = 0; + } else if (!strcasecmp(name, ED_TOK_NAME)) { + if (profile_name[0] != '\0') + errx(ED_EFMT("duplicated token: %s"), name); + strncpy(profile_name, arg, sizeof(profile_name) - 1); + profile_name[sizeof(profile_name)-1] = '\0'; + do_points = 0; + } else if (!strcasecmp(name, ED_TOK_DELAY)) { + if (do_points) + errx(ED_EFMT("duplicated token: %s"), name); + delay_first = 1; + do_points = 1; + } else if (!strcasecmp(name, ED_TOK_PROB)) { + if (do_points) + errx(ED_EFMT("duplicated token: %s"), name); + delay_first = 0; + do_points = 1; + } else if (do_points) { + if (!is_valid_number(name) || !is_valid_number(arg)) + errx(ED_EFMT("invalid point found")); + if (delay_first) { + points[points_no].delay = atof(name); + points[points_no].prob = atof(arg); + } else { + points[points_no].delay = atof(arg); + points[points_no].prob = atof(name); + } + if (points[points_no].prob > 1.0) + errx(ED_EFMT("probability greater than 1.0")); + ++points_no; + } else { + errx(ED_EFMT("unrecognised command '%s'"), name); + } + } + + fclose (f); + + if (samples == -1) { + warnx("'%s' not found, assuming 100", ED_TOK_SAMPLES); + samples = 100; + } + + if (loss == -1.0) { + warnx("'%s' not found, assuming no loss", ED_TOK_LOSS); + loss = 1; + } + + /* make sure that there are enough points. */ + if (points_no < ED_MIN_SAMPLES_NO) + errx(ED_EFMT("too few samples, need at least %d"), + ED_MIN_SAMPLES_NO); + + qsort(points, points_no, sizeof(struct point), compare_points); + + /* interpolation */ + for (i = 0; isamples[ix] = x1; + } else { + double m = (y2-y1)/(x2-x1); + double c = y1 - m*x1; + for (; ixsamples[ix] = (ix - c)/m; + } + } + p->samples_no = samples; + p->loss_level = loss * samples; + strncpy(p->name, profile_name, sizeof(p->name)); +} + +#ifdef NEW_AQM + +/* Parse AQM/extra scheduler parameters */ +static int +process_extra_parms(int *ac, char **av, struct dn_extra_parms *ep, + uint16_t type) +{ + int i; + + /* use kernel defaults */ + for (i=0; ipar[i] = -1; + + switch(type) { + case TOK_CODEL: + case TOK_FQ_CODEL: + /* Codel + * 0- target, 1- interval, 2- flags, + * FQ_CODEL + * 3- quantum, 4- limit, 5- flows + */ + if (type==TOK_CODEL) + ep->par[2] = 0; + else + ep->par[2] = CODEL_ECN_ENABLED; + + while (*ac > 0) { + int tok = match_token(aqm_params, *av); + (*ac)--; av++; + switch(tok) { + case TOK_TARGET: + if (*ac <= 0 || time_to_us(av[0]) < 0) + errx(EX_DATAERR, "target needs time\n"); + + ep->par[0] = time_to_us(av[0]); + (*ac)--; av++; + break; + + case TOK_INTERVAL: + if (*ac <= 0 || time_to_us(av[0]) < 0) + errx(EX_DATAERR, "interval needs time\n"); + + ep->par[1] = time_to_us(av[0]); + (*ac)--; av++; + break; + + case TOK_ECN: + ep->par[2] = CODEL_ECN_ENABLED; + break; + case TOK_NO_ECN: + ep->par[2] &= ~CODEL_ECN_ENABLED; + break; + /* Config fq_codel parameters */ + case TOK_QUANTUM: + if (type != TOK_FQ_CODEL) + errx(EX_DATAERR, "quantum is not for codel\n"); + if (*ac <= 0 || !is_valid_number(av[0])) + errx(EX_DATAERR, "quantum needs number\n"); + + ep->par[3]= atoi(av[0]); + (*ac)--; av++; + break; + + case TOK_LIMIT: + if (type != TOK_FQ_CODEL) + errx(EX_DATAERR, "limit is not for codel, use queue instead\n"); + if (*ac <= 0 || !is_valid_number(av[0])) + errx(EX_DATAERR, "limit needs number\n"); + + ep->par[4] = atoi(av[0]); + (*ac)--; av++; + break; + + case TOK_FLOWS: + if (type != TOK_FQ_CODEL) + errx(EX_DATAERR, "flows is not for codel\n"); + if (*ac <= 0 || !is_valid_number(av[0])) + errx(EX_DATAERR, "flows needs number\n"); + + ep->par[5] = atoi(av[0]); + (*ac)--; av++; + break; + + default: + printf("%s is Invalid parameter\n", av[-1]); + } + } + break; + case TOK_PIE: + case TOK_FQ_PIE: + /* PIE + * 0- target , 1- tupdate, 2- max_burst, + * 3- max_ecnth, 4- alpha, + * 5- beta, 6- flags + * FQ_CODEL + * 7- quantum, 8- limit, 9- flows + */ + + if ( type == TOK_PIE) + ep->par[6] = PIE_CAPDROP_ENABLED | PIE_DEPRATEEST_ENABLED + | PIE_DERAND_ENABLED; + else + /* for FQ-PIE, use TS mode */ + ep->par[6] = PIE_CAPDROP_ENABLED | PIE_DERAND_ENABLED + | PIE_ECN_ENABLED; + + while (*ac > 0) { + int tok = match_token(aqm_params, *av); + (*ac)--; av++; + switch(tok) { + case TOK_TARGET: + if (*ac <= 0 || time_to_us(av[0]) < 0) + errx(EX_DATAERR, "target needs time\n"); + + ep->par[0] = time_to_us(av[0]); + (*ac)--; av++; + break; + + case TOK_TUPDATE: + if (*ac <= 0 || time_to_us(av[0]) < 0) + errx(EX_DATAERR, "tupdate needs time\n"); + + ep->par[1] = time_to_us(av[0]); + (*ac)--; av++; + break; + + case TOK_MAX_BURST: + if (*ac <= 0 || time_to_us(av[0]) < 0) + errx(EX_DATAERR, "max_burst needs time\n"); + + ep->par[2] = time_to_us(av[0]); + (*ac)--; av++; + break; + + case TOK_MAX_ECNTH: + if (*ac <= 0 || !is_valid_number(av[0])) + errx(EX_DATAERR, "max_ecnth needs number\n"); + + ep->par[3] = atof(av[0]) * PIE_SCALE; + (*ac)--; av++; + break; + + case TOK_ALPHA: + if (*ac <= 0 || !is_valid_number(av[0])) + errx(EX_DATAERR, "alpha needs number\n"); + + ep->par[4] = atof(av[0]) * PIE_SCALE; + (*ac)--; av++; + break; + + case TOK_BETA: + if (*ac <= 0 || !is_valid_number(av[0])) + errx(EX_DATAERR, "beta needs number\n"); + + ep->par[5] = atof(av[0]) * PIE_SCALE; + (*ac)--; av++; + break; + + case TOK_ECN: + ep->par[6] |= PIE_ECN_ENABLED; + break; + case TOK_NO_ECN: + ep->par[6] &= ~PIE_ECN_ENABLED; + break; + + case TOK_CAPDROP: + ep->par[6] |= PIE_CAPDROP_ENABLED; + break; + case TOK_NO_CAPDROP: + ep->par[6] &= ~PIE_CAPDROP_ENABLED; + break; + + case TOK_ONOFF: + ep->par[6] |= PIE_ON_OFF_MODE_ENABLED; + break; + + case TOK_DRE: + ep->par[6] |= PIE_DEPRATEEST_ENABLED; + break; + + case TOK_TS: + ep->par[6] &= ~PIE_DEPRATEEST_ENABLED; + break; + + case TOK_DERAND: + ep->par[6] |= PIE_DERAND_ENABLED; + break; + case TOK_NO_DERAND: + ep->par[6] &= ~PIE_DERAND_ENABLED; + break; + + /* Config fq_pie parameters */ + case TOK_QUANTUM: + if (type != TOK_FQ_PIE) + errx(EX_DATAERR, "quantum is not for pie\n"); + if (*ac <= 0 || !is_valid_number(av[0])) + errx(EX_DATAERR, "quantum needs number\n"); + + ep->par[7]= atoi(av[0]); + (*ac)--; av++; + break; + + case TOK_LIMIT: + if (type != TOK_FQ_PIE) + errx(EX_DATAERR, "limit is not for pie, use queue instead\n"); + if (*ac <= 0 || !is_valid_number(av[0])) + errx(EX_DATAERR, "limit needs number\n"); + + ep->par[8] = atoi(av[0]); + (*ac)--; av++; + break; + + case TOK_FLOWS: + if (type != TOK_FQ_PIE) + errx(EX_DATAERR, "flows is not for pie\n"); + if (*ac <= 0 || !is_valid_number(av[0])) + errx(EX_DATAERR, "flows needs number\n"); + + ep->par[9] = atoi(av[0]); + (*ac)--; av++; + break; + + + default: + printf("%s is invalid parameter\n", av[-1]); + } + } + break; + } + + return 0; +} + +#endif + + +/* + * configuration of pipes, schedulers, flowsets. + * When we configure a new scheduler, an empty pipe is created, so: + * + * do_pipe = 1 -> "pipe N config ..." only for backward compatibility + * sched N+Delta type fifo sched_mask ... + * pipe N+Delta + * flowset N+Delta pipe N+Delta (no parameters) + * sched N type wf2q+ sched_mask ... + * pipe N + * + * do_pipe = 2 -> flowset N config + * flowset N parameters + * + * do_pipe = 3 -> sched N config + * sched N parameters (default no pipe) + * optional Pipe N config ... + * pipe ==> + */ +void +ipfw_config_pipe(int ac, char **av) +{ + int i; + u_int j; + char *end; + struct dn_id *buf, *base; + struct dn_sch *sch = NULL; + struct dn_link *p = NULL; + struct dn_fs *fs = NULL; + struct dn_profile *pf = NULL; + struct ipfw_flow_id *mask = NULL; +#ifdef NEW_AQM + struct dn_extra_parms *aqm_extra; + struct dn_extra_parms *sch_extra; + int lmax_extra; +#endif + + int lmax; + uint32_t _foo = 0, *flags = &_foo , *buckets = &_foo; + + /* + * allocate space for 1 header, + * 1 scheduler, 1 link, 1 flowset, 1 profile + */ + lmax = sizeof(struct dn_id); /* command header */ + lmax += sizeof(struct dn_sch) + sizeof(struct dn_link) + + sizeof(struct dn_fs) + sizeof(struct dn_profile); + +#ifdef NEW_AQM + /* Extra Params */ + lmax_extra = sizeof(struct dn_extra_parms); + /* two lmax_extra because one for AQM params and another + * sch params + */ + lmax += lmax_extra*2; +#endif + + av++; ac--; + /* Pipe number */ + if (ac && isdigit(**av)) { + i = atoi(*av); av++; ac--; + } else + i = -1; + if (i <= 0) + errx(EX_USAGE, "need a pipe/flowset/sched number"); + base = buf = safe_calloc(1, lmax); + /* all commands start with a 'CONFIGURE' and a version */ + o_next(&buf, sizeof(struct dn_id), DN_CMD_CONFIG); + base->id = DN_API_VERSION; + + switch (co.do_pipe) { + case 1: /* "pipe N config ..." */ + /* Allocate space for the WF2Q+ scheduler, its link + * and the FIFO flowset. Set the number, but leave + * the scheduler subtype and other parameters to 0 + * so the kernel will use appropriate defaults. + * XXX todo: add a flag to record if a parameter + * is actually configured. + * If we do a 'pipe config' mask -> sched_mask. + * The FIFO scheduler and link are derived from the + * WF2Q+ one in the kernel. + */ +#ifdef NEW_AQM + sch_extra = o_next(&buf, lmax_extra, DN_TEXT); + sch_extra ->oid.subtype = 0; /* don't configure scheduler */ +#endif + sch = o_next(&buf, sizeof(*sch), DN_SCH); + p = o_next(&buf, sizeof(*p), DN_LINK); +#ifdef NEW_AQM + aqm_extra = o_next(&buf, lmax_extra, DN_TEXT); + aqm_extra ->oid.subtype = 0; /* don't configure AQM */ +#endif + fs = o_next(&buf, sizeof(*fs), DN_FS); + + sch->sched_nr = i; + sch->oid.subtype = 0; /* defaults to WF2Q+ */ + mask = &sch->sched_mask; + flags = &sch->flags; + buckets = &sch->buckets; + *flags |= DN_PIPE_CMD; + + p->link_nr = i; + + /* This flowset is only for the FIFO scheduler */ + fs->fs_nr = i + 2*DN_MAX_ID; + fs->sched_nr = i + DN_MAX_ID; + break; + + case 2: /* "queue N config ... " */ +#ifdef NEW_AQM + aqm_extra = o_next(&buf, lmax_extra, DN_TEXT); + aqm_extra ->oid.subtype = 0; +#endif + fs = o_next(&buf, sizeof(*fs), DN_FS); + fs->fs_nr = i; + mask = &fs->flow_mask; + flags = &fs->flags; + buckets = &fs->buckets; + break; + + case 3: /* "sched N config ..." */ +#ifdef NEW_AQM + sch_extra = o_next(&buf, lmax_extra, DN_TEXT); + sch_extra ->oid.subtype = 0; +#endif + sch = o_next(&buf, sizeof(*sch), DN_SCH); +#ifdef NEW_AQM + aqm_extra = o_next(&buf, lmax_extra, DN_TEXT); + aqm_extra ->oid.subtype = 0; +#endif + fs = o_next(&buf, sizeof(*fs), DN_FS); + sch->sched_nr = i; + mask = &sch->sched_mask; + flags = &sch->flags; + buckets = &sch->buckets; + /* fs is used only with !MULTIQUEUE schedulers */ + fs->fs_nr = i + DN_MAX_ID; + fs->sched_nr = i; + break; + } + /* set to -1 those fields for which we want to reuse existing + * values from the kernel. + * Also, *_nr and subtype = 0 mean reuse the value from the kernel. + * XXX todo: support reuse of the mask. + */ + if (p) + p->bandwidth = -1; + for (j = 0; j < sizeof(fs->par)/sizeof(fs->par[0]); j++) + fs->par[j] = -1; + while (ac > 0) { + double d; + int tok = match_token(dummynet_params, *av); + ac--; av++; + + switch(tok) { + case TOK_NOERROR: + NEED(fs, "noerror is only for pipes"); + fs->flags |= DN_NOERROR; + break; + + case TOK_PLR: + NEED(fs, "plr is only for pipes"); + NEED1("plr needs argument 0..1\n"); + d = strtod(av[0], NULL); + if (d > 1) + d = 1; + else if (d < 0) + d = 0; + fs->plr = (int)(d*0x7fffffff); + ac--; av++; + break; + + case TOK_QUEUE: + NEED(fs, "queue is only for pipes or flowsets"); + NEED1("queue needs queue size\n"); + end = NULL; + fs->qsize = strtoul(av[0], &end, 0); + if (*end == 'K' || *end == 'k') { + fs->flags |= DN_QSIZE_BYTES; + fs->qsize *= 1024; + } else if (*end == 'B' || + _substrcmp2(end, "by", "bytes") == 0) { + fs->flags |= DN_QSIZE_BYTES; + } + ac--; av++; + break; + + case TOK_BUCKETS: + NEED(fs, "buckets is only for pipes or flowsets"); + NEED1("buckets needs argument\n"); + *buckets = strtoul(av[0], NULL, 0); + ac--; av++; + break; + + case TOK_FLOW_MASK: + case TOK_SCHED_MASK: + case TOK_MASK: + NEED(mask, "tok_mask"); + NEED1("mask needs mask specifier\n"); + /* + * per-flow queue, mask is dst_ip, dst_port, + * src_ip, src_port, proto measured in bits + */ + + bzero(mask, sizeof(*mask)); + end = NULL; + + while (ac >= 1) { + uint32_t *p32 = NULL; + uint16_t *p16 = NULL; + uint32_t *p20 = NULL; + struct in6_addr *pa6 = NULL; + uint32_t a; + + tok = match_token(dummynet_params, *av); + ac--; av++; + switch(tok) { + case TOK_ALL: + /* + * special case, all bits significant + * except 'extra' (the queue number) + */ + mask->dst_ip = ~0; + mask->src_ip = ~0; + mask->dst_port = ~0; + mask->src_port = ~0; + mask->proto = ~0; + n2mask(&mask->dst_ip6, 128); + n2mask(&mask->src_ip6, 128); + mask->flow_id6 = ~0; + *flags |= DN_HAVE_MASK; + goto end_mask; + + case TOK_QUEUE: + mask->extra = ~0; + *flags |= DN_HAVE_MASK; + goto end_mask; + + case TOK_DSTIP: + mask->addr_type = 4; + p32 = &mask->dst_ip; + break; + + case TOK_SRCIP: + mask->addr_type = 4; + p32 = &mask->src_ip; + break; + + case TOK_DSTIP6: + mask->addr_type = 6; + pa6 = &mask->dst_ip6; + break; + + case TOK_SRCIP6: + mask->addr_type = 6; + pa6 = &mask->src_ip6; + break; + + case TOK_FLOWID: + mask->addr_type = 6; + p20 = &mask->flow_id6; + break; + + case TOK_DSTPORT: + p16 = &mask->dst_port; + break; + + case TOK_SRCPORT: + p16 = &mask->src_port; + break; + + case TOK_PROTO: + break; + + default: + ac++; av--; /* backtrack */ + goto end_mask; + } + if (ac < 1) + errx(EX_USAGE, "mask: value missing"); + if (*av[0] == '/') { + a = strtoul(av[0]+1, &end, 0); + if (pa6 == NULL) + a = (a == 32) ? ~0 : (1 << a) - 1; + } else + a = strtoul(av[0], &end, 0); + if (p32 != NULL) + *p32 = a; + else if (p16 != NULL) { + if (a > 0xFFFF) + errx(EX_DATAERR, + "port mask must be 16 bit"); + *p16 = (uint16_t)a; + } else if (p20 != NULL) { + if (a > 0xfffff) + errx(EX_DATAERR, + "flow_id mask must be 20 bit"); + *p20 = (uint32_t)a; + } else if (pa6 != NULL) { + if (a > 128) + errx(EX_DATAERR, + "in6addr invalid mask len"); + else + n2mask(pa6, a); + } else { + if (a > 0xFF) + errx(EX_DATAERR, + "proto mask must be 8 bit"); + mask->proto = (uint8_t)a; + } + if (a != 0) + *flags |= DN_HAVE_MASK; + ac--; av++; + } /* end while, config masks */ +end_mask: + break; +#ifdef NEW_AQM + case TOK_CODEL: + case TOK_PIE: + NEED(fs, "codel/pie is only for flowsets"); + + fs->flags &= ~(DN_IS_RED|DN_IS_GENTLE_RED); + fs->flags |= DN_IS_AQM; + + strcpy(aqm_extra->name,av[-1]); + aqm_extra->oid.subtype = DN_AQM_PARAMS; + + process_extra_parms(&ac, av, aqm_extra, tok); + break; + + case TOK_FQ_CODEL: + case TOK_FQ_PIE: + if (!strcmp(av[-1],"type")) + errx(EX_DATAERR, "use type before fq_codel/fq_pie"); + + NEED(sch, "fq_codel/fq_pie is only for schd"); + strcpy(sch_extra->name,av[-1]); + sch_extra->oid.subtype = DN_SCH_PARAMS; + process_extra_parms(&ac, av, sch_extra, tok); + break; +#endif + case TOK_RED: + case TOK_GRED: + NEED1("red/gred needs w_q/min_th/max_th/max_p\n"); + fs->flags |= DN_IS_RED; + if (tok == TOK_GRED) + fs->flags |= DN_IS_GENTLE_RED; + /* + * the format for parameters is w_q/min_th/max_th/max_p + */ + if ((end = strsep(&av[0], "/"))) { + double w_q = strtod(end, NULL); + if (w_q > 1 || w_q <= 0) + errx(EX_DATAERR, "0 < w_q <= 1"); + fs->w_q = (int) (w_q * (1 << SCALE_RED)); + } + if ((end = strsep(&av[0], "/"))) { + fs->min_th = strtoul(end, &end, 0); + if (*end == 'K' || *end == 'k') + fs->min_th *= 1024; + } + if ((end = strsep(&av[0], "/"))) { + fs->max_th = strtoul(end, &end, 0); + if (*end == 'K' || *end == 'k') + fs->max_th *= 1024; + } + if ((end = strsep(&av[0], "/"))) { + double max_p = strtod(end, NULL); + if (max_p > 1 || max_p < 0) + errx(EX_DATAERR, "0 <= max_p <= 1"); + fs->max_p = (int)(max_p * (1 << SCALE_RED)); + } + ac--; av++; + break; + + case TOK_ECN: + fs->flags |= DN_IS_ECN; + break; + + case TOK_DROPTAIL: + NEED(fs, "droptail is only for flowsets"); + fs->flags &= ~(DN_IS_RED|DN_IS_GENTLE_RED); + break; + + case TOK_BW: + NEED(p, "bw is only for links"); + NEED1("bw needs bandwidth or interface\n"); + read_bandwidth(av[0], &p->bandwidth, NULL, 0); + ac--; av++; + break; + + case TOK_DELAY: + NEED(p, "delay is only for links"); + NEED1("delay needs argument 0..10000ms\n"); + p->delay = strtoul(av[0], NULL, 0); + ac--; av++; + break; + + case TOK_TYPE: { + int l; + NEED(sch, "type is only for schedulers"); + NEED1("type needs a string"); + l = strlen(av[0]); + if (l == 0 || l > 15) + errx(1, "type %s too long\n", av[0]); + strcpy(sch->name, av[0]); + sch->oid.subtype = 0; /* use string */ +#ifdef NEW_AQM + /* if fq_codel is selected, consider all tokens after it + * as parameters + */ + if (!strcasecmp(av[0],"fq_codel") || !strcasecmp(av[0],"fq_pie")){ + strcpy(sch_extra->name,av[0]); + sch_extra->oid.subtype = DN_SCH_PARAMS; + process_extra_parms(&ac, av, sch_extra, tok); + } else { + ac--;av++; + } +#else + ac--;av++; +#endif + break; + } + + case TOK_WEIGHT: + NEED(fs, "weight is only for flowsets"); + NEED1("weight needs argument\n"); + fs->par[0] = strtol(av[0], &end, 0); + ac--; av++; + break; + + case TOK_LMAX: + NEED(fs, "lmax is only for flowsets"); + NEED1("lmax needs argument\n"); + fs->par[1] = strtol(av[0], &end, 0); + ac--; av++; + break; + + case TOK_PRI: + NEED(fs, "priority is only for flowsets"); + NEED1("priority needs argument\n"); + fs->par[2] = strtol(av[0], &end, 0); + ac--; av++; + break; + + case TOK_SCHED: + case TOK_PIPE: + NEED(fs, "pipe/sched"); + NEED1("pipe/link/sched needs number\n"); + fs->sched_nr = strtoul(av[0], &end, 0); + ac--; av++; + break; + + case TOK_PROFILE: + NEED((!pf), "profile already set"); + NEED(p, "profile"); + { + NEED1("extra delay needs the file name\n"); + pf = o_next(&buf, sizeof(*pf), DN_PROFILE); + load_extra_delays(av[0], pf, p); //XXX can't fail? + --ac; ++av; + } + break; + + case TOK_BURST: + NEED(p, "burst"); + NEED1("burst needs argument\n"); + errno = 0; + if (expand_number(av[0], &p->burst) < 0) + if (errno != ERANGE) + errx(EX_DATAERR, + "burst: invalid argument"); + if (errno || p->burst > (1ULL << 48) - 1) + errx(EX_DATAERR, + "burst: out of range (0..2^48-1)"); + ac--; av++; + break; + + default: + errx(EX_DATAERR, "unrecognised option ``%s''", av[-1]); + } + } + + /* check validity of parameters */ + if (p) { + if (p->delay > 10000) + errx(EX_DATAERR, "delay must be < 10000"); + if (p->bandwidth == -1) + p->bandwidth = 0; + } + if (fs) { + /* XXX accept a 0 scheduler to keep the default */ + if (fs->flags & DN_QSIZE_BYTES) { + size_t len; + long limit; + + len = sizeof(limit); + if (sysctlbyname("net.inet.ip.dummynet.pipe_byte_limit", + &limit, &len, NULL, 0) == -1) + limit = 1024*1024; + if (fs->qsize > limit) + errx(EX_DATAERR, "queue size must be < %ldB", limit); + } else { + size_t len; + long limit; + + len = sizeof(limit); + if (sysctlbyname("net.inet.ip.dummynet.pipe_slot_limit", + &limit, &len, NULL, 0) == -1) + limit = 100; + if (fs->qsize > limit) + errx(EX_DATAERR, "2 <= queue size <= %ld", limit); + } + +#ifdef NEW_AQM + if ((fs->flags & DN_IS_ECN) && !((fs->flags & DN_IS_RED)|| + (fs->flags & DN_IS_AQM))) + errx(EX_USAGE, "ECN can be used with red/gred/" + "codel/fq_codel only!"); +#else + if ((fs->flags & DN_IS_ECN) && !(fs->flags & DN_IS_RED)) + errx(EX_USAGE, "enable red/gred for ECN"); + +#endif + + if (fs->flags & DN_IS_RED) { + size_t len; + int lookup_depth, avg_pkt_size; + + if (!(fs->flags & DN_IS_ECN) && (fs->min_th >= fs->max_th)) + errx(EX_DATAERR, "min_th %d must be < than max_th %d", + fs->min_th, fs->max_th); + else if ((fs->flags & DN_IS_ECN) && (fs->min_th > fs->max_th)) + errx(EX_DATAERR, "min_th %d must be =< than max_th %d", + fs->min_th, fs->max_th); + + if (fs->max_th == 0) + errx(EX_DATAERR, "max_th must be > 0"); + + len = sizeof(int); + if (sysctlbyname("net.inet.ip.dummynet.red_lookup_depth", + &lookup_depth, &len, NULL, 0) == -1) + lookup_depth = 256; + if (lookup_depth == 0) + errx(EX_DATAERR, "net.inet.ip.dummynet.red_lookup_depth" + " must be greater than zero"); + + len = sizeof(int); + if (sysctlbyname("net.inet.ip.dummynet.red_avg_pkt_size", + &avg_pkt_size, &len, NULL, 0) == -1) + avg_pkt_size = 512; + + if (avg_pkt_size == 0) + errx(EX_DATAERR, + "net.inet.ip.dummynet.red_avg_pkt_size must" + " be greater than zero"); + +#if 0 /* the following computation is now done in the kernel */ + /* + * Ticks needed for sending a medium-sized packet. + * Unfortunately, when we are configuring a WF2Q+ queue, we + * do not have bandwidth information, because that is stored + * in the parent pipe, and also we have multiple queues + * competing for it. So we set s=0, which is not very + * correct. But on the other hand, why do we want RED with + * WF2Q+ ? + */ + if (p.bandwidth==0) /* this is a WF2Q+ queue */ + s = 0; + else + s = (double)ck.hz * avg_pkt_size * 8 / p.bandwidth; + /* + * max idle time (in ticks) before avg queue size becomes 0. + * NOTA: (3/w_q) is approx the value x so that + * (1-w_q)^x < 10^-3. + */ + w_q = ((double)fs->w_q) / (1 << SCALE_RED); + idle = s * 3. / w_q; + fs->lookup_step = (int)idle / lookup_depth; + if (!fs->lookup_step) + fs->lookup_step = 1; + weight = 1 - w_q; + for (t = fs->lookup_step; t > 1; --t) + weight *= 1 - w_q; + fs->lookup_weight = (int)(weight * (1 << SCALE_RED)); +#endif /* code moved in the kernel */ + } + } + + i = do_cmd(IP_DUMMYNET3, base, (char *)buf - (char *)base); + + if (i) + err(1, "setsockopt(%s)", "IP_DUMMYNET_CONFIGURE"); +} + +void +dummynet_flush(void) +{ + struct dn_id oid; + oid_fill(&oid, sizeof(oid), DN_CMD_FLUSH, DN_API_VERSION); + do_cmd(IP_DUMMYNET3, &oid, oid.len); +} + +/* Parse input for 'ipfw [pipe|sched|queue] show [range list]' + * Returns the number of ranges, and possibly stores them + * in the array v of size len. + */ +static int +parse_range(int ac, char *av[], uint32_t *v, int len) +{ + int n = 0; + char *endptr, *s; + uint32_t base[2]; + + if (v == NULL || len < 2) { + v = base; + len = 2; + } + + for (s = *av; s != NULL; av++, ac--) { + v[0] = strtoul(s, &endptr, 10); + v[1] = (*endptr != '-') ? v[0] : + strtoul(endptr+1, &endptr, 10); + if (*endptr == '\0') { /* prepare for next round */ + s = (ac > 0) ? *(av+1) : NULL; + } else { + if (*endptr != ',') { + warn("invalid number: %s", s); + s = ++endptr; + continue; + } + /* continue processing from here */ + s = ++endptr; + ac++; + av--; + } + if (v[1] < v[0] || + v[1] >= DN_MAX_ID-1 || + v[1] >= DN_MAX_ID-1) { + continue; /* invalid entry */ + } + n++; + /* translate if 'pipe list' */ + if (co.do_pipe == 1) { + v[0] += DN_MAX_ID; + v[1] += DN_MAX_ID; + } + v = (n*2 < len) ? v + 2 : base; + } + return n; +} + +/* main entry point for dummynet list functions. co.do_pipe indicates + * which function we want to support. + * av may contain filtering arguments, either individual entries + * or ranges, or lists (space or commas are valid separators). + * Format for a range can be n1-n2 or n3 n4 n5 ... + * In a range n1 must be <= n2, otherwise the range is ignored. + * A number 'n4' is translate in a range 'n4-n4' + * All number must be > 0 and < DN_MAX_ID-1 + */ +void +dummynet_list(int ac, char *av[], int show_counters) +{ + struct dn_id *oid, *x = NULL; + int ret, i; + int n; /* # of ranges */ + u_int buflen, l; + u_int max_size; /* largest obj passed up */ + + (void)show_counters; // XXX unused, but we should use it. + ac--; + av++; /* skip 'list' | 'show' word */ + + n = parse_range(ac, av, NULL, 0); /* Count # of ranges. */ + + /* Allocate space to store ranges */ + l = sizeof(*oid) + sizeof(uint32_t) * n * 2; + oid = safe_calloc(1, l); + oid_fill(oid, l, DN_CMD_GET, DN_API_VERSION); + + if (n > 0) /* store ranges in idx */ + parse_range(ac, av, (uint32_t *)(oid + 1), n*2); + /* + * Compute the size of the largest object returned. If the + * response leaves at least this much spare space in the + * buffer, then surely the response is complete; otherwise + * there might be a risk of truncation and we will need to + * retry with a larger buffer. + * XXX don't bother with smaller structs. + */ + max_size = sizeof(struct dn_fs); + if (max_size < sizeof(struct dn_sch)) + max_size = sizeof(struct dn_sch); + if (max_size < sizeof(struct dn_flow)) + max_size = sizeof(struct dn_flow); + + switch (co.do_pipe) { + case 1: + oid->subtype = DN_LINK; /* list pipe */ + break; + case 2: + oid->subtype = DN_FS; /* list queue */ + break; + case 3: + oid->subtype = DN_SCH; /* list sched */ + break; + } + + /* + * Ask the kernel an estimate of the required space (result + * in oid.id), unless we are requesting a subset of objects, + * in which case the kernel does not give an exact answer. + * In any case, space might grow in the meantime due to the + * creation of new queues, so we must be prepared to retry. + */ + if (n > 0) { + buflen = 4*1024; + } else { + ret = do_cmd(-IP_DUMMYNET3, oid, (uintptr_t)&l); + if (ret != 0 || oid->id <= sizeof(*oid)) + goto done; + buflen = oid->id + max_size; + oid->len = sizeof(*oid); /* restore */ + } + /* Try a few times, until the buffer fits */ + for (i = 0; i < 20; i++) { + l = buflen; + x = safe_realloc(x, l); + bcopy(oid, x, oid->len); + ret = do_cmd(-IP_DUMMYNET3, x, (uintptr_t)&l); + if (ret != 0 || x->id <= sizeof(*oid)) + goto done; /* no response */ + if (l + max_size <= buflen) + break; /* ok */ + buflen *= 2; /* double for next attempt */ + } + list_pipes(x, O_NEXT(x, l)); +done: + if (x) + free(x); + free(oid); +} diff --git a/tools/ipfw/ipfw.8 b/tools/ipfw/ipfw.8 new file mode 100644 index 000000000..ce8823fbb --- /dev/null +++ b/tools/ipfw/ipfw.8 @@ -0,0 +1,3725 @@ +.\" +.\" $FreeBSD$ +.\" +.Dd May 26, 2016 +.Dt IPFW 8 +.Os +.Sh NAME +.Nm ipfw +.Nd User interface for firewall, traffic shaper, packet scheduler, +in-kernel NAT. +.Sh SYNOPSIS +.Ss FIREWALL CONFIGURATION +.Nm +.Op Fl cq +.Cm add +.Ar rule +.Nm +.Op Fl acdefnNStT +.Op Cm set Ar N +.Brq Cm list | show +.Op Ar rule | first-last ... +.Nm +.Op Fl f | q +.Op Cm set Ar N +.Cm flush +.Nm +.Op Fl q +.Op Cm set Ar N +.Brq Cm delete | zero | resetlog +.Op Ar number ... +.Pp +.Nm +.Cm set Oo Cm disable Ar number ... Oc Op Cm enable Ar number ... +.Nm +.Cm set move +.Op Cm rule +.Ar number Cm to Ar number +.Nm +.Cm set swap Ar number number +.Nm +.Cm set show +.Ss SYSCTL SHORTCUTS +.Nm +.Cm enable +.Brq Cm firewall | altq | one_pass | debug | verbose | dyn_keepalive +.Nm +.Cm disable +.Brq Cm firewall | altq | one_pass | debug | verbose | dyn_keepalive +.Ss LOOKUP TABLES +.Nm +.Oo Cm set Ar N Oc Cm table Ar name Cm create Ar create-options +.Nm +.Oo Cm set Ar N Oc Cm table Ar name Cm destroy +.Nm +.Oo Cm set Ar N Oc Cm table Ar name Cm modify Ar modify-options +.Nm +.Oo Cm set Ar N Oc Cm table Ar name Cm swap Ar name +.Nm +.Oo Cm set Ar N Oc Cm table Ar name Cm add Ar table-key Op Ar value +.Nm +.Oo Cm set Ar N Oc Cm table Ar name Cm add Op Ar table-key Ar value ... +.Nm +.Oo Cm set Ar N Oc Cm table Ar name Cm atomic add Op Ar table-key Ar value ... +.Nm +.Oo Cm set Ar N Oc Cm table Ar name Cm delete Op Ar table-key ... +.Nm +.Oo Cm set Ar N Oc Cm table Ar name Cm lookup Ar addr +.Nm +.Oo Cm set Ar N Oc Cm table Ar name Cm lock +.Nm +.Oo Cm set Ar N Oc Cm table Ar name Cm unlock +.Nm +.Oo Cm set Ar N Oc Cm table +.Brq Ar name | all +.Cm list +.Nm +.Oo Cm set Ar N Oc Cm table +.Brq Ar name | all +.Cm info +.Nm +.Oo Cm set Ar N Oc Cm table +.Brq Ar name | all +.Cm detail +.Nm +.Oo Cm set Ar N Oc Cm table +.Brq Ar name | all +.Cm flush +.Ss DUMMYNET CONFIGURATION (TRAFFIC SHAPER AND PACKET SCHEDULER) +.Nm +.Brq Cm pipe | queue | sched +.Ar number +.Cm config +.Ar config-options +.Nm +.Op Fl s Op Ar field +.Brq Cm pipe | queue | sched +.Brq Cm delete | list | show +.Op Ar number ... +.Ss IN-KERNEL NAT +.Nm +.Op Fl q +.Cm nat +.Ar number +.Cm config +.Ar config-options +.Pp +.Nm +.Op Fl cfnNqS +.Oo +.Fl p Ar preproc +.Oo +.Ar preproc-flags +.Oc +.Oc +.Ar pathname +.Ss INTERNAL DIAGNOSTICS +.Nm +.Cm internal iflist +.Nm +.Cm internal talist +.Nm +.Cm internal vlist +.Sh DESCRIPTION +The +.Nm +utility is the user interface for controlling the +.Xr ipfw 4 +firewall, the +.Xr dummynet 4 +traffic shaper/packet scheduler, and the +in-kernel NAT services. +.Pp +A firewall configuration, or +.Em ruleset , +is made of a list of +.Em rules +numbered from 1 to 65535. +Packets are passed to the firewall +from a number of different places in the protocol stack +(depending on the source and destination of the packet, +it is possible for the firewall to be +invoked multiple times on the same packet). +The packet passed to the firewall is compared +against each of the rules in the +.Em ruleset , +in rule-number order +(multiple rules with the same number are permitted, in which case +they are processed in order of insertion). +When a match is found, the action corresponding to the +matching rule is performed. +.Pp +Depending on the action and certain system settings, packets +can be reinjected into the firewall at some rule after the +matching one for further processing. +.Pp +A ruleset always includes a +.Em default +rule (numbered 65535) which cannot be modified or deleted, +and matches all packets. +The action associated with the +.Em default +rule can be either +.Cm deny +or +.Cm allow +depending on how the kernel is configured. +.Pp +If the ruleset includes one or more rules with the +.Cm keep-state +or +.Cm limit +option, +the firewall will have a +.Em stateful +behaviour, i.e., upon a match it will create +.Em dynamic rules , +i.e., rules that match packets with the same 5-tuple +(protocol, source and destination addresses and ports) +as the packet which caused their creation. +Dynamic rules, which have a limited lifetime, are checked +at the first occurrence of a +.Cm check-state , +.Cm keep-state +or +.Cm limit +rule, and are typically used to open the firewall on-demand to +legitimate traffic only. +See the +.Sx STATEFUL FIREWALL +and +.Sx EXAMPLES +Sections below for more information on the stateful behaviour of +.Nm . +.Pp +All rules (including dynamic ones) have a few associated counters: +a packet count, a byte count, a log count and a timestamp +indicating the time of the last match. +Counters can be displayed or reset with +.Nm +commands. +.Pp +Each rule belongs to one of 32 different +.Em sets +, and there are +.Nm +commands to atomically manipulate sets, such as enable, +disable, swap sets, move all rules in a set to another +one, delete all rules in a set. +These can be useful to +install temporary configurations, or to test them. +See Section +.Sx SETS OF RULES +for more information on +.Em sets . +.Pp +Rules can be added with the +.Cm add +command; deleted individually or in groups with the +.Cm delete +command, and globally (except those in set 31) with the +.Cm flush +command; displayed, optionally with the content of the +counters, using the +.Cm show +and +.Cm list +commands. +Finally, counters can be reset with the +.Cm zero +and +.Cm resetlog +commands. +.Pp +.Ss COMMAND OPTIONS +The following general options are available when invoking +.Nm : +.Bl -tag -width indent +.It Fl a +Show counter values when listing rules. +The +.Cm show +command implies this option. +.It Fl b +Only show the action and the comment, not the body of a rule. +Implies +.Fl c . +.It Fl c +When entering or showing rules, print them in compact form, +i.e., omitting the "ip from any to any" string +when this does not carry any additional information. +.It Fl d +When listing, show dynamic rules in addition to static ones. +.It Fl e +When listing and +.Fl d +is specified, also show expired dynamic rules. +.It Fl f +Do not ask for confirmation for commands that can cause problems +if misused, i.e., +.Cm flush . +If there is no tty associated with the process, this is implied. +.It Fl i +When listing a table (see the +.Sx LOOKUP TABLES +section below for more information on lookup tables), format values +as IP addresses. +By default, values are shown as integers. +.It Fl n +Only check syntax of the command strings, without actually passing +them to the kernel. +.It Fl N +Try to resolve addresses and service names in output. +.It Fl q +Be quiet when executing the +.Cm add , +.Cm nat , +.Cm zero , +.Cm resetlog +or +.Cm flush +commands; +(implies +.Fl f ) . +This is useful when updating rulesets by executing multiple +.Nm +commands in a script +(e.g., +.Ql sh\ /etc/rc.firewall ) , +or by processing a file with many +.Nm +rules across a remote login session. +It also stops a table add or delete +from failing if the entry already exists or is not present. +.Pp +The reason why this option may be important is that +for some of these actions, +.Nm +may print a message; if the action results in blocking the +traffic to the remote client, +the remote login session will be closed +and the rest of the ruleset will not be processed. +Access to the console would then be required to recover. +.It Fl S +When listing rules, show the +.Em set +each rule belongs to. +If this flag is not specified, disabled rules will not be +listed. +.It Fl s Op Ar field +When listing pipes, sort according to one of the four +counters (total or current packets or bytes). +.It Fl t +When listing, show last match timestamp converted with ctime(). +.It Fl T +When listing, show last match timestamp as seconds from the epoch. +This form can be more convenient for postprocessing by scripts. +.El +.Ss LIST OF RULES AND PREPROCESSING +To ease configuration, rules can be put into a file which is +processed using +.Nm +as shown in the last synopsis line. +An absolute +.Ar pathname +must be used. +The file will be read line by line and applied as arguments to the +.Nm +utility. +.Pp +Optionally, a preprocessor can be specified using +.Fl p Ar preproc +where +.Ar pathname +is to be piped through. +Useful preprocessors include +.Xr cpp 1 +and +.Xr m4 1 . +If +.Ar preproc +does not start with a slash +.Pq Ql / +as its first character, the usual +.Ev PATH +name search is performed. +Care should be taken with this in environments where not all +file systems are mounted (yet) by the time +.Nm +is being run (e.g.\& when they are mounted over NFS). +Once +.Fl p +has been specified, any additional arguments are passed on to the preprocessor +for interpretation. +This allows for flexible configuration files (like conditionalizing +them on the local hostname) and the use of macros to centralize +frequently required arguments like IP addresses. +.Ss TRAFFIC SHAPER CONFIGURATION +The +.Nm +.Cm pipe , queue +and +.Cm sched +commands are used to configure the traffic shaper and packet scheduler. +See the +.Sx TRAFFIC SHAPER (DUMMYNET) CONFIGURATION +Section below for details. +.Pp +If the world and the kernel get out of sync the +.Nm +ABI may break, preventing you from being able to add any rules. +This can adversely affect the booting process. +You can use +.Nm +.Cm disable +.Cm firewall +to temporarily disable the firewall to regain access to the network, +allowing you to fix the problem. +.Sh PACKET FLOW +A packet is checked against the active ruleset in multiple places +in the protocol stack, under control of several sysctl variables. +These places and variables are shown below, and it is important to +have this picture in mind in order to design a correct ruleset. +.Bd -literal -offset indent + ^ to upper layers V + | | + +----------->-----------+ + ^ V + [ip(6)_input] [ip(6)_output] net.inet(6).ip(6).fw.enable=1 + | | + ^ V + [ether_demux] [ether_output_frame] net.link.ether.ipfw=1 + | | + +-->--[bdg_forward]-->--+ net.link.bridge.ipfw=1 + ^ V + | to devices | +.Ed +.Pp +The number of +times the same packet goes through the firewall can +vary between 0 and 4 depending on packet source and +destination, and system configuration. +.Pp +Note that as packets flow through the stack, headers can be +stripped or added to it, and so they may or may not be available +for inspection. +E.g., incoming packets will include the MAC header when +.Nm +is invoked from +.Cm ether_demux() , +but the same packets will have the MAC header stripped off when +.Nm +is invoked from +.Cm ip_input() +or +.Cm ip6_input() . +.Pp +Also note that each packet is always checked against the complete ruleset, +irrespective of the place where the check occurs, or the source of the packet. +If a rule contains some match patterns or actions which are not valid +for the place of invocation (e.g.\& trying to match a MAC header within +.Cm ip_input +or +.Cm ip6_input ), +the match pattern will not match, but a +.Cm not +operator in front of such patterns +.Em will +cause the pattern to +.Em always +match on those packets. +It is thus the responsibility of +the programmer, if necessary, to write a suitable ruleset to +differentiate among the possible places. +.Cm skipto +rules can be useful here, as an example: +.Bd -literal -offset indent +# packets from ether_demux or bdg_forward +ipfw add 10 skipto 1000 all from any to any layer2 in +# packets from ip_input +ipfw add 10 skipto 2000 all from any to any not layer2 in +# packets from ip_output +ipfw add 10 skipto 3000 all from any to any not layer2 out +# packets from ether_output_frame +ipfw add 10 skipto 4000 all from any to any layer2 out +.Ed +.Pp +(yes, at the moment there is no way to differentiate between +ether_demux and bdg_forward). +.Sh SYNTAX +In general, each keyword or argument must be provided as +a separate command line argument, with no leading or trailing +spaces. +Keywords are case-sensitive, whereas arguments may +or may not be case-sensitive depending on their nature +(e.g.\& uid's are, hostnames are not). +.Pp +Some arguments (e.g., port or address lists) are comma-separated +lists of values. +In this case, spaces after commas ',' are allowed to make +the line more readable. +You can also put the entire +command (including flags) into a single argument. +E.g., the following forms are equivalent: +.Bd -literal -offset indent +ipfw -q add deny src-ip 10.0.0.0/24,127.0.0.1/8 +ipfw -q add deny src-ip 10.0.0.0/24, 127.0.0.1/8 +ipfw "-q add deny src-ip 10.0.0.0/24, 127.0.0.1/8" +.Ed +.Sh RULE FORMAT +The format of firewall rules is the following: +.Bd -ragged -offset indent +.Bk -words +.Op Ar rule_number +.Op Cm set Ar set_number +.Op Cm prob Ar match_probability +.Ar action +.Op Cm log Op Cm logamount Ar number +.Op Cm altq Ar queue +.Oo +.Bro Cm tag | untag +.Brc Ar number +.Oc +.Ar body +.Ek +.Ed +.Pp +where the body of the rule specifies which information is used +for filtering packets, among the following: +.Pp +.Bl -tag -width "Source and dest. addresses and ports" -offset XXX -compact +.It Layer-2 header fields +When available +.It IPv4 and IPv6 Protocol +TCP, UDP, ICMP, etc. +.It Source and dest. addresses and ports +.It Direction +See Section +.Sx PACKET FLOW +.It Transmit and receive interface +By name or address +.It Misc. IP header fields +Version, type of service, datagram length, identification, +fragment flag (non-zero IP offset), +Time To Live +.It IP options +.It IPv6 Extension headers +Fragmentation, Hop-by-Hop options, +Routing Headers, Source routing rthdr0, Mobile IPv6 rthdr2, IPSec options. +.It IPv6 Flow-ID +.It Misc. TCP header fields +TCP flags (SYN, FIN, ACK, RST, etc.), +sequence number, acknowledgment number, +window +.It TCP options +.It ICMP types +for ICMP packets +.It ICMP6 types +for ICMP6 packets +.It User/group ID +When the packet can be associated with a local socket. +.It Divert status +Whether a packet came from a divert socket (e.g., +.Xr natd 8 ) . +.It Fib annotation state +Whether a packet has been tagged for using a specific FIB (routing table) +in future forwarding decisions. +.El +.Pp +Note that some of the above information, e.g.\& source MAC or IP addresses and +TCP/UDP ports, can be easily spoofed, so filtering on those fields +alone might not guarantee the desired results. +.Bl -tag -width indent +.It Ar rule_number +Each rule is associated with a +.Ar rule_number +in the range 1..65535, with the latter reserved for the +.Em default +rule. +Rules are checked sequentially by rule number. +Multiple rules can have the same number, in which case they are +checked (and listed) according to the order in which they have +been added. +If a rule is entered without specifying a number, the kernel will +assign one in such a way that the rule becomes the last one +before the +.Em default +rule. +Automatic rule numbers are assigned by incrementing the last +non-default rule number by the value of the sysctl variable +.Ar net.inet.ip.fw.autoinc_step +which defaults to 100. +If this is not possible (e.g.\& because we would go beyond the +maximum allowed rule number), the number of the last +non-default value is used instead. +.It Cm set Ar set_number +Each rule is associated with a +.Ar set_number +in the range 0..31. +Sets can be individually disabled and enabled, so this parameter +is of fundamental importance for atomic ruleset manipulation. +It can be also used to simplify deletion of groups of rules. +If a rule is entered without specifying a set number, +set 0 will be used. +.br +Set 31 is special in that it cannot be disabled, +and rules in set 31 are not deleted by the +.Nm ipfw flush +command (but you can delete them with the +.Nm ipfw delete set 31 +command). +Set 31 is also used for the +.Em default +rule. +.It Cm prob Ar match_probability +A match is only declared with the specified probability +(floating point number between 0 and 1). +This can be useful for a number of applications such as +random packet drop or +(in conjunction with +.Nm dummynet ) +to simulate the effect of multiple paths leading to out-of-order +packet delivery. +.Pp +Note: this condition is checked before any other condition, including +ones such as keep-state or check-state which might have side effects. +.It Cm log Op Cm logamount Ar number +Packets matching a rule with the +.Cm log +keyword will be made available for logging in two ways: +if the sysctl variable +.Va net.inet.ip.fw.verbose +is set to 0 (default), one can use +.Xr bpf 4 +attached to the +.Li ipfw0 +pseudo interface. +This pseudo interface can be created after a boot +manually by using the following command: +.Bd -literal -offset indent +# ifconfig ipfw0 create +.Ed +.Pp +Or, automatically at boot time by adding the following +line to the +.Xr rc.conf 5 +file: +.Bd -literal -offset indent +firewall_logif="YES" +.Ed +.Pp +There is no overhead if no +.Xr bpf 4 +is attached to the pseudo interface. +.Pp +If +.Va net.inet.ip.fw.verbose +is set to 1, packets will be logged to +.Xr syslogd 8 +with a +.Dv LOG_SECURITY +facility up to a maximum of +.Cm logamount +packets. +If no +.Cm logamount +is specified, the limit is taken from the sysctl variable +.Va net.inet.ip.fw.verbose_limit . +In both cases, a value of 0 means unlimited logging. +.Pp +Once the limit is reached, logging can be re-enabled by +clearing the logging counter or the packet counter for that entry, see the +.Cm resetlog +command. +.Pp +Note: logging is done after all other packet matching conditions +have been successfully verified, and before performing the final +action (accept, deny, etc.) on the packet. +.It Cm tag Ar number +When a packet matches a rule with the +.Cm tag +keyword, the numeric tag for the given +.Ar number +in the range 1..65534 will be attached to the packet. +The tag acts as an internal marker (it is not sent out over +the wire) that can be used to identify these packets later on. +This can be used, for example, to provide trust between interfaces +and to start doing policy-based filtering. +A packet can have multiple tags at the same time. +Tags are "sticky", meaning once a tag is applied to a packet by a +matching rule it exists until explicit removal. +Tags are kept with the packet everywhere within the kernel, but are +lost when packet leaves the kernel, for example, on transmitting +packet out to the network or sending packet to a +.Xr divert 4 +socket. +.Pp +To check for previously applied tags, use the +.Cm tagged +rule option. +To delete previously applied tag, use the +.Cm untag +keyword. +.Pp +Note: since tags are kept with the packet everywhere in kernelspace, +they can be set and unset anywhere in the kernel network subsystem +(using the +.Xr mbuf_tags 9 +facility), not only by means of the +.Xr ipfw 4 +.Cm tag +and +.Cm untag +keywords. +For example, there can be a specialized +.Xr netgraph 4 +node doing traffic analyzing and tagging for later inspecting +in firewall. +.It Cm untag Ar number +When a packet matches a rule with the +.Cm untag +keyword, the tag with the number +.Ar number +is searched among the tags attached to this packet and, +if found, removed from it. +Other tags bound to packet, if present, are left untouched. +.It Cm altq Ar queue +When a packet matches a rule with the +.Cm altq +keyword, the ALTQ identifier for the given +.Ar queue +(see +.Xr altq 4 ) +will be attached. +Note that this ALTQ tag is only meaningful for packets going "out" of IPFW, +and not being rejected or going to divert sockets. +Note that if there is insufficient memory at the time the packet is +processed, it will not be tagged, so it is wise to make your ALTQ +"default" queue policy account for this. +If multiple +.Cm altq +rules match a single packet, only the first one adds the ALTQ classification +tag. +In doing so, traffic may be shaped by using +.Cm count Cm altq Ar queue +rules for classification early in the ruleset, then later applying +the filtering decision. +For example, +.Cm check-state +and +.Cm keep-state +rules may come later and provide the actual filtering decisions in +addition to the fallback ALTQ tag. +.Pp +You must run +.Xr pfctl 8 +to set up the queues before IPFW will be able to look them up by name, +and if the ALTQ disciplines are rearranged, the rules in containing the +queue identifiers in the kernel will likely have gone stale and need +to be reloaded. +Stale queue identifiers will probably result in misclassification. +.Pp +All system ALTQ processing can be turned on or off via +.Nm +.Cm enable Ar altq +and +.Nm +.Cm disable Ar altq . +The usage of +.Va net.inet.ip.fw.one_pass +is irrelevant to ALTQ traffic shaping, as the actual rule action is followed +always after adding an ALTQ tag. +.El +.Ss RULE ACTIONS +A rule can be associated with one of the following actions, which +will be executed when the packet matches the body of the rule. +.Bl -tag -width indent +.It Cm allow | accept | pass | permit +Allow packets that match rule. +The search terminates. +.It Cm check-state +Checks the packet against the dynamic ruleset. +If a match is found, execute the action associated with +the rule which generated this dynamic rule, otherwise +move to the next rule. +.br +.Cm Check-state +rules do not have a body. +If no +.Cm check-state +rule is found, the dynamic ruleset is checked at the first +.Cm keep-state +or +.Cm limit +rule. +.It Cm count +Update counters for all packets that match rule. +The search continues with the next rule. +.It Cm deny | drop +Discard packets that match this rule. +The search terminates. +.It Cm divert Ar port +Divert packets that match this rule to the +.Xr divert 4 +socket bound to port +.Ar port . +The search terminates. +.It Cm fwd | forward Ar ipaddr | tablearg Ns Op , Ns Ar port +Change the next-hop on matching packets to +.Ar ipaddr , +which can be an IP address or a host name. +For IPv4, the next hop can also be supplied by the last table +looked up for the packet by using the +.Cm tablearg +keyword instead of an explicit address. +The search terminates if this rule matches. +.Pp +If +.Ar ipaddr +is a local address, then matching packets will be forwarded to +.Ar port +(or the port number in the packet if one is not specified in the rule) +on the local machine. +.br +If +.Ar ipaddr +is not a local address, then the port number +(if specified) is ignored, and the packet will be +forwarded to the remote address, using the route as found in +the local routing table for that IP. +.br +A +.Ar fwd +rule will not match layer-2 packets (those received +on ether_input, ether_output, or bridged). +.br +The +.Cm fwd +action does not change the contents of the packet at all. +In particular, the destination address remains unmodified, so +packets forwarded to another system will usually be rejected by that system +unless there is a matching rule on that system to capture them. +For packets forwarded locally, +the local address of the socket will be +set to the original destination address of the packet. +This makes the +.Xr netstat 1 +entry look rather weird but is intended for +use with transparent proxy servers. +.It Cm nat Ar nat_nr | tablearg +Pass packet to a +nat instance +(for network address translation, address redirect, etc.): +see the +.Sx NETWORK ADDRESS TRANSLATION (NAT) +Section for further information. +.It Cm pipe Ar pipe_nr +Pass packet to a +.Nm dummynet +.Dq pipe +(for bandwidth limitation, delay, etc.). +See the +.Sx TRAFFIC SHAPER (DUMMYNET) CONFIGURATION +Section for further information. +The search terminates; however, on exit from the pipe and if +the +.Xr sysctl 8 +variable +.Va net.inet.ip.fw.one_pass +is not set, the packet is passed again to the firewall code +starting from the next rule. +.It Cm queue Ar queue_nr +Pass packet to a +.Nm dummynet +.Dq queue +(for bandwidth limitation using WF2Q+). +.It Cm reject +(Deprecated). +Synonym for +.Cm unreach host . +.It Cm reset +Discard packets that match this rule, and if the +packet is a TCP packet, try to send a TCP reset (RST) notice. +The search terminates. +.It Cm reset6 +Discard packets that match this rule, and if the +packet is a TCP packet, try to send a TCP reset (RST) notice. +The search terminates. +.It Cm skipto Ar number | tablearg +Skip all subsequent rules numbered less than +.Ar number . +The search continues with the first rule numbered +.Ar number +or higher. +It is possible to use the +.Cm tablearg +keyword with a skipto for a +.Em computed +skipto. Skipto may work either in O(log(N)) or in O(1) depending +on amount of memory and/or sysctl variables. +See the +.Sx SYSCTL VARIABLES +section for more details. +.It Cm call Ar number | tablearg +The current rule number is saved in the internal stack and +ruleset processing continues with the first rule numbered +.Ar number +or higher. +If later a rule with the +.Cm return +action is encountered, the processing returns to the first rule +with number of this +.Cm call +rule plus one or higher +(the same behaviour as with packets returning from +.Xr divert 4 +socket after a +.Cm divert +action). +This could be used to make somewhat like an assembly language +.Dq subroutine +calls to rules with common checks for different interfaces, etc. +.Pp +Rule with any number could be called, not just forward jumps as with +.Cm skipto . +So, to prevent endless loops in case of mistakes, both +.Cm call +and +.Cm return +actions don't do any jumps and simply go to the next rule if memory +cannot be allocated or stack overflowed/underflowed. +.Pp +Internally stack for rule numbers is implemented using +.Xr mbuf_tags 9 +facility and currently has size of 16 entries. +As mbuf tags are lost when packet leaves the kernel, +.Cm divert +should not be used in subroutines to avoid endless loops +and other undesired effects. +.It Cm return +Takes rule number saved to internal stack by the last +.Cm call +action and returns ruleset processing to the first rule +with number greater than number of corresponding +.Cm call +rule. +See description of the +.Cm call +action for more details. +.Pp +Note that +.Cm return +rules usually end a +.Dq subroutine +and thus are unconditional, but +.Nm +command-line utility currently requires every action except +.Cm check-state +to have body. +While it is sometimes useful to return only on some packets, +usually you want to print just +.Dq return +for readability. +A workaround for this is to use new syntax and +.Fl c +switch: +.Bd -literal -offset indent +# Add a rule without actual body +ipfw add 2999 return via any + +# List rules without "from any to any" part +ipfw -c list +.Ed +.Pp +This cosmetic annoyance may be fixed in future releases. +.It Cm tee Ar port +Send a copy of packets matching this rule to the +.Xr divert 4 +socket bound to port +.Ar port . +The search continues with the next rule. +.It Cm unreach Ar code +Discard packets that match this rule, and try to send an ICMP +unreachable notice with code +.Ar code , +where +.Ar code +is a number from 0 to 255, or one of these aliases: +.Cm net , host , protocol , port , +.Cm needfrag , srcfail , net-unknown , host-unknown , +.Cm isolated , net-prohib , host-prohib , tosnet , +.Cm toshost , filter-prohib , host-precedence +or +.Cm precedence-cutoff . +The search terminates. +.It Cm unreach6 Ar code +Discard packets that match this rule, and try to send an ICMPv6 +unreachable notice with code +.Ar code , +where +.Ar code +is a number from 0, 1, 3 or 4, or one of these aliases: +.Cm no-route, admin-prohib, address +or +.Cm port . +The search terminates. +.It Cm netgraph Ar cookie +Divert packet into netgraph with given +.Ar cookie . +The search terminates. +If packet is later returned from netgraph it is either +accepted or continues with the next rule, depending on +.Va net.inet.ip.fw.one_pass +sysctl variable. +.It Cm ngtee Ar cookie +A copy of packet is diverted into netgraph, original +packet continues with the next rule. +See +.Xr ng_ipfw 4 +for more information on +.Cm netgraph +and +.Cm ngtee +actions. +.It Cm setfib Ar fibnum | tablearg +The packet is tagged so as to use the FIB (routing table) +.Ar fibnum +in any subsequent forwarding decisions. +In the current implementation, this is limited to the values 0 through 15, see +.Xr setfib 2 . +Processing continues at the next rule. +It is possible to use the +.Cm tablearg +keyword with setfib. +If the tablearg value is not within the compiled range of fibs, +the packet's fib is set to 0. +.It Cm setdscp Ar DSCP | number | tablearg +Set specified DiffServ codepoint for an IPv4/IPv6 packet. +Processing continues at the next rule. +Supported values are: +.Pp +.Cm CS0 +.Pq Dv 000000 , +.Cm CS1 +.Pq Dv 001000 , +.Cm CS2 +.Pq Dv 010000 , +.Cm CS3 +.Pq Dv 011000 , +.Cm CS4 +.Pq Dv 100000 , +.Cm CS5 +.Pq Dv 101000 , +.Cm CS6 +.Pq Dv 110000 , +.Cm CS7 +.Pq Dv 111000 , +.Cm AF11 +.Pq Dv 001010 , +.Cm AF12 +.Pq Dv 001100 , +.Cm AF13 +.Pq Dv 001110 , +.Cm AF21 +.Pq Dv 010010 , +.Cm AF22 +.Pq Dv 010100 , +.Cm AF23 +.Pq Dv 010110 , +.Cm AF31 +.Pq Dv 011010 , +.Cm AF32 +.Pq Dv 011100 , +.Cm AF33 +.Pq Dv 011110 , +.Cm AF41 +.Pq Dv 100010 , +.Cm AF42 +.Pq Dv 100100 , +.Cm AF43 +.Pq Dv 100110 , +.Cm EF +.Pq Dv 101110 , +.Cm BE +.Pq Dv 000000 . +Additionally, DSCP value can be specified by number (0..64). +It is also possible to use the +.Cm tablearg +keyword with setdscp. +If the tablearg value is not within the 0..64 range, lower 6 bits of supplied +value are used. +.It Cm reass +Queue and reassemble IP fragments. +If the packet is not fragmented, counters are updated and +processing continues with the next rule. +If the packet is the last logical fragment, the packet is reassembled and, if +.Va net.inet.ip.fw.one_pass +is set to 0, processing continues with the next rule. +Otherwise, the packet is allowed to pass and the search terminates. +If the packet is a fragment in the middle of a logical group of fragments, +it is consumed and +processing stops immediately. +.Pp +Fragment handling can be tuned via +.Va net.inet.ip.maxfragpackets +and +.Va net.inet.ip.maxfragsperpacket +which limit, respectively, the maximum number of processable +fragments (default: 800) and +the maximum number of fragments per packet (default: 16). +.Pp +NOTA BENE: since fragments do not contain port numbers, +they should be avoided with the +.Nm reass +rule. +Alternatively, direction-based (like +.Nm in +/ +.Nm out +) and source-based (like +.Nm via +) match patterns can be used to select fragments. +.Pp +Usually a simple rule like: +.Bd -literal -offset indent +# reassemble incoming fragments +ipfw add reass all from any to any in +.Ed +.Pp +is all you need at the beginning of your ruleset. +.El +.Ss RULE BODY +The body of a rule contains zero or more patterns (such as +specific source and destination addresses or ports, +protocol options, incoming or outgoing interfaces, etc.) +that the packet must match in order to be recognised. +In general, the patterns are connected by (implicit) +.Cm and +operators -- i.e., all must match in order for the +rule to match. +Individual patterns can be prefixed by the +.Cm not +operator to reverse the result of the match, as in +.Pp +.Dl "ipfw add 100 allow ip from not 1.2.3.4 to any" +.Pp +Additionally, sets of alternative match patterns +.Pq Em or-blocks +can be constructed by putting the patterns in +lists enclosed between parentheses ( ) or braces { }, and +using the +.Cm or +operator as follows: +.Pp +.Dl "ipfw add 100 allow ip from { x or not y or z } to any" +.Pp +Only one level of parentheses is allowed. +Beware that most shells have special meanings for parentheses +or braces, so it is advisable to put a backslash \\ in front of them +to prevent such interpretations. +.Pp +The body of a rule must in general include a source and destination +address specifier. +The keyword +.Ar any +can be used in various places to specify that the content of +a required field is irrelevant. +.Pp +The rule body has the following format: +.Bd -ragged -offset indent +.Op Ar proto Cm from Ar src Cm to Ar dst +.Op Ar options +.Ed +.Pp +The first part (proto from src to dst) is for backward +compatibility with earlier versions of +.Fx . +In modern +.Fx +any match pattern (including MAC headers, IP protocols, +addresses and ports) can be specified in the +.Ar options +section. +.Pp +Rule fields have the following meaning: +.Bl -tag -width indent +.It Ar proto : protocol | Cm { Ar protocol Cm or ... } +.It Ar protocol : Oo Cm not Oc Ar protocol-name | protocol-number +An IP protocol specified by number or name +(for a complete list see +.Pa /etc/protocols ) , +or one of the following keywords: +.Bl -tag -width indent +.It Cm ip4 | ipv4 +Matches IPv4 packets. +.It Cm ip6 | ipv6 +Matches IPv6 packets. +.It Cm ip | all +Matches any packet. +.El +.Pp +The +.Cm ipv6 +in +.Cm proto +option will be treated as inner protocol. +And, the +.Cm ipv4 +is not available in +.Cm proto +option. +.Pp +The +.Cm { Ar protocol Cm or ... } +format (an +.Em or-block ) +is provided for convenience only but its use is deprecated. +.It Ar src No and Ar dst : Bro Cm addr | Cm { Ar addr Cm or ... } Brc Op Oo Cm not Oc Ar ports +An address (or a list, see below) +optionally followed by +.Ar ports +specifiers. +.Pp +The second format +.Em ( or-block +with multiple addresses) is provided for convenience only and +its use is discouraged. +.It Ar addr : Oo Cm not Oc Bro +.Cm any | me | me6 | +.Cm table Ns Pq Ar name Ns Op , Ns Ar value +.Ar | addr-list | addr-set +.Brc +.Bl -tag -width indent +.It Cm any +matches any IP address. +.It Cm me +matches any IP address configured on an interface in the system. +.It Cm me6 +matches any IPv6 address configured on an interface in the system. +The address list is evaluated at the time the packet is +analysed. +.It Cm table Ns Pq Ar name Ns Op , Ns Ar value +Matches any IPv4 or IPv6 address for which an entry exists in the lookup table +.Ar number . +If an optional 32-bit unsigned +.Ar value +is also specified, an entry will match only if it has this value. +See the +.Sx LOOKUP TABLES +section below for more information on lookup tables. +.El +.It Ar addr-list : ip-addr Ns Op Ns , Ns Ar addr-list +.It Ar ip-addr : +A host or subnet address specified in one of the following ways: +.Bl -tag -width indent +.It Ar numeric-ip | hostname +Matches a single IPv4 address, specified as dotted-quad or a hostname. +Hostnames are resolved at the time the rule is added to the firewall list. +.It Ar addr Ns / Ns Ar masklen +Matches all addresses with base +.Ar addr +(specified as an IP address, a network number, or a hostname) +and mask width of +.Cm masklen +bits. +As an example, 1.2.3.4/25 or 1.2.3.0/25 will match +all IP numbers from 1.2.3.0 to 1.2.3.127 . +.It Ar addr Ns : Ns Ar mask +Matches all addresses with base +.Ar addr +(specified as an IP address, a network number, or a hostname) +and the mask of +.Ar mask , +specified as a dotted quad. +As an example, 1.2.3.4:255.0.255.0 or 1.0.3.0:255.0.255.0 will match +1.*.3.*. +This form is advised only for non-contiguous +masks. +It is better to resort to the +.Ar addr Ns / Ns Ar masklen +format for contiguous masks, which is more compact and less +error-prone. +.El +.It Ar addr-set : addr Ns Oo Ns / Ns Ar masklen Oc Ns Cm { Ns Ar list Ns Cm } +.It Ar list : Bro Ar num | num-num Brc Ns Op Ns , Ns Ar list +Matches all addresses with base address +.Ar addr +(specified as an IP address, a network number, or a hostname) +and whose last byte is in the list between braces { } . +Note that there must be no spaces between braces and +numbers (spaces after commas are allowed). +Elements of the list can be specified as single entries +or ranges. +The +.Ar masklen +field is used to limit the size of the set of addresses, +and can have any value between 24 and 32. +If not specified, +it will be assumed as 24. +.br +This format is particularly useful to handle sparse address sets +within a single rule. +Because the matching occurs using a +bitmask, it takes constant time and dramatically reduces +the complexity of rulesets. +.br +As an example, an address specified as 1.2.3.4/24{128,35-55,89} +or 1.2.3.0/24{128,35-55,89} +will match the following IP addresses: +.br +1.2.3.128, 1.2.3.35 to 1.2.3.55, 1.2.3.89 . +.It Ar addr6-list : ip6-addr Ns Op Ns , Ns Ar addr6-list +.It Ar ip6-addr : +A host or subnet specified one of the following ways: +.Bl -tag -width indent +.It Ar numeric-ip | hostname +Matches a single IPv6 address as allowed by +.Xr inet_pton 3 +or a hostname. +Hostnames are resolved at the time the rule is added to the firewall +list. +.It Ar addr Ns / Ns Ar masklen +Matches all IPv6 addresses with base +.Ar addr +(specified as allowed by +.Xr inet_pton +or a hostname) +and mask width of +.Cm masklen +bits. +.El +.Pp +No support for sets of IPv6 addresses is provided because IPv6 addresses +are typically random past the initial prefix. +.It Ar ports : Bro Ar port | port Ns \&- Ns Ar port Ns Brc Ns Op , Ns Ar ports +For protocols which support port numbers (such as TCP and UDP), optional +.Cm ports +may be specified as one or more ports or port ranges, separated +by commas but no spaces, and an optional +.Cm not +operator. +The +.Ql \&- +notation specifies a range of ports (including boundaries). +.Pp +Service names (from +.Pa /etc/services ) +may be used instead of numeric port values. +The length of the port list is limited to 30 ports or ranges, +though one can specify larger ranges by using an +.Em or-block +in the +.Cm options +section of the rule. +.Pp +A backslash +.Pq Ql \e +can be used to escape the dash +.Pq Ql - +character in a service name (from a shell, the backslash must be +typed twice to avoid the shell itself interpreting it as an escape +character). +.Pp +.Dl "ipfw add count tcp from any ftp\e\e-data-ftp to any" +.Pp +Fragmented packets which have a non-zero offset (i.e., not the first +fragment) will never match a rule which has one or more port +specifications. +See the +.Cm frag +option for details on matching fragmented packets. +.El +.Ss RULE OPTIONS (MATCH PATTERNS) +Additional match patterns can be used within +rules. +Zero or more of these so-called +.Em options +can be present in a rule, optionally prefixed by the +.Cm not +operand, and possibly grouped into +.Em or-blocks . +.Pp +The following match patterns can be used (listed in alphabetical order): +.Bl -tag -width indent +.It Cm // this is a comment. +Inserts the specified text as a comment in the rule. +Everything following // is considered as a comment and stored in the rule. +You can have comment-only rules, which are listed as having a +.Cm count +action followed by the comment. +.It Cm bridged +Alias for +.Cm layer2 . +.It Cm diverted +Matches only packets generated by a divert socket. +.It Cm diverted-loopback +Matches only packets coming from a divert socket back into the IP stack +input for delivery. +.It Cm diverted-output +Matches only packets going from a divert socket back outward to the IP +stack output for delivery. +.It Cm dst-ip Ar ip-address +Matches IPv4 packets whose destination IP is one of the address(es) +specified as argument. +.It Bro Cm dst-ip6 | dst-ipv6 Brc Ar ip6-address +Matches IPv6 packets whose destination IP is one of the address(es) +specified as argument. +.It Cm dst-port Ar ports +Matches IP packets whose destination port is one of the port(s) +specified as argument. +.It Cm established +Matches TCP packets that have the RST or ACK bits set. +.It Cm ext6hdr Ar header +Matches IPv6 packets containing the extended header given by +.Ar header . +Supported headers are: +.Pp +Fragment, +.Pq Cm frag , +Hop-to-hop options +.Pq Cm hopopt , +any type of Routing Header +.Pq Cm route , +Source routing Routing Header Type 0 +.Pq Cm rthdr0 , +Mobile IPv6 Routing Header Type 2 +.Pq Cm rthdr2 , +Destination options +.Pq Cm dstopt , +IPSec authentication headers +.Pq Cm ah , +and IPsec encapsulated security payload headers +.Pq Cm esp . +.It Cm fib Ar fibnum +Matches a packet that has been tagged to use +the given FIB (routing table) number. +.It Cm flow Ar table Ns Pq Ar name Ns Op , Ns Ar value +Search for the flow entry in lookup table +.Ar name . +If not found, the match fails. +Otherwise, the match succeeds and +.Cm tablearg +is set to the value extracted from the table. +.Pp +This option can be useful to quickly dispatch traffic based on +certain packet fields. +See the +.Sx LOOKUP TABLES +section below for more information on lookup tables. +.It Cm flow-id Ar labels +Matches IPv6 packets containing any of the flow labels given in +.Ar labels . +.Ar labels +is a comma separated list of numeric flow labels. +.It Cm frag +Matches packets that are fragments and not the first +fragment of an IP datagram. +Note that these packets will not have +the next protocol header (e.g.\& TCP, UDP) so options that look into +these headers cannot match. +.It Cm gid Ar group +Matches all TCP or UDP packets sent by or received for a +.Ar group . +A +.Ar group +may be specified by name or number. +.It Cm jail Ar prisonID +Matches all TCP or UDP packets sent by or received for the +jail whos prison ID is +.Ar prisonID . +.It Cm icmptypes Ar types +Matches ICMP packets whose ICMP type is in the list +.Ar types . +The list may be specified as any combination of +individual types (numeric) separated by commas. +.Em Ranges are not allowed . +The supported ICMP types are: +.Pp +echo reply +.Pq Cm 0 , +destination unreachable +.Pq Cm 3 , +source quench +.Pq Cm 4 , +redirect +.Pq Cm 5 , +echo request +.Pq Cm 8 , +router advertisement +.Pq Cm 9 , +router solicitation +.Pq Cm 10 , +time-to-live exceeded +.Pq Cm 11 , +IP header bad +.Pq Cm 12 , +timestamp request +.Pq Cm 13 , +timestamp reply +.Pq Cm 14 , +information request +.Pq Cm 15 , +information reply +.Pq Cm 16 , +address mask request +.Pq Cm 17 +and address mask reply +.Pq Cm 18 . +.It Cm icmp6types Ar types +Matches ICMP6 packets whose ICMP6 type is in the list of +.Ar types . +The list may be specified as any combination of +individual types (numeric) separated by commas. +.Em Ranges are not allowed . +.It Cm in | out +Matches incoming or outgoing packets, respectively. +.Cm in +and +.Cm out +are mutually exclusive (in fact, +.Cm out +is implemented as +.Cm not in Ns No ). +.It Cm ipid Ar id-list +Matches IPv4 packets whose +.Cm ip_id +field has value included in +.Ar id-list , +which is either a single value or a list of values or ranges +specified in the same way as +.Ar ports . +.It Cm iplen Ar len-list +Matches IP packets whose total length, including header and data, is +in the set +.Ar len-list , +which is either a single value or a list of values or ranges +specified in the same way as +.Ar ports . +.It Cm ipoptions Ar spec +Matches packets whose IPv4 header contains the comma separated list of +options specified in +.Ar spec . +The supported IP options are: +.Pp +.Cm ssrr +(strict source route), +.Cm lsrr +(loose source route), +.Cm rr +(record packet route) and +.Cm ts +(timestamp). +The absence of a particular option may be denoted +with a +.Ql \&! . +.It Cm ipprecedence Ar precedence +Matches IPv4 packets whose precedence field is equal to +.Ar precedence . +.It Cm ipsec +Matches packets that have IPSEC history associated with them +(i.e., the packet comes encapsulated in IPSEC, the kernel +has IPSEC support and IPSEC_FILTERTUNNEL option, and can correctly +decapsulate it). +.Pp +Note that specifying +.Cm ipsec +is different from specifying +.Cm proto Ar ipsec +as the latter will only look at the specific IP protocol field, +irrespective of IPSEC kernel support and the validity of the IPSEC data. +.Pp +Further note that this flag is silently ignored in kernels without +IPSEC support. +It does not affect rule processing when given and the +rules are handled as if with no +.Cm ipsec +flag. +.It Cm iptos Ar spec +Matches IPv4 packets whose +.Cm tos +field contains the comma separated list of +service types specified in +.Ar spec . +The supported IP types of service are: +.Pp +.Cm lowdelay +.Pq Dv IPTOS_LOWDELAY , +.Cm throughput +.Pq Dv IPTOS_THROUGHPUT , +.Cm reliability +.Pq Dv IPTOS_RELIABILITY , +.Cm mincost +.Pq Dv IPTOS_MINCOST , +.Cm congestion +.Pq Dv IPTOS_ECN_CE . +The absence of a particular type may be denoted +with a +.Ql \&! . +.It Cm dscp spec Ns Op , Ns Ar spec +Matches IPv4/IPv6 packets whose +.Cm DS +field value is contained in +.Ar spec +mask. +Multiple values can be specified via +the comma separated list. +Value can be one of keywords used in +.Cm setdscp +action or exact number. +.It Cm ipttl Ar ttl-list +Matches IPv4 packets whose time to live is included in +.Ar ttl-list , +which is either a single value or a list of values or ranges +specified in the same way as +.Ar ports . +.It Cm ipversion Ar ver +Matches IP packets whose IP version field is +.Ar ver . +.It Cm keep-state +Upon a match, the firewall will create a dynamic rule, whose +default behaviour is to match bidirectional traffic between +source and destination IP/port using the same protocol. +The rule has a limited lifetime (controlled by a set of +.Xr sysctl 8 +variables), and the lifetime is refreshed every time a matching +packet is found. +.It Cm layer2 +Matches only layer2 packets, i.e., those passed to +.Nm +from ether_demux() and ether_output_frame(). +.It Cm limit Bro Cm src-addr | src-port | dst-addr | dst-port Brc Ar N +The firewall will only allow +.Ar N +connections with the same +set of parameters as specified in the rule. +One or more +of source and destination addresses and ports can be +specified. +Currently, +only IPv4 flows are supported. +.It Cm lookup Bro Cm dst-ip | dst-port | src-ip | src-port | uid | jail Brc Ar name +Search an entry in lookup table +.Ar name +that matches the field specified as argument. +If not found, the match fails. +Otherwise, the match succeeds and +.Cm tablearg +is set to the value extracted from the table. +.Pp +This option can be useful to quickly dispatch traffic based on +certain packet fields. +See the +.Sx LOOKUP TABLES +section below for more information on lookup tables. +.It Cm { MAC | mac } Ar dst-mac src-mac +Match packets with a given +.Ar dst-mac +and +.Ar src-mac +addresses, specified as the +.Cm any +keyword (matching any MAC address), or six groups of hex digits +separated by colons, +and optionally followed by a mask indicating the significant bits. +The mask may be specified using either of the following methods: +.Bl -enum -width indent +.It +A slash +.Pq / +followed by the number of significant bits. +For example, an address with 33 significant bits could be specified as: +.Pp +.Dl "MAC 10:20:30:40:50:60/33 any" +.It +An ampersand +.Pq & +followed by a bitmask specified as six groups of hex digits separated +by colons. +For example, an address in which the last 16 bits are significant could +be specified as: +.Pp +.Dl "MAC 10:20:30:40:50:60&00:00:00:00:ff:ff any" +.Pp +Note that the ampersand character has a special meaning in many shells +and should generally be escaped. +.El +Note that the order of MAC addresses (destination first, +source second) is +the same as on the wire, but the opposite of the one used for +IP addresses. +.It Cm mac-type Ar mac-type +Matches packets whose Ethernet Type field +corresponds to one of those specified as argument. +.Ar mac-type +is specified in the same way as +.Cm port numbers +(i.e., one or more comma-separated single values or ranges). +You can use symbolic names for known values such as +.Em vlan , ipv4, ipv6 . +Values can be entered as decimal or hexadecimal (if prefixed by 0x), +and they are always printed as hexadecimal (unless the +.Cm -N +option is used, in which case symbolic resolution will be attempted). +.It Cm proto Ar protocol +Matches packets with the corresponding IP protocol. +.It Cm recv | xmit | via Brq Ar ifX | Ar if Ns Cm * | Ar table Ns Po Ar name Ns Oo , Ns Ar value Oc Pc | Ar ipno | Ar any +Matches packets received, transmitted or going through, +respectively, the interface specified by exact name +.Po Ar ifX Pc , +by device name +.Po Ar if* Pc , +by IP address, or through some interface. +Table +.Ar name +may be used to match interface by its kernel ifindex. +See the +.Sx LOOKUP TABLES +section below for more information on lookup tables. +.Pp +The +.Cm via +keyword causes the interface to always be checked. +If +.Cm recv +or +.Cm xmit +is used instead of +.Cm via , +then only the receive or transmit interface (respectively) +is checked. +By specifying both, it is possible to match packets based on +both receive and transmit interface, e.g.: +.Pp +.Dl "ipfw add deny ip from any to any out recv ed0 xmit ed1" +.Pp +The +.Cm recv +interface can be tested on either incoming or outgoing packets, +while the +.Cm xmit +interface can only be tested on outgoing packets. +So +.Cm out +is required (and +.Cm in +is invalid) whenever +.Cm xmit +is used. +.Pp +A packet might not have a receive or transmit interface: packets +originating from the local host have no receive interface, +while packets destined for the local host have no transmit +interface. +.It Cm setup +Matches TCP packets that have the SYN bit set but no ACK bit. +This is the short form of +.Dq Li tcpflags\ syn,!ack . +.It Cm sockarg +Matches packets that are associated to a local socket and +for which the SO_USER_COOKIE socket option has been set +to a non-zero value. +As a side effect, the value of the +option is made available as +.Cm tablearg +value, which in turn can be used as +.Cm skipto +or +.Cm pipe +number. +.It Cm src-ip Ar ip-address +Matches IPv4 packets whose source IP is one of the address(es) +specified as an argument. +.It Cm src-ip6 Ar ip6-address +Matches IPv6 packets whose source IP is one of the address(es) +specified as an argument. +.It Cm src-port Ar ports +Matches IP packets whose source port is one of the port(s) +specified as argument. +.It Cm tagged Ar tag-list +Matches packets whose tags are included in +.Ar tag-list , +which is either a single value or a list of values or ranges +specified in the same way as +.Ar ports . +Tags can be applied to the packet using +.Cm tag +rule action parameter (see it's description for details on tags). +.It Cm tcpack Ar ack +TCP packets only. +Match if the TCP header acknowledgment number field is set to +.Ar ack . +.It Cm tcpdatalen Ar tcpdatalen-list +Matches TCP packets whose length of TCP data is +.Ar tcpdatalen-list , +which is either a single value or a list of values or ranges +specified in the same way as +.Ar ports . +.It Cm tcpflags Ar spec +TCP packets only. +Match if the TCP header contains the comma separated list of +flags specified in +.Ar spec . +The supported TCP flags are: +.Pp +.Cm fin , +.Cm syn , +.Cm rst , +.Cm psh , +.Cm ack +and +.Cm urg . +The absence of a particular flag may be denoted +with a +.Ql \&! . +A rule which contains a +.Cm tcpflags +specification can never match a fragmented packet which has +a non-zero offset. +See the +.Cm frag +option for details on matching fragmented packets. +.It Cm tcpseq Ar seq +TCP packets only. +Match if the TCP header sequence number field is set to +.Ar seq . +.It Cm tcpwin Ar tcpwin-list +Matches TCP packets whose header window field is set to +.Ar tcpwin-list , +which is either a single value or a list of values or ranges +specified in the same way as +.Ar ports . +.It Cm tcpoptions Ar spec +TCP packets only. +Match if the TCP header contains the comma separated list of +options specified in +.Ar spec . +The supported TCP options are: +.Pp +.Cm mss +(maximum segment size), +.Cm window +(tcp window advertisement), +.Cm sack +(selective ack), +.Cm ts +(rfc1323 timestamp) and +.Cm cc +(rfc1644 t/tcp connection count). +The absence of a particular option may be denoted +with a +.Ql \&! . +.It Cm uid Ar user +Match all TCP or UDP packets sent by or received for a +.Ar user . +A +.Ar user +may be matched by name or identification number. +.It Cm verrevpath +For incoming packets, +a routing table lookup is done on the packet's source address. +If the interface on which the packet entered the system matches the +outgoing interface for the route, +the packet matches. +If the interfaces do not match up, +the packet does not match. +All outgoing packets or packets with no incoming interface match. +.Pp +The name and functionality of the option is intentionally similar to +the Cisco IOS command: +.Pp +.Dl ip verify unicast reverse-path +.Pp +This option can be used to make anti-spoofing rules to reject all +packets with source addresses not from this interface. +See also the option +.Cm antispoof . +.It Cm versrcreach +For incoming packets, +a routing table lookup is done on the packet's source address. +If a route to the source address exists, but not the default route +or a blackhole/reject route, the packet matches. +Otherwise, the packet does not match. +All outgoing packets match. +.Pp +The name and functionality of the option is intentionally similar to +the Cisco IOS command: +.Pp +.Dl ip verify unicast source reachable-via any +.Pp +This option can be used to make anti-spoofing rules to reject all +packets whose source address is unreachable. +.It Cm antispoof +For incoming packets, the packet's source address is checked if it +belongs to a directly connected network. +If the network is directly connected, then the interface the packet +came on in is compared to the interface the network is connected to. +When incoming interface and directly connected interface are not the +same, the packet does not match. +Otherwise, the packet does match. +All outgoing packets match. +.Pp +This option can be used to make anti-spoofing rules to reject all +packets that pretend to be from a directly connected network but do +not come in through that interface. +This option is similar to but more restricted than +.Cm verrevpath +because it engages only on packets with source addresses of directly +connected networks instead of all source addresses. +.El +.Sh LOOKUP TABLES +Lookup tables are useful to handle large sparse sets of +addresses or other search keys (e.g., ports, jail IDs, interface names). +In the rest of this section we will use the term ``key''. +Table name needs to match the following spec: +.Ar table-name . +Tables with the same name can be created in different +.Ar sets . +However, rule links to the tables in +.Ar set 0 +by default. +This behavior can be controlled by +.Va net.inet.ip.fw.tables_sets +variable. +See the +.Sx SETS OF RULES +section for more information. +There may be up to 65535 different lookup tables. +.Pp +The following table types are supported: +.Bl -tag -width indent +.It Ar table-type : Ar addr | iface | number | flow +.It Ar table-key : Ar addr Ns Oo / Ns Ar masklen Oc | iface-name | number | flow-spec +.It Ar flow-spec : Ar flow-field Ns Op , Ns Ar flow-spec +.It Ar flow-field : src-ip | proto | src-port | dst-ip | dst-port +.It Cm addr +matches IPv4 or IPv6 address. +Each entry is represented by an +.Ar addr Ns Op / Ns Ar masklen +and will match all addresses with base +.Ar addr +(specified as an IPv4/IPv6 address, or a hostname) and mask width of +.Ar masklen +bits. +If +.Ar masklen +is not specified, it defaults to 32 for IPv4 and 128 for IPv6. +When looking up an IP address in a table, the most specific +entry will match. +.It Cm iface +matches interface names. +Each entry is represented by string treated as interface name. +Wildcards are not supported. +.It Cm number +maches protocol ports, uids/gids or jail IDs. +Each entry is represented by 32-bit unsigned integer. +Ranges are not supported. +.It Cm flow +Matches packet fields specified by +.Ar flow +type suboptions with table entries. +.El +.Pp +Tables require explicit creation via +.Cm create +before use. +.Pp +The following creation options are supported: +.Bl -tag -width indent +.It Ar create-options : Ar create-option | create-options +.It Ar create-option : Cm type Ar table-type | Cm valtype Ar value-mask | Cm algo Ar algo-desc | +.Cm limit Ar number | Cm locked +.It Cm type +Table key type. +.It Cm valtype +Table value mask. +.It Cm algo +Table algorithm to use (see below). +.It Cm limit +Maximum number of items that may be inserted into table. +.It Cm locked +Restrict any table modifications. +.El +.Pp +Some of these options may be modified later via +.Cm modify +keyword. +The following options can be changed: +.Bl -tag -width indent +.It Ar modify-options : Ar modify-option | modify-options +.It Ar modify-option : Cm limit Ar number +.It Cm limit +Alter maximum number of items that may be inserted into table. +.El +.Pp +Additionally, table can be locked or unlocked using +.Cm lock +or +.Cm unlock +commands. +.Pp +Tables of the same +.Ar type +can be swapped with each other using +.Cm swap Ar name +command. +Swap may fail if tables limits are set and data exchange +would result in limits hit. +Operation is performed atomically. +.Pp +One or more entries can be added to a table at once using +.Cm add +command. +Addition of all items are performed atomically. +By default, error in addition of one entry does not influence +addition of other entries. However, non-zero error code is returned +in that case. +Special +.Cm atomic +keyword may be specified before +.Cm add +to indicate all-or-none add request. +.Pp +One or more entries can be removed from a table at once using +.Cm delete +command. +By default, error in removal of one entry does not influence +removing of other entries. However, non-zero error code is returned +in that case. +.Pp +It may be possible to check what entry will be found on particular +.Ar table-key +using +.Cm lookup +.Ar table-key +command. +This functionality is optional and may be unsupported in some algorithms. +.Pp +The following operations can be performed on +.Ar one +or +.Cm all +tables: +.Bl -tag -width indent +.It Cm list +List all entries. +.It Cm flush +Removes all entries. +.It Cm info +Shows generic table information. +.It Cm detail +Shows generic table information and algo-specific data. +.El +.Pp +The following lookup algorithms are supported: +.Bl -tag -width indent +.It Ar algo-desc : algo-name | "algo-name algo-data" +.It Ar algo-name: Ar addr:radix | addr:hash | iface:array | number:array | flow:hash +.It Cm addr:radix +Separate Radix trees for IPv4 and IPv6, the same way as the routing table (see +.Xr route 4 ) . +Default choice for +.Ar addr +type. +.It Cm addr:hash +Separate auto-growing hashes for IPv4 and IPv6. +Accepts entries with the same mask length specified initially via +.Cm "addr:hash masks=/v4,/v6" +algorithm creation options. +Assume /32 and /128 masks by default. +Search removes host bits (according to mask) from supplied address and checks +resulting key in appropriate hash. +Mostly optimized for /64 and byte-ranged IPv6 masks. +.It Cm iface:array +Array storing sorted indexes for entries which are presented in the system. +Optimized for very fast lookup. +.It Cm number:array +Array storing sorted u32 numbers. +.It Cm flow:hash +Auto-growing hash storing flow entries. +Search calculates hash on required packet fields and searches for matching +entries in selected bucket. +.El +.Pp +The +.Cm tablearg +feature provides the ability to use a value, looked up in the table, as +the argument for a rule action, action parameter or rule option. +This can significantly reduce number of rules in some configurations. +If two tables are used in a rule, the result of the second (destination) +is used. +.Pp +Each record may hold one or more values according to +.Ar value-mask . +This mask is set on table creation via +.Cm valtype +option. +The following value types are supported: +.Bl -tag -width indent +.It Ar value-mask : Ar value-type Ns Op , Ns Ar value-mask +.It Ar value-type : Ar skipto | pipe | fib | nat | dscp | tag | divert | +.Ar netgraph | limit | ipv4 +.It Cm skipto +rule number to jump to. +.It Cm pipe +Pipe number to use. +.It Cm fib +fib number to match/set. +.It Cm nat +nat number to jump to. +.It Cm dscp +dscp value to match/set. +.It Cm tag +tag number to match/set. +.It Cm divert +port number to divert traffic to. +.It Cm netgraph +hook number to move packet to. +.It Cm limit +maximum number of connections. +.It Cm ipv4 +IPv4 nexthop to fwd packets to. +.It Cm ipv6 +IPv6 nexthop to fwd packets to. +.El +.Pp +The +.Cm tablearg +argument can be used with the following actions: +.Cm nat, pipe , queue, divert, tee, netgraph, ngtee, fwd, skipto, setfib, +action parameters: +.Cm tag, untag, +rule options: +.Cm limit, tagged. +.Pp +When used with the +.Cm skipto +action, the user should be aware that the code will walk the ruleset +up to a rule equal to, or past, the given number. +.Pp +See the +.Sx EXAMPLES +Section for example usage of tables and the tablearg keyword. +.Sh SETS OF RULES +Each rule or table belongs to one of 32 different +.Em sets +, numbered 0 to 31. +Set 31 is reserved for the default rule. +.Pp +By default, rules or tables are put in set 0, unless you use the +.Cm set N +attribute when adding a new rule or table. +Sets can be individually and atomically enabled or disabled, +so this mechanism permits an easy way to store multiple configurations +of the firewall and quickly (and atomically) switch between them. +.Pp +By default, tables from set 0 are referenced when adding rule with +table opcodes regardless of rule set. +This behavior can be changed by setting +.Va net.inet.ip.fw.tables_set +variable to 1. +Rule's set will then be used for table references. +.Pp +The command to enable/disable sets is +.Bd -ragged -offset indent +.Nm +.Cm set Oo Cm disable Ar number ... Oc Op Cm enable Ar number ... +.Ed +.Pp +where multiple +.Cm enable +or +.Cm disable +sections can be specified. +Command execution is atomic on all the sets specified in the command. +By default, all sets are enabled. +.Pp +When you disable a set, its rules behave as if they do not exist +in the firewall configuration, with only one exception: +.Bd -ragged -offset indent +dynamic rules created from a rule before it had been disabled +will still be active until they expire. +In order to delete +dynamic rules you have to explicitly delete the parent rule +which generated them. +.Ed +.Pp +The set number of rules can be changed with the command +.Bd -ragged -offset indent +.Nm +.Cm set move +.Brq Cm rule Ar rule-number | old-set +.Cm to Ar new-set +.Ed +.Pp +Also, you can atomically swap two rulesets with the command +.Bd -ragged -offset indent +.Nm +.Cm set swap Ar first-set second-set +.Ed +.Pp +See the +.Sx EXAMPLES +Section on some possible uses of sets of rules. +.Sh STATEFUL FIREWALL +Stateful operation is a way for the firewall to dynamically +create rules for specific flows when packets that +match a given pattern are detected. +Support for stateful +operation comes through the +.Cm check-state , keep-state +and +.Cm limit +options of +.Nm rules . +.Pp +Dynamic rules are created when a packet matches a +.Cm keep-state +or +.Cm limit +rule, causing the creation of a +.Em dynamic +rule which will match all and only packets with +a given +.Em protocol +between a +.Em src-ip/src-port dst-ip/dst-port +pair of addresses +.Em ( src +and +.Em dst +are used here only to denote the initial match addresses, but they +are completely equivalent afterwards). +Dynamic rules will be checked at the first +.Cm check-state, keep-state +or +.Cm limit +occurrence, and the action performed upon a match will be the same +as in the parent rule. +.Pp +Note that no additional attributes other than protocol and IP addresses +and ports are checked on dynamic rules. +.Pp +The typical use of dynamic rules is to keep a closed firewall configuration, +but let the first TCP SYN packet from the inside network install a +dynamic rule for the flow so that packets belonging to that session +will be allowed through the firewall: +.Pp +.Dl "ipfw add check-state" +.Dl "ipfw add allow tcp from my-subnet to any setup keep-state" +.Dl "ipfw add deny tcp from any to any" +.Pp +A similar approach can be used for UDP, where an UDP packet coming +from the inside will install a dynamic rule to let the response through +the firewall: +.Pp +.Dl "ipfw add check-state" +.Dl "ipfw add allow udp from my-subnet to any keep-state" +.Dl "ipfw add deny udp from any to any" +.Pp +Dynamic rules expire after some time, which depends on the status +of the flow and the setting of some +.Cm sysctl +variables. +See Section +.Sx SYSCTL VARIABLES +for more details. +For TCP sessions, dynamic rules can be instructed to periodically +send keepalive packets to refresh the state of the rule when it is +about to expire. +.Pp +See Section +.Sx EXAMPLES +for more examples on how to use dynamic rules. +.Sh TRAFFIC SHAPER (DUMMYNET) CONFIGURATION +.Nm +is also the user interface for the +.Nm dummynet +traffic shaper, packet scheduler and network emulator, a subsystem that +can artificially queue, delay or drop packets +emulating the behaviour of certain network links +or queueing systems. +.Pp +.Nm dummynet +operates by first using the firewall to select packets +using any match pattern that can be used in +.Nm +rules. +Matching packets are then passed to either of two +different objects, which implement the traffic regulation: +.Bl -hang -offset XXXX +.It Em pipe +A +.Em pipe +emulates a +.Em link +with given bandwidth and propagation delay, +driven by a FIFO scheduler and a single queue with programmable +queue size and packet loss rate. +Packets are appended to the queue as they come out from +.Nm ipfw , +and then transferred in FIFO order to the link at the desired rate. +.It Em queue +A +.Em queue +is an abstraction used to implement packet scheduling +using one of several packet scheduling algorithms. +Packets sent to a +.Em queue +are first grouped into flows according to a mask on the 5-tuple. +Flows are then passed to the scheduler associated to the +.Em queue , +and each flow uses scheduling parameters (weight and others) +as configured in the +.Em queue +itself. +A scheduler in turn is connected to an emulated link, +and arbitrates the link's bandwidth among backlogged flows according to +weights and to the features of the scheduling algorithm in use. +.El +.Pp +In practice, +.Em pipes +can be used to set hard limits to the bandwidth that a flow can use, whereas +.Em queues +can be used to determine how different flows share the available bandwidth. +.Pp +A graphical representation of the binding of queues, +flows, schedulers and links is below. +.Bd -literal -offset indent + (flow_mask|sched_mask) sched_mask + +---------+ weight Wx +-------------+ + | |->-[flow]-->--| |-+ + -->--| QUEUE x | ... | | | + | |->-[flow]-->--| SCHEDuler N | | + +---------+ | | | + ... | +--[LINK N]-->-- + +---------+ weight Wy | | +--[LINK N]-->-- + | |->-[flow]-->--| | | + -->--| QUEUE y | ... | | | + | |->-[flow]-->--| | | + +---------+ +-------------+ | + +-------------+ +.Ed +It is important to understand the role of the SCHED_MASK +and FLOW_MASK, which are configured through the commands +.Dl "ipfw sched N config mask SCHED_MASK ..." +and +.Dl "ipfw queue X config mask FLOW_MASK ..." . +.Pp +The SCHED_MASK is used to assign flows to one or more +scheduler instances, one for each +value of the packet's 5-tuple after applying SCHED_MASK. +As an example, using ``src-ip 0xffffff00'' creates one instance +for each /24 destination subnet. +.Pp +The FLOW_MASK, together with the SCHED_MASK, is used to split +packets into flows. +As an example, using +``src-ip 0x000000ff'' +together with the previous SCHED_MASK makes a flow for +each individual source address. +In turn, flows for each /24 +subnet will be sent to the same scheduler instance. +.Pp +The above diagram holds even for the +.Em pipe +case, with the only restriction that a +.Em pipe +only supports a SCHED_MASK, and forces the use of a FIFO +scheduler (these are for backward compatibility reasons; +in fact, internally, a +.Nm dummynet's +pipe is implemented exactly as above). +.Pp +There are two modes of +.Nm dummynet +operation: +.Dq normal +and +.Dq fast . +The +.Dq normal +mode tries to emulate a real link: the +.Nm dummynet +scheduler ensures that the packet will not leave the pipe faster than it +would on the real link with a given bandwidth. +The +.Dq fast +mode allows certain packets to bypass the +.Nm dummynet +scheduler (if packet flow does not exceed pipe's bandwidth). +This is the reason why the +.Dq fast +mode requires less CPU cycles per packet (on average) and packet latency +can be significantly lower in comparison to a real link with the same +bandwidth. +The default mode is +.Dq normal . +The +.Dq fast +mode can be enabled by setting the +.Va net.inet.ip.dummynet.io_fast +.Xr sysctl 8 +variable to a non-zero value. +.Pp +.Ss PIPE, QUEUE AND SCHEDULER CONFIGURATION +The +.Em pipe , +.Em queue +and +.Em scheduler +configuration commands are the following: +.Bd -ragged -offset indent +.Cm pipe Ar number Cm config Ar pipe-configuration +.Pp +.Cm queue Ar number Cm config Ar queue-configuration +.Pp +.Cm sched Ar number Cm config Ar sched-configuration +.Ed +.Pp +The following parameters can be configured for a pipe: +.Pp +.Bl -tag -width indent -compact +.It Cm bw Ar bandwidth | device +Bandwidth, measured in +.Sm off +.Op Cm K | M +.Brq Cm bit/s | Byte/s . +.Sm on +.Pp +A value of 0 (default) means unlimited bandwidth. +The unit must immediately follow the number, as in +.Pp +.Dl "ipfw pipe 1 config bw 300Kbit/s" +.Pp +If a device name is specified instead of a numeric value, as in +.Pp +.Dl "ipfw pipe 1 config bw tun0" +.Pp +then the transmit clock is supplied by the specified device. +At the moment only the +.Xr tun 4 +device supports this +functionality, for use in conjunction with +.Xr ppp 8 . +.Pp +.It Cm delay Ar ms-delay +Propagation delay, measured in milliseconds. +The value is rounded to the next multiple of the clock tick +(typically 10ms, but it is a good practice to run kernels +with +.Dq "options HZ=1000" +to reduce +the granularity to 1ms or less). +The default value is 0, meaning no delay. +.Pp +.It Cm burst Ar size +If the data to be sent exceeds the pipe's bandwidth limit +(and the pipe was previously idle), up to +.Ar size +bytes of data are allowed to bypass the +.Nm dummynet +scheduler, and will be sent as fast as the physical link allows. +Any additional data will be transmitted at the rate specified +by the +.Nm pipe +bandwidth. +The burst size depends on how long the pipe has been idle; +the effective burst size is calculated as follows: +MAX( +.Ar size +, +.Nm bw +* pipe_idle_time). +.Pp +.It Cm profile Ar filename +A file specifying the additional overhead incurred in the transmission +of a packet on the link. +.Pp +Some link types introduce extra delays in the transmission +of a packet, e.g., because of MAC level framing, contention on +the use of the channel, MAC level retransmissions and so on. +From our point of view, the channel is effectively unavailable +for this extra time, which is constant or variable depending +on the link type. +Additionally, packets may be dropped after this +time (e.g., on a wireless link after too many retransmissions). +We can model the additional delay with an empirical curve +that represents its distribution. +.Bd -literal -offset indent + cumulative probability + 1.0 ^ + | + L +-- loss-level x + | ****** + | * + | ***** + | * + | ** + | * + +-------*-------------------> + delay +.Ed +The empirical curve may have both vertical and horizontal lines. +Vertical lines represent constant delay for a range of +probabilities. +Horizontal lines correspond to a discontinuity in the delay +distribution: the pipe will use the largest delay for a +given probability. +.Pp +The file format is the following, with whitespace acting as +a separator and '#' indicating the beginning a comment: +.Bl -tag -width indent +.It Cm name Ar identifier +optional name (listed by "ipfw pipe show") +to identify the delay distribution; +.It Cm bw Ar value +the bandwidth used for the pipe. +If not specified here, it must be present +explicitly as a configuration parameter for the pipe; +.It Cm loss-level Ar L +the probability above which packets are lost. +(0.0 <= L <= 1.0, default 1.0 i.e., no loss); +.It Cm samples Ar N +the number of samples used in the internal +representation of the curve (2..1024; default 100); +.It Cm "delay prob" | "prob delay" +One of these two lines is mandatory and defines +the format of the following lines with data points. +.It Ar XXX Ar YYY +2 or more lines representing points in the curve, +with either delay or probability first, according +to the chosen format. +The unit for delay is milliseconds. +Data points do not need to be sorted. +Also, the number of actual lines can be different +from the value of the "samples" parameter: +.Nm +utility will sort and interpolate +the curve as needed. +.El +.Pp +Example of a profile file: +.Bd -literal -offset indent +name bla_bla_bla +samples 100 +loss-level 0.86 +prob delay +0 200 # minimum overhead is 200ms +0.5 200 +0.5 300 +0.8 1000 +0.9 1300 +1 1300 +#configuration file end +.Ed +.El +.Pp +The following parameters can be configured for a queue: +.Pp +.Bl -tag -width indent -compact +.It Cm pipe Ar pipe_nr +Connects a queue to the specified pipe. +Multiple queues (with the same or different weights) can be connected to +the same pipe, which specifies the aggregate rate for the set of queues. +.Pp +.It Cm weight Ar weight +Specifies the weight to be used for flows matching this queue. +The weight must be in the range 1..100, and defaults to 1. +.El +.Pp +The following case-insensitive parameters can be configured for a +scheduler: +.Pp +.Bl -tag -width indent -compact +.It Cm type Ar {fifo | wf2q+ | rr | qfq} +specifies the scheduling algorithm to use. +.Bl -tag -width indent -compact +.It Cm fifo +is just a FIFO scheduler (which means that all packets +are stored in the same queue as they arrive to the scheduler). +FIFO has O(1) per-packet time complexity, with very low +constants (estimate 60-80ns on a 2GHz desktop machine) +but gives no service guarantees. +.It Cm wf2q+ +implements the WF2Q+ algorithm, which is a Weighted Fair Queueing +algorithm which permits flows to share bandwidth according to +their weights. +Note that weights are not priorities; even a flow +with a minuscule weight will never starve. +WF2Q+ has O(log N) per-packet processing cost, where N is the number +of flows, and is the default algorithm used by previous versions +dummynet's queues. +.It Cm rr +implements the Deficit Round Robin algorithm, which has O(1) processing +costs (roughly, 100-150ns per packet) +and permits bandwidth allocation according to weights, but +with poor service guarantees. +.It Cm qfq +implements the QFQ algorithm, which is a very fast variant of +WF2Q+, with similar service guarantees and O(1) processing +costs (roughly, 200-250ns per packet). +.El +.El +.Pp +In addition to the type, all parameters allowed for a pipe can also +be specified for a scheduler. +.Pp +Finally, the following parameters can be configured for both +pipes and queues: +.Pp +.Bl -tag -width XXXX -compact +.It Cm buckets Ar hash-table-size +Specifies the size of the hash table used for storing the +various queues. +Default value is 64 controlled by the +.Xr sysctl 8 +variable +.Va net.inet.ip.dummynet.hash_size , +allowed range is 16 to 65536. +.Pp +.It Cm mask Ar mask-specifier +Packets sent to a given pipe or queue by an +.Nm +rule can be further classified into multiple flows, each of which is then +sent to a different +.Em dynamic +pipe or queue. +A flow identifier is constructed by masking the IP addresses, +ports and protocol types as specified with the +.Cm mask +options in the configuration of the pipe or queue. +For each different flow identifier, a new pipe or queue is created +with the same parameters as the original object, and matching packets +are sent to it. +.Pp +Thus, when +.Em dynamic pipes +are used, each flow will get the same bandwidth as defined by the pipe, +whereas when +.Em dynamic queues +are used, each flow will share the parent's pipe bandwidth evenly +with other flows generated by the same queue (note that other queues +with different weights might be connected to the same pipe). +.br +Available mask specifiers are a combination of one or more of the following: +.Pp +.Cm dst-ip Ar mask , +.Cm dst-ip6 Ar mask , +.Cm src-ip Ar mask , +.Cm src-ip6 Ar mask , +.Cm dst-port Ar mask , +.Cm src-port Ar mask , +.Cm flow-id Ar mask , +.Cm proto Ar mask +or +.Cm all , +.Pp +where the latter means all bits in all fields are significant. +.Pp +.It Cm noerror +When a packet is dropped by a +.Nm dummynet +queue or pipe, the error +is normally reported to the caller routine in the kernel, in the +same way as it happens when a device queue fills up. +Setting this +option reports the packet as successfully delivered, which can be +needed for some experimental setups where you want to simulate +loss or congestion at a remote router. +.Pp +.It Cm plr Ar packet-loss-rate +Packet loss rate. +Argument +.Ar packet-loss-rate +is a floating-point number between 0 and 1, with 0 meaning no +loss, 1 meaning 100% loss. +The loss rate is internally represented on 31 bits. +.Pp +.It Cm queue Brq Ar slots | size Ns Cm Kbytes +Queue size, in +.Ar slots +or +.Cm KBytes . +Default value is 50 slots, which +is the typical queue size for Ethernet devices. +Note that for slow speed links you should keep the queue +size short or your traffic might be affected by a significant +queueing delay. +E.g., 50 max-sized ethernet packets (1500 bytes) mean 600Kbit +or 20s of queue on a 30Kbit/s pipe. +Even worse effects can result if you get packets from an +interface with a much larger MTU, e.g.\& the loopback interface +with its 16KB packets. +The +.Xr sysctl 8 +variables +.Em net.inet.ip.dummynet.pipe_byte_limit +and +.Em net.inet.ip.dummynet.pipe_slot_limit +control the maximum lengths that can be specified. +.Pp +.It Cm red | gred Ar w_q Ns / Ns Ar min_th Ns / Ns Ar max_th Ns / Ns Ar max_p +[ecn] +Make use of the RED (Random Early Detection) queue management algorithm. +.Ar w_q +and +.Ar max_p +are floating +point numbers between 0 and 1 (inclusive), while +.Ar min_th +and +.Ar max_th +are integer numbers specifying thresholds for queue management +(thresholds are computed in bytes if the queue has been defined +in bytes, in slots otherwise). +The two parameters can also be of the same value if needed. The +.Nm dummynet +also supports the gentle RED variant (gred) and ECN (Explicit Congestion +Notification) as optional. Three +.Xr sysctl 8 +variables can be used to control the RED behaviour: +.Bl -tag -width indent +.It Va net.inet.ip.dummynet.red_lookup_depth +specifies the accuracy in computing the average queue +when the link is idle (defaults to 256, must be greater than zero) +.It Va net.inet.ip.dummynet.red_avg_pkt_size +specifies the expected average packet size (defaults to 512, must be +greater than zero) +.It Va net.inet.ip.dummynet.red_max_pkt_size +specifies the expected maximum packet size, only used when queue +thresholds are in bytes (defaults to 1500, must be greater than zero). +.El +.El +.Pp +When used with IPv6 data, +.Nm dummynet +currently has several limitations. +Information necessary to route link-local packets to an +interface is not available after processing by +.Nm dummynet +so those packets are dropped in the output path. +Care should be taken to ensure that link-local packets are not passed to +.Nm dummynet . +.Sh CHECKLIST +Here are some important points to consider when designing your +rules: +.Bl -bullet +.It +Remember that you filter both packets going +.Cm in +and +.Cm out . +Most connections need packets going in both directions. +.It +Remember to test very carefully. +It is a good idea to be near the console when doing this. +If you cannot be near the console, +use an auto-recovery script such as the one in +.Pa /usr/share/examples/ipfw/change_rules.sh . +.It +Do not forget the loopback interface. +.El +.Sh FINE POINTS +.Bl -bullet +.It +There are circumstances where fragmented datagrams are unconditionally +dropped. +TCP packets are dropped if they do not contain at least 20 bytes of +TCP header, UDP packets are dropped if they do not contain a full 8 +byte UDP header, and ICMP packets are dropped if they do not contain +4 bytes of ICMP header, enough to specify the ICMP type, code, and +checksum. +These packets are simply logged as +.Dq pullup failed +since there may not be enough good data in the packet to produce a +meaningful log entry. +.It +Another type of packet is unconditionally dropped, a TCP packet with a +fragment offset of one. +This is a valid packet, but it only has one use, to try +to circumvent firewalls. +When logging is enabled, these packets are +reported as being dropped by rule -1. +.It +If you are logged in over a network, loading the +.Xr kld 4 +version of +.Nm +is probably not as straightforward as you would think. +The following command line is recommended: +.Bd -literal -offset indent +kldload ipfw && \e +ipfw add 32000 allow ip from any to any +.Ed +.Pp +Along the same lines, doing an +.Bd -literal -offset indent +ipfw flush +.Ed +.Pp +in similar surroundings is also a bad idea. +.It +The +.Nm +filter list may not be modified if the system security level +is set to 3 or higher +(see +.Xr init 8 +for information on system security levels). +.El +.Sh PACKET DIVERSION +A +.Xr divert 4 +socket bound to the specified port will receive all packets +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) +.Nm +support in-kernel NAT using the kernel version of +.Xr libalias 3 . +.Pp +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 address of NIC for aliasing, dynamically changing +it if NIC's ip address changes. +.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. +.It Cm skip_global +Skip instance in case of global state lookup (see below). +.El +.Pp +Some specials value can be supplied instead of +.Va nat_number: +.Bl -tag -width indent +.It Cm global +Looks up translation state in all configured nat instances. +If an entry is found, packet is aliased according to that entry. +If no entry was found in any of the instances, packet is passed unchanged, +and no new entry will be created. +See section +.Sx MULTIPLE INSTANCES +in +.Xr natd 8 +for more information. +.It Cm tablearg +Uses argument supplied in lookup table. +See +.Sx LOOKUP TABLES +section below for more information on lookup tables. +.El +.Pp +To let the packet continue after being (de)aliased, set the sysctl variable +.Va 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. +.Ss 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. +.Ss SCTP NAT SUPPORT +SCTP nat can be configured in a similar manner to TCP through the +.Nm +command line tool. +The main difference is that +.Nm sctp nat +does not do port translation. +Since the local and global side ports will be the same, +there is no need to specify both. +Ports are redirected as follows: +.Bd -ragged -offset indent +.Bk -words +.Cm nat +.Ar nat_number +.Cm config if +.Ar nic +.Cm redirect_port sctp +.Ar ip_address [,addr_list] {[port | port-port] [,ports]} +.Ek +.Ed +.Pp +Most +.Nm sctp nat +configuration can be done in real-time through the +.Xr sysctl 8 +interface. +All may be changed dynamically, though the hash_table size will only +change for new +.Nm nat +instances. +See +.Sx SYSCTL VARIABLES +for more info. +.Sh LOADER TUNABLES +Tunables can be set in +.Xr loader 8 +prompt, +.Xr loader.conf 5 +or +.Xr kenv 1 +before ipfw module gets loaded. +.Bl -tag -width indent +.It Va net.inet.ip.fw.default_to_accept: No 0 +Defines ipfw last rule behavior. +This value overrides +.Cd "options IPFW_DEFAULT_TO_(ACCEPT|DENY)" +from kernel configuration file. +.It Va net.inet.ip.fw.tables_max: No 128 +Defines number of tables available in ipfw. +Number cannot exceed 65534. +.El +.Sh SYSCTL VARIABLES +A set of +.Xr sysctl 8 +variables controls the behaviour of the firewall and +associated modules +.Pq Nm dummynet , bridge , sctp nat . +These are shown below together with their default value +(but always check with the +.Xr sysctl 8 +command what value is actually in use) and meaning: +.Bl -tag -width indent +.It Va net.inet.ip.alias.sctp.accept_global_ootb_addip: No 0 +Defines how the +.Nm nat +responds to receipt of global OOTB ASCONF-AddIP: +.Bl -tag -width indent +.It Cm 0 +No response (unless a partially matching association exists - +ports and vtags match but global address does not) +.It Cm 1 +.Nm nat +will accept and process all OOTB global AddIP messages. +.El +.Pp +Option 1 should never be selected as this forms a security risk. +An attacker can +establish multiple fake associations by sending AddIP messages. +.It Va net.inet.ip.alias.sctp.chunk_proc_limit: No 5 +Defines the maximum number of chunks in an SCTP packet that will be +parsed for a +packet that matches an existing association. +This value is enforced to be greater or equal than +.Cm net.inet.ip.alias.sctp.initialising_chunk_proc_limit . +A high value is +a DoS risk yet setting too low a value may result in +important control chunks in +the packet not being located and parsed. +.It Va net.inet.ip.alias.sctp.error_on_ootb: No 1 +Defines when the +.Nm nat +responds to any Out-of-the-Blue (OOTB) packets with ErrorM packets. +An OOTB packet is a packet that arrives with no existing association +registered in the +.Nm nat +and is not an INIT or ASCONF-AddIP packet: +.Bl -tag -width indent +.It Cm 0 +ErrorM is never sent in response to OOTB packets. +.It Cm 1 +ErrorM is only sent to OOTB packets received on the local side. +.It Cm 2 +ErrorM is sent to the local side and on the global side ONLY if there is a +partial match (ports and vtags match but the source global IP does not). +This value is only useful if the +.Nm nat +is tracking global IP addresses. +.It Cm 3 +ErrorM is sent in response to all OOTB packets on both +the local and global side +(DoS risk). +.El +.Pp +At the moment the default is 0, since the ErrorM packet is not yet +supported by most SCTP stacks. +When it is supported, and if not tracking +global addresses, we recommend setting this value to 1 to allow +multi-homed local hosts to function with the +.Nm nat . +To track global addresses, we recommend setting this value to 2 to +allow global hosts to be informed when they need to (re)send an +ASCONF-AddIP. +Value 3 should never be chosen (except for debugging) as the +.Nm nat +will respond to all OOTB global packets (a DoS risk). +.It Va net.inet.ip.alias.sctp.hashtable_size: No 2003 +Size of hash tables used for +.Nm nat +lookups (100 < prime_number > 1000001). +This value sets the +.Nm hash table +size for any future created +.Nm nat +instance and therefore must be set prior to creating a +.Nm nat +instance. +The table sizes may be changed to suit specific needs. +If there will be few +concurrent associations, and memory is scarce, you may make these smaller. +If there will be many thousands (or millions) of concurrent associations, you +should make these larger. +A prime number is best for the table size. +The sysctl +update function will adjust your input value to the next highest prime number. +.It Va net.inet.ip.alias.sctp.holddown_time: No 0 +Hold association in table for this many seconds after receiving a +SHUTDOWN-COMPLETE. +This allows endpoints to correct shutdown gracefully if a +shutdown_complete is lost and retransmissions are required. +.It Va net.inet.ip.alias.sctp.init_timer: No 15 +Timeout value while waiting for (INIT-ACK|AddIP-ACK). +This value cannot be 0. +.It Va net.inet.ip.alias.sctp.initialising_chunk_proc_limit: No 2 +Defines the maximum number of chunks in an SCTP packet that will be parsed when +no existing association exists that matches that packet. +Ideally this packet +will only be an INIT or ASCONF-AddIP packet. +A higher value may become a DoS +risk as malformed packets can consume processing resources. +.It Va net.inet.ip.alias.sctp.param_proc_limit: No 25 +Defines the maximum number of parameters within a chunk that will be +parsed in a +packet. +As for other similar sysctl variables, larger values pose a DoS risk. +.It Va net.inet.ip.alias.sctp.log_level: No 0 +Level of detail in the system log messages (0 \- minimal, 1 \- event, +2 \- info, 3 \- detail, 4 \- debug, 5 \- max debug). +May be a good +option in high loss environments. +.It Va net.inet.ip.alias.sctp.shutdown_time: No 15 +Timeout value while waiting for SHUTDOWN-COMPLETE. +This value cannot be 0. +.It Va net.inet.ip.alias.sctp.track_global_addresses: No 0 +Enables/disables global IP address tracking within the +.Nm nat +and places an +upper limit on the number of addresses tracked for each association: +.Bl -tag -width indent +.It Cm 0 +Global tracking is disabled +.It Cm >1 +Enables tracking, the maximum number of addresses tracked for each +association is limited to this value +.El +.Pp +This variable is fully dynamic, the new value will be adopted for all newly +arriving associations, existing associations are treated +as they were previously. +Global tracking will decrease the number of collisions within the +.Nm nat +at a cost +of increased processing load, memory usage, complexity, and possible +.Nm nat +state +problems in complex networks with multiple +.Nm nats . +We recommend not tracking +global IP addresses, this will still result in a fully functional +.Nm nat . +.It Va net.inet.ip.alias.sctp.up_timer: No 300 +Timeout value to keep an association up with no traffic. +This value cannot be 0. +.It Va net.inet.ip.dummynet.expire : No 1 +Lazily delete dynamic pipes/queue once they have no pending traffic. +You can disable this by setting the variable to 0, in which case +the pipes/queues will only be deleted when the threshold is reached. +.It Va net.inet.ip.dummynet.hash_size : No 64 +Default size of the hash table used for dynamic pipes/queues. +This value is used when no +.Cm buckets +option is specified when configuring a pipe/queue. +.It Va net.inet.ip.dummynet.io_fast : No 0 +If set to a non-zero value, +the +.Dq fast +mode of +.Nm dummynet +operation (see above) is enabled. +.It Va net.inet.ip.dummynet.io_pkt +Number of packets passed to +.Nm dummynet . +.It Va net.inet.ip.dummynet.io_pkt_drop +Number of packets dropped by +.Nm dummynet . +.It Va net.inet.ip.dummynet.io_pkt_fast +Number of packets bypassed by the +.Nm dummynet +scheduler. +.It Va net.inet.ip.dummynet.max_chain_len : No 16 +Target value for the maximum number of pipes/queues in a hash bucket. +The product +.Cm max_chain_len*hash_size +is used to determine the threshold over which empty pipes/queues +will be expired even when +.Cm net.inet.ip.dummynet.expire=0 . +.It Va net.inet.ip.dummynet.red_lookup_depth : No 256 +.It Va net.inet.ip.dummynet.red_avg_pkt_size : No 512 +.It Va net.inet.ip.dummynet.red_max_pkt_size : No 1500 +Parameters used in the computations of the drop probability +for the RED algorithm. +.It Va net.inet.ip.dummynet.pipe_byte_limit : No 1048576 +.It Va net.inet.ip.dummynet.pipe_slot_limit : No 100 +The maximum queue size that can be specified in bytes or packets. +These limits prevent accidental exhaustion of resources such as mbufs. +If you raise these limits, +you should make sure the system is configured so that sufficient resources +are available. +.It Va net.inet.ip.fw.autoinc_step : No 100 +Delta between rule numbers when auto-generating them. +The value must be in the range 1..1000. +.It Va net.inet.ip.fw.curr_dyn_buckets : Va net.inet.ip.fw.dyn_buckets +The current number of buckets in the hash table for dynamic rules +(readonly). +.It Va net.inet.ip.fw.debug : No 1 +Controls debugging messages produced by +.Nm . +.It Va net.inet.ip.fw.default_rule : No 65535 +The default rule number (read-only). +By the design of +.Nm , the default rule is the last one, so its number +can also serve as the highest number allowed for a rule. +.It Va net.inet.ip.fw.dyn_buckets : No 256 +The number of buckets in the hash table for dynamic rules. +Must be a power of 2, up to 65536. +It only takes effect when all dynamic rules have expired, so you +are advised to use a +.Cm flush +command to make sure that the hash table is resized. +.It Va net.inet.ip.fw.dyn_count : No 3 +Current number of dynamic rules +(read-only). +.It Va net.inet.ip.fw.dyn_keepalive : No 1 +Enables generation of keepalive packets for +.Cm keep-state +rules on TCP sessions. +A keepalive is generated to both +sides of the connection every 5 seconds for the last 20 +seconds of the lifetime of the rule. +.It Va net.inet.ip.fw.dyn_max : No 8192 +Maximum number of dynamic rules. +When you hit this limit, no more dynamic rules can be +installed until old ones expire. +.It Va net.inet.ip.fw.dyn_ack_lifetime : No 300 +.It Va net.inet.ip.fw.dyn_syn_lifetime : No 20 +.It Va net.inet.ip.fw.dyn_fin_lifetime : No 1 +.It Va net.inet.ip.fw.dyn_rst_lifetime : No 1 +.It Va net.inet.ip.fw.dyn_udp_lifetime : No 5 +.It Va net.inet.ip.fw.dyn_short_lifetime : No 30 +These variables control the lifetime, in seconds, of dynamic +rules. +Upon the initial SYN exchange the lifetime is kept short, +then increased after both SYN have been seen, then decreased +again during the final FIN exchange or when a RST is received. +Both +.Em dyn_fin_lifetime +and +.Em dyn_rst_lifetime +must be strictly lower than 5 seconds, the period of +repetition of keepalives. +The firewall enforces that. +.It Va net.inet.ip.fw.dyn_keep_states: No 0 +Keep dynamic states on rule/set deletion. +States are relinked to default rule (65535). +This can be handly for ruleset reload. +Turned off by default. +.It Va net.inet.ip.fw.enable : No 1 +Enables the firewall. +Setting this variable to 0 lets you run your machine without +firewall even if compiled in. +.It Va net.inet6.ip6.fw.enable : No 1 +provides the same functionality as above for the IPv6 case. +.It Va net.inet.ip.fw.one_pass : No 1 +When set, the packet exiting from the +.Nm dummynet +pipe or from +.Xr ng_ipfw 4 +node is not passed though the firewall again. +Otherwise, after an action, the packet is +reinjected into the firewall at the next rule. +.It Va net.inet.ip.fw.tables_max : No 128 +Maximum number of tables. +.It Va net.inet.ip.fw.verbose : No 1 +Enables verbose messages. +.It Va net.inet.ip.fw.verbose_limit : No 0 +Limits the number of messages produced by a verbose firewall. +.It Va net.inet6.ip6.fw.deny_unknown_exthdrs : No 1 +If enabled packets with unknown IPv6 Extension Headers will be denied. +.It Va net.link.ether.ipfw : No 0 +Controls whether layer-2 packets are passed to +.Nm . +Default is no. +.It Va net.link.bridge.ipfw : No 0 +Controls whether bridged packets are passed to +.Nm . +Default is no. +.El +.Sh INTERNAL DIAGNOSTICS +There are some commands that may be useful to understand current state +of certain subsystems inside kernel module. +These commands provide debugging output which may change without notice. +.Pp +Currently the following commands are available as +.Cm internal +sub-options: +.Bl -tag -width indent +.It Cm iflist +Lists all interface which are currently tracked by +.Nm +with their in-kernel status. +.It Cm talist +List all table lookup algorithms currently available. +.El +.Sh EXAMPLES +There are far too many possible uses of +.Nm +so this Section will only give a small set of examples. +.Pp +.Ss BASIC PACKET FILTERING +This command adds an entry which denies all tcp packets from +.Em cracker.evil.org +to the telnet port of +.Em wolf.tambov.su +from being forwarded by the host: +.Pp +.Dl "ipfw add deny tcp from cracker.evil.org to wolf.tambov.su telnet" +.Pp +This one disallows any connection from the entire cracker's +network to my host: +.Pp +.Dl "ipfw add deny ip from 123.45.67.0/24 to my.host.org" +.Pp +A first and efficient way to limit access (not using dynamic rules) +is the use of the following rules: +.Pp +.Dl "ipfw add allow tcp from any to any established" +.Dl "ipfw add allow tcp from net1 portlist1 to net2 portlist2 setup" +.Dl "ipfw add allow tcp from net3 portlist3 to net3 portlist3 setup" +.Dl "..." +.Dl "ipfw add deny tcp from any to any" +.Pp +The first rule will be a quick match for normal TCP packets, +but it will not match the initial SYN packet, which will be +matched by the +.Cm setup +rules only for selected source/destination pairs. +All other SYN packets will be rejected by the final +.Cm deny +rule. +.Pp +If you administer one or more subnets, you can take advantage +of the address sets and or-blocks and write extremely +compact rulesets which selectively enable services to blocks +of clients, as below: +.Pp +.Dl "goodguys=\*q{ 10.1.2.0/24{20,35,66,18} or 10.2.3.0/28{6,3,11} }\*q" +.Dl "badguys=\*q10.1.2.0/24{8,38,60}\*q" +.Dl "" +.Dl "ipfw add allow ip from ${goodguys} to any" +.Dl "ipfw add deny ip from ${badguys} to any" +.Dl "... normal policies ..." +.Pp +The +.Cm verrevpath +option could be used to do automated anti-spoofing by adding the +following to the top of a ruleset: +.Pp +.Dl "ipfw add deny ip from any to any not verrevpath in" +.Pp +This rule drops all incoming packets that appear to be coming to the +system on the wrong interface. +For example, a packet with a source +address belonging to a host on a protected internal network would be +dropped if it tried to enter the system from an external interface. +.Pp +The +.Cm antispoof +option could be used to do similar but more restricted anti-spoofing +by adding the following to the top of a ruleset: +.Pp +.Dl "ipfw add deny ip from any to any not antispoof in" +.Pp +This rule drops all incoming packets that appear to be coming from another +directly connected system but on the wrong interface. +For example, a packet with a source address of +.Li 192.168.0.0/24 , +configured on +.Li fxp0 , +but coming in on +.Li fxp1 +would be dropped. +.Pp +The +.Cm setdscp +option could be used to (re)mark user traffic, +by adding the following to the appropriate place in ruleset: +.Pp +.Dl "ipfw add setdscp be ip from any to any dscp af11,af21" +.Ss DYNAMIC RULES +In order to protect a site from flood attacks involving fake +TCP packets, it is safer to use dynamic rules: +.Pp +.Dl "ipfw add check-state" +.Dl "ipfw add deny tcp from any to any established" +.Dl "ipfw add allow tcp from my-net to any setup keep-state" +.Pp +This will let the firewall install dynamic rules only for +those connection which start with a regular SYN packet coming +from the inside of our network. +Dynamic rules are checked when encountering the first +occurrence of a +.Cm check-state , +.Cm keep-state +or +.Cm limit +rule. +A +.Cm check-state +rule should usually be placed near the beginning of the +ruleset to minimize the amount of work scanning the ruleset. +Your mileage may vary. +.Pp +To limit the number of connections a user can open +you can use the following type of rules: +.Pp +.Dl "ipfw add allow tcp from my-net/24 to any setup limit src-addr 10" +.Dl "ipfw add allow tcp from any to me setup limit src-addr 4" +.Pp +The former (assuming it runs on a gateway) will allow each host +on a /24 network to open at most 10 TCP connections. +The latter can be placed on a server to make sure that a single +client does not use more than 4 simultaneous connections. +.Pp +.Em BEWARE : +stateful rules can be subject to denial-of-service attacks +by a SYN-flood which opens a huge number of dynamic rules. +The effects of such attacks can be partially limited by +acting on a set of +.Xr sysctl 8 +variables which control the operation of the firewall. +.Pp +Here is a good usage of the +.Cm list +command to see accounting records and timestamp information: +.Pp +.Dl ipfw -at list +.Pp +or in short form without timestamps: +.Pp +.Dl ipfw -a list +.Pp +which is equivalent to: +.Pp +.Dl ipfw show +.Pp +Next rule diverts all incoming packets from 192.168.2.0/24 +to divert port 5000: +.Pp +.Dl ipfw divert 5000 ip from 192.168.2.0/24 to any in +.Ss TRAFFIC SHAPING +The following rules show some of the applications of +.Nm +and +.Nm dummynet +for simulations and the like. +.Pp +This rule drops random incoming packets with a probability +of 5%: +.Pp +.Dl "ipfw add prob 0.05 deny ip from any to any in" +.Pp +A similar effect can be achieved making use of +.Nm dummynet +pipes: +.Pp +.Dl "ipfw add pipe 10 ip from any to any" +.Dl "ipfw pipe 10 config plr 0.05" +.Pp +We can use pipes to artificially limit bandwidth, e.g.\& on a +machine acting as a router, if we want to limit traffic from +local clients on 192.168.2.0/24 we do: +.Pp +.Dl "ipfw add pipe 1 ip from 192.168.2.0/24 to any out" +.Dl "ipfw pipe 1 config bw 300Kbit/s queue 50KBytes" +.Pp +note that we use the +.Cm out +modifier so that the rule is not used twice. +Remember in fact that +.Nm +rules are checked both on incoming and outgoing packets. +.Pp +Should we want to simulate a bidirectional link with bandwidth +limitations, the correct way is the following: +.Pp +.Dl "ipfw add pipe 1 ip from any to any out" +.Dl "ipfw add pipe 2 ip from any to any in" +.Dl "ipfw pipe 1 config bw 64Kbit/s queue 10Kbytes" +.Dl "ipfw pipe 2 config bw 64Kbit/s queue 10Kbytes" +.Pp +The above can be very useful, e.g.\& if you want to see how +your fancy Web page will look for a residential user who +is connected only through a slow link. +You should not use only one pipe for both directions, unless +you want to simulate a half-duplex medium (e.g.\& AppleTalk, +Ethernet, IRDA). +It is not necessary that both pipes have the same configuration, +so we can also simulate asymmetric links. +.Pp +Should we want to verify network performance with the RED queue +management algorithm: +.Pp +.Dl "ipfw add pipe 1 ip from any to any" +.Dl "ipfw pipe 1 config bw 500Kbit/s queue 100 red 0.002/30/80/0.1" +.Pp +Another typical application of the traffic shaper is to +introduce some delay in the communication. +This can significantly affect applications which do a lot of Remote +Procedure Calls, and where the round-trip-time of the +connection often becomes a limiting factor much more than +bandwidth: +.Pp +.Dl "ipfw add pipe 1 ip from any to any out" +.Dl "ipfw add pipe 2 ip from any to any in" +.Dl "ipfw pipe 1 config delay 250ms bw 1Mbit/s" +.Dl "ipfw pipe 2 config delay 250ms bw 1Mbit/s" +.Pp +Per-flow queueing can be useful for a variety of purposes. +A very simple one is counting traffic: +.Pp +.Dl "ipfw add pipe 1 tcp from any to any" +.Dl "ipfw add pipe 1 udp from any to any" +.Dl "ipfw add pipe 1 ip from any to any" +.Dl "ipfw pipe 1 config mask all" +.Pp +The above set of rules will create queues (and collect +statistics) for all traffic. +Because the pipes have no limitations, the only effect is +collecting statistics. +Note that we need 3 rules, not just the last one, because +when +.Nm +tries to match IP packets it will not consider ports, so we +would not see connections on separate ports as different +ones. +.Pp +A more sophisticated example is limiting the outbound traffic +on a net with per-host limits, rather than per-network limits: +.Pp +.Dl "ipfw add pipe 1 ip from 192.168.2.0/24 to any out" +.Dl "ipfw add pipe 2 ip from any to 192.168.2.0/24 in" +.Dl "ipfw pipe 1 config mask src-ip 0x000000ff bw 200Kbit/s queue 20Kbytes" +.Dl "ipfw pipe 2 config mask dst-ip 0x000000ff bw 200Kbit/s queue 20Kbytes" +.Ss LOOKUP TABLES +In the following example, we need to create several traffic bandwidth +classes and we need different hosts/networks to fall into different classes. +We create one pipe for each class and configure them accordingly. +Then we create a single table and fill it with IP subnets and addresses. +For each subnet/host we set the argument equal to the number of the pipe +that it should use. +Then we classify traffic using a single rule: +.Pp +.Dl "ipfw pipe 1 config bw 1000Kbyte/s" +.Dl "ipfw pipe 4 config bw 4000Kbyte/s" +.Dl "..." +.Dl "ipfw table T1 create type addr" +.Dl "ipfw table T1 add 192.168.2.0/24 1" +.Dl "ipfw table T1 add 192.168.0.0/27 4" +.Dl "ipfw table T1 add 192.168.0.2 1" +.Dl "..." +.Dl "ipfw add pipe tablearg ip from 'table(T1)' to any" +.Pp +Using the +.Cm fwd +action, the table entries may include hostnames and IP addresses. +.Pp +.Dl "ipfw table T2 create type addr ftype ip" +.Dl "ipfw table T2 add 192.168.2.0/24 10.23.2.1" +.Dl "ipfw table T21 add 192.168.0.0/27 router1.dmz" +.Dl "..." +.Dl "ipfw add 100 fwd tablearg ip from any to table(1)" +.Pp +In the following example per-interface firewall is created: +.Pp +.Dl "ipfw table IN create type iface valtype skipto,fib" +.Dl "ipfw table IN add vlan20 12000,12" +.Dl "ipfw table IN add vlan30 13000,13" +.Dl "ipfw table OUT create type iface valtype skipto" +.Dl "ipfw table OUT add vlan20 22000" +.Dl "ipfw table OUT add vlan30 23000" +.Dl ".." +.Dl "ipfw add 100 ipfw setfib tablearg ip from any to any recv 'table(IN)' in" +.Dl "ipfw add 200 ipfw skipto tablearg ip from any to any recv 'table(IN)' in" +.Dl "ipfw add 300 ipfw skipto tablearg ip from any to any xmit 'table(OUT)' out" +.Pp +The following example illustrate usage of flow tables: +.Pp +.Dl "ipfw table fl create type flow:flow:src-ip,proto,dst-ip,dst-port" +.Dl "ipfw table fl add 2a02:6b8:77::88,tcp,2a02:6b8:77::99,80 11" +.Dl "ipfw table fl add 10.0.0.1,udp,10.0.0.2,53 12" +.Dl ".." +.Dl "ipfw add 100 allow ip from any to any flow 'table(fl,11)' recv ix0" +.Ss SETS OF RULES +To add a set of rules atomically, e.g.\& set 18: +.Pp +.Dl "ipfw set disable 18" +.Dl "ipfw add NN set 18 ... # repeat as needed" +.Dl "ipfw set enable 18" +.Pp +To delete a set of rules atomically the command is simply: +.Pp +.Dl "ipfw delete set 18" +.Pp +To test a ruleset and disable it and regain control if something goes wrong: +.Pp +.Dl "ipfw set disable 18" +.Dl "ipfw add NN set 18 ... # repeat as needed" +.Dl "ipfw set enable 18; echo done; sleep 30 && ipfw set disable 18" +.Pp +Here if everything goes well, you press control-C before the "sleep" +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. +.Pp +To show rules of the specific set: +.Pp +.Dl "ipfw set 18 show" +.Pp +To show rules of the disabled set: +.Pp +.Dl "ipfw -S set 18 show" +.Pp +To clear a specific rule counters of the specific set: +.Pp +.Dl "ipfw set 18 zero NN" +.Pp +To delete a specific rule of the specific set: +.Pp +.Dl "ipfw set 18 delete NN" +.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 split 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" +.Sh SEE ALSO +.Xr cpp 1 , +.Xr m4 1 , +.Xr altq 4 , +.Xr divert 4 , +.Xr dummynet 4 , +.Xr if_bridge 4 , +.Xr ip 4 , +.Xr ipfirewall 4 , +.Xr ng_ipfw 4 , +.Xr protocols 5 , +.Xr services 5 , +.Xr init 8 , +.Xr kldload 8 , +.Xr reboot 8 , +.Xr sysctl 8 , +.Xr syslogd 8 +.Sh HISTORY +The +.Nm +utility first appeared in +.Fx 2.0 . +.Nm dummynet +was introduced in +.Fx 2.2.8 . +Stateful extensions were introduced in +.Fx 4.0 . +.Nm ipfw2 +was introduced in Summer 2002. +.Sh AUTHORS +.An Ugen J. S. Antsilevich , +.An Poul-Henning Kamp , +.An Alex Nash , +.An Archie Cobbs , +.An Luigi Rizzo . +.Pp +.An -nosplit +API based upon code written by +.An Daniel Boulet +for BSDI. +.Pp +Dummynet has been introduced by Luigi Rizzo in 1997-1998. +.Pp +Some early work (1999-2000) on the +.Nm dummynet +traffic shaper supported by Akamba Corp. +.Pp +The ipfw core (ipfw2) has been completely redesigned and +reimplemented by Luigi Rizzo in summer 2002. +Further +actions and +options have been added by various developer over the years. +.Pp +.An -nosplit +In-kernel NAT support written by +.An Paolo Pisati Aq Mt piso@FreeBSD.org +as part of a Summer of Code 2005 project. +.Pp +SCTP +.Nm nat +support has been developed by +.An The Centre for Advanced Internet Architectures (CAIA) Aq http://www.caia.swin.edu.au . +The primary developers and maintainers are David Hayes and Jason But. +For further information visit: +.Aq http://www.caia.swin.edu.au/urp/SONATA +.Pp +Delay profiles have been developed by Alessandro Cerri and +Luigi Rizzo, supported by the +European Commission within Projects Onelab and Onelab2. +.Sh BUGS +The syntax has grown over the years and sometimes it might be confusing. +Unfortunately, backward compatibility prevents cleaning up mistakes +made in the definition of the syntax. +.Pp +.Em !!! WARNING !!! +.Pp +Misconfiguring the firewall can put your computer in an unusable state, +possibly shutting down network services and requiring console access to +regain control of it. +.Pp +Incoming packet fragments diverted by +.Cm divert +are reassembled before delivery to the socket. +The action used on those packet is the one from the +rule which matches the first fragment of the packet. +.Pp +Packets diverted to userland, and then reinserted by a userland process +may lose various packet attributes. +The packet source interface name +will be preserved if it is shorter than 8 bytes and the userland process +saves and reuses the sockaddr_in +(as does +.Xr natd 8 ) ; +otherwise, it may be lost. +If a packet is reinserted in this manner, later rules may be incorrectly +applied, making the order of +.Cm divert +rules in the rule sequence very important. +.Pp +Dummynet drops all packets with IPv6 link-local addresses. +.Pp +Rules using +.Cm uid +or +.Cm gid +may not behave as expected. +In particular, incoming SYN packets may +have no uid or gid associated with them since they do not yet belong +to a TCP connection, and the uid/gid associated with a packet may not +be as expected if the associated process calls +.Xr setuid 2 +or similar system calls. +.Pp +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 . +.Pp +ICMP error messages are not implicitly matched by dynamic rules +for the respective conversations. +To avoid failures of network error detection and path MTU discovery, +ICMP error messages may need to be allowed explicitly through static +rules. +.Pp +Rules using +.Cm call +and +.Cm return +actions may lead to confusing behaviour if ruleset has mistakes, +and/or interaction with other subsystems (netgraph, dummynet, etc.) is used. +One possible case for this is packet leaving +.Nm +in subroutine on the input pass, while later on output encountering unpaired +.Cm return +first. +As the call stack is kept intact after input pass, packet will suddenly +return to the rule number used on input pass, not on output one. +Order of processing should be checked carefully to avoid such mistakes. diff --git a/tools/ipfw/ipfw2.c b/tools/ipfw/ipfw2.c new file mode 100644 index 000000000..a50326862 --- /dev/null +++ b/tools/ipfw/ipfw2.c @@ -0,0 +1,5333 @@ +/* + * Copyright (c) 2002-2003 Luigi Rizzo + * Copyright (c) 1996 Alex Nash, Paul Traina, Poul-Henning Kamp + * Copyright (c) 1994 Ugen J.S.Antsilevich + * + * Idea and grammar partially left from: + * Copyright (c) 1993 Daniel Boulet + * + * Redistribution and use in source forms, with and without modification, + * are permitted provided that this entire comment appears intact. + * + * Redistribution in binary form may occur without any restrictions. + * Obviously, it would be nice if you gave credit where credit is due + * but requiring it would be too onerous. + * + * This software is provided ``AS IS'' without any warranties of any kind. + * + * NEW command line interface for IP firewall facility + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include + +#include "ipfw2.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* ctime */ +#include /* _long_to_time */ +#include +#include +#include /* offsetof */ + +#include +#include /* only IFNAMSIZ */ +#include +#include /* only n_short, n_long */ +#include +#include +#include +#include +#include + +struct cmdline_opts co; /* global options */ + +struct format_opts { + int bcwidth; + int pcwidth; + int show_counters; + int show_time; /* show timestamp */ + uint32_t set_mask; /* enabled sets mask */ + uint32_t flags; /* request flags */ + uint32_t first; /* first rule to request */ + uint32_t last; /* last rule to request */ + uint32_t dcnt; /* number of dynamic states */ + ipfw_obj_ctlv *tstate; /* table state data */ +}; + +int resvd_set_number = RESVD_SET; + +int ipfw_socket = -1; + +#define CHECK_LENGTH(v, len) do { \ + if ((v) < (len)) \ + errx(EX_DATAERR, "Rule too long"); \ + } while (0) +/* + * Check if we have enough space in cmd buffer. Note that since + * first 8? u32 words are reserved by reserved header, full cmd + * buffer can't be used, so we need to protect from buffer overrun + * only. At the beginning, cblen is less than actual buffer size by + * size of ipfw_insn_u32 instruction + 1 u32 work. This eliminates need + * for checking small instructions fitting in given range. + * We also (ab)use the fact that ipfw_insn is always the first field + * for any custom instruction. + */ +#define CHECK_CMDLEN CHECK_LENGTH(cblen, F_LEN((ipfw_insn *)cmd)) + +#define GET_UINT_ARG(arg, min, max, tok, s_x) do { \ + if (!av[0]) \ + errx(EX_USAGE, "%s: missing argument", match_value(s_x, tok)); \ + if (_substrcmp(*av, "tablearg") == 0) { \ + arg = IP_FW_TARG; \ + break; \ + } \ + \ + { \ + long _xval; \ + char *end; \ + \ + _xval = strtol(*av, &end, 10); \ + \ + if (!isdigit(**av) || *end != '\0' || (_xval == 0 && errno == EINVAL)) \ + errx(EX_DATAERR, "%s: invalid argument: %s", \ + match_value(s_x, tok), *av); \ + \ + if (errno == ERANGE || _xval < min || _xval > max) \ + errx(EX_DATAERR, "%s: argument is out of range (%u..%u): %s", \ + match_value(s_x, tok), min, max, *av); \ + \ + if (_xval == IP_FW_TARG) \ + errx(EX_DATAERR, "%s: illegal argument value: %s", \ + match_value(s_x, tok), *av); \ + arg = _xval; \ + } \ +} while (0) + +static struct _s_x f_tcpflags[] = { + { "syn", TH_SYN }, + { "fin", TH_FIN }, + { "ack", TH_ACK }, + { "psh", TH_PUSH }, + { "rst", TH_RST }, + { "urg", TH_URG }, + { "tcp flag", 0 }, + { NULL, 0 } +}; + +static struct _s_x f_tcpopts[] = { + { "mss", IP_FW_TCPOPT_MSS }, + { "maxseg", IP_FW_TCPOPT_MSS }, + { "window", IP_FW_TCPOPT_WINDOW }, + { "sack", IP_FW_TCPOPT_SACK }, + { "ts", IP_FW_TCPOPT_TS }, + { "timestamp", IP_FW_TCPOPT_TS }, + { "cc", IP_FW_TCPOPT_CC }, + { "tcp option", 0 }, + { NULL, 0 } +}; + +/* + * IP options span the range 0 to 255 so we need to remap them + * (though in fact only the low 5 bits are significant). + */ +static struct _s_x f_ipopts[] = { + { "ssrr", IP_FW_IPOPT_SSRR}, + { "lsrr", IP_FW_IPOPT_LSRR}, + { "rr", IP_FW_IPOPT_RR}, + { "ts", IP_FW_IPOPT_TS}, + { "ip option", 0 }, + { NULL, 0 } +}; + +static struct _s_x f_iptos[] = { + { "lowdelay", IPTOS_LOWDELAY}, + { "throughput", IPTOS_THROUGHPUT}, + { "reliability", IPTOS_RELIABILITY}, + { "mincost", IPTOS_MINCOST}, + { "congestion", IPTOS_ECN_CE}, + { "ecntransport", IPTOS_ECN_ECT0}, + { "ip tos option", 0}, + { NULL, 0 } +}; + +struct _s_x f_ipdscp[] = { + { "af11", IPTOS_DSCP_AF11 >> 2 }, /* 001010 */ + { "af12", IPTOS_DSCP_AF12 >> 2 }, /* 001100 */ + { "af13", IPTOS_DSCP_AF13 >> 2 }, /* 001110 */ + { "af21", IPTOS_DSCP_AF21 >> 2 }, /* 010010 */ + { "af22", IPTOS_DSCP_AF22 >> 2 }, /* 010100 */ + { "af23", IPTOS_DSCP_AF23 >> 2 }, /* 010110 */ + { "af31", IPTOS_DSCP_AF31 >> 2 }, /* 011010 */ + { "af32", IPTOS_DSCP_AF32 >> 2 }, /* 011100 */ + { "af33", IPTOS_DSCP_AF33 >> 2 }, /* 011110 */ + { "af41", IPTOS_DSCP_AF41 >> 2 }, /* 100010 */ + { "af42", IPTOS_DSCP_AF42 >> 2 }, /* 100100 */ + { "af43", IPTOS_DSCP_AF43 >> 2 }, /* 100110 */ + { "be", IPTOS_DSCP_CS0 >> 2 }, /* 000000 */ + { "ef", IPTOS_DSCP_EF >> 2 }, /* 101110 */ + { "cs0", IPTOS_DSCP_CS0 >> 2 }, /* 000000 */ + { "cs1", IPTOS_DSCP_CS1 >> 2 }, /* 001000 */ + { "cs2", IPTOS_DSCP_CS2 >> 2 }, /* 010000 */ + { "cs3", IPTOS_DSCP_CS3 >> 2 }, /* 011000 */ + { "cs4", IPTOS_DSCP_CS4 >> 2 }, /* 100000 */ + { "cs5", IPTOS_DSCP_CS5 >> 2 }, /* 101000 */ + { "cs6", IPTOS_DSCP_CS6 >> 2 }, /* 110000 */ + { "cs7", IPTOS_DSCP_CS7 >> 2 }, /* 100000 */ + { NULL, 0 } +}; + +static struct _s_x limit_masks[] = { + {"all", DYN_SRC_ADDR|DYN_SRC_PORT|DYN_DST_ADDR|DYN_DST_PORT}, + {"src-addr", DYN_SRC_ADDR}, + {"src-port", DYN_SRC_PORT}, + {"dst-addr", DYN_DST_ADDR}, + {"dst-port", DYN_DST_PORT}, + {NULL, 0} +}; + +/* + * we use IPPROTO_ETHERTYPE as a fake protocol id to call the print routines + * This is only used in this code. + */ +#define IPPROTO_ETHERTYPE 0x1000 +static struct _s_x ether_types[] = { + /* + * Note, we cannot use "-:&/" in the names because they are field + * separators in the type specifications. Also, we use s = NULL as + * end-delimiter, because a type of 0 can be legal. + */ + { "ip", 0x0800 }, + { "ipv4", 0x0800 }, + { "ipv6", 0x86dd }, + { "arp", 0x0806 }, + { "rarp", 0x8035 }, + { "vlan", 0x8100 }, + { "loop", 0x9000 }, + { "trail", 0x1000 }, + { "at", 0x809b }, + { "atalk", 0x809b }, + { "aarp", 0x80f3 }, + { "pppoe_disc", 0x8863 }, + { "pppoe_sess", 0x8864 }, + { "ipx_8022", 0x00E0 }, + { "ipx_8023", 0x0000 }, + { "ipx_ii", 0x8137 }, + { "ipx_snap", 0x8137 }, + { "ipx", 0x8137 }, + { "ns", 0x0600 }, + { NULL, 0 } +}; + +static struct _s_x rule_eactions[] = { + { NULL, 0 } /* terminator */ +}; + +static struct _s_x rule_actions[] = { + { "accept", TOK_ACCEPT }, + { "pass", TOK_ACCEPT }, + { "allow", TOK_ACCEPT }, + { "permit", TOK_ACCEPT }, + { "count", TOK_COUNT }, + { "pipe", TOK_PIPE }, + { "queue", TOK_QUEUE }, + { "divert", TOK_DIVERT }, + { "tee", TOK_TEE }, + { "netgraph", TOK_NETGRAPH }, + { "ngtee", TOK_NGTEE }, + { "fwd", TOK_FORWARD }, + { "forward", TOK_FORWARD }, + { "skipto", TOK_SKIPTO }, + { "deny", TOK_DENY }, + { "drop", TOK_DENY }, + { "reject", TOK_REJECT }, + { "reset6", TOK_RESET6 }, + { "reset", TOK_RESET }, + { "unreach6", TOK_UNREACH6 }, + { "unreach", TOK_UNREACH }, + { "check-state", TOK_CHECKSTATE }, + { "//", TOK_COMMENT }, + { "nat", TOK_NAT }, + { "reass", TOK_REASS }, + { "setfib", TOK_SETFIB }, + { "setdscp", TOK_SETDSCP }, + { "call", TOK_CALL }, + { "return", TOK_RETURN }, + { "eaction", TOK_EACTION }, + { NULL, 0 } /* terminator */ +}; + +static struct _s_x rule_action_params[] = { + { "altq", TOK_ALTQ }, + { "log", TOK_LOG }, + { "tag", TOK_TAG }, + { "untag", TOK_UNTAG }, + { NULL, 0 } /* terminator */ +}; + +/* + * The 'lookup' instruction accepts one of the following arguments. + * -1 is a terminator for the list. + * Arguments are passed as v[1] in O_DST_LOOKUP options. + */ +static int lookup_key[] = { + TOK_DSTIP, TOK_SRCIP, TOK_DSTPORT, TOK_SRCPORT, + TOK_UID, TOK_JAIL, TOK_DSCP, -1 }; + +static struct _s_x rule_options[] = { + { "tagged", TOK_TAGGED }, + { "uid", TOK_UID }, + { "gid", TOK_GID }, + { "jail", TOK_JAIL }, + { "in", TOK_IN }, + { "limit", TOK_LIMIT }, + { "keep-state", TOK_KEEPSTATE }, + { "bridged", TOK_LAYER2 }, + { "layer2", TOK_LAYER2 }, + { "out", TOK_OUT }, + { "diverted", TOK_DIVERTED }, + { "diverted-loopback", TOK_DIVERTEDLOOPBACK }, + { "diverted-output", TOK_DIVERTEDOUTPUT }, + { "xmit", TOK_XMIT }, + { "recv", TOK_RECV }, + { "via", TOK_VIA }, + { "fragment", TOK_FRAG }, + { "frag", TOK_FRAG }, + { "fib", TOK_FIB }, + { "ipoptions", TOK_IPOPTS }, + { "ipopts", TOK_IPOPTS }, + { "iplen", TOK_IPLEN }, + { "ipid", TOK_IPID }, + { "ipprecedence", TOK_IPPRECEDENCE }, + { "dscp", TOK_DSCP }, + { "iptos", TOK_IPTOS }, + { "ipttl", TOK_IPTTL }, + { "ipversion", TOK_IPVER }, + { "ipver", TOK_IPVER }, + { "estab", TOK_ESTAB }, + { "established", TOK_ESTAB }, + { "setup", TOK_SETUP }, + { "sockarg", TOK_SOCKARG }, + { "tcpdatalen", TOK_TCPDATALEN }, + { "tcpflags", TOK_TCPFLAGS }, + { "tcpflgs", TOK_TCPFLAGS }, + { "tcpoptions", TOK_TCPOPTS }, + { "tcpopts", TOK_TCPOPTS }, + { "tcpseq", TOK_TCPSEQ }, + { "tcpack", TOK_TCPACK }, + { "tcpwin", TOK_TCPWIN }, + { "icmptype", TOK_ICMPTYPES }, + { "icmptypes", TOK_ICMPTYPES }, + { "dst-ip", TOK_DSTIP }, + { "src-ip", TOK_SRCIP }, + { "dst-port", TOK_DSTPORT }, + { "src-port", TOK_SRCPORT }, + { "proto", TOK_PROTO }, + { "MAC", TOK_MAC }, + { "mac", TOK_MAC }, + { "mac-type", TOK_MACTYPE }, + { "verrevpath", TOK_VERREVPATH }, + { "versrcreach", TOK_VERSRCREACH }, + { "antispoof", TOK_ANTISPOOF }, + { "ipsec", TOK_IPSEC }, + { "icmp6type", TOK_ICMP6TYPES }, + { "icmp6types", TOK_ICMP6TYPES }, + { "ext6hdr", TOK_EXT6HDR}, + { "flow-id", TOK_FLOWID}, + { "ipv6", TOK_IPV6}, + { "ip6", TOK_IPV6}, + { "ipv4", TOK_IPV4}, + { "ip4", TOK_IPV4}, + { "dst-ipv6", TOK_DSTIP6}, + { "dst-ip6", TOK_DSTIP6}, + { "src-ipv6", TOK_SRCIP6}, + { "src-ip6", TOK_SRCIP6}, + { "lookup", TOK_LOOKUP}, + { "flow", TOK_FLOW}, + { "//", TOK_COMMENT }, + + { "not", TOK_NOT }, /* pseudo option */ + { "!", /* escape ? */ TOK_NOT }, /* pseudo option */ + { "or", TOK_OR }, /* pseudo option */ + { "|", /* escape */ TOK_OR }, /* pseudo option */ + { "{", TOK_STARTBRACE }, /* pseudo option */ + { "(", TOK_STARTBRACE }, /* pseudo option */ + { "}", TOK_ENDBRACE }, /* pseudo option */ + { ")", TOK_ENDBRACE }, /* pseudo option */ + { NULL, 0 } /* terminator */ +}; + +void bprint_uint_arg(struct buf_pr *bp, const char *str, uint32_t arg); +static int ipfw_get_config(struct cmdline_opts *co, struct format_opts *fo, + ipfw_cfg_lheader **pcfg, size_t *psize); +static int ipfw_show_config(struct cmdline_opts *co, struct format_opts *fo, + ipfw_cfg_lheader *cfg, size_t sz, int ac, char **av); +static void ipfw_list_tifaces(void); + +struct tidx; +static uint16_t pack_object(struct tidx *tstate, char *name, int otype); +static uint16_t pack_table(struct tidx *tstate, char *name); + +static char *table_search_ctlv(ipfw_obj_ctlv *ctlv, uint16_t idx); +static void object_sort_ctlv(ipfw_obj_ctlv *ctlv); +static char *object_search_ctlv(ipfw_obj_ctlv *ctlv, uint16_t idx, + uint16_t type); + +/* + * Simple string buffer API. + * Used to simplify buffer passing between function and for + * transparent overrun handling. + */ + +/* + * Allocates new buffer of given size @sz. + * + * Returns 0 on success. + */ +int +bp_alloc(struct buf_pr *b, size_t size) +{ + memset(b, 0, sizeof(struct buf_pr)); + + if ((b->buf = calloc(1, size)) == NULL) + return (ENOMEM); + + b->ptr = b->buf; + b->size = size; + b->avail = b->size; + + return (0); +} + +void +bp_free(struct buf_pr *b) +{ + + free(b->buf); +} + +/* + * Flushes buffer so new writer start from beginning. + */ +void +bp_flush(struct buf_pr *b) +{ + + b->ptr = b->buf; + b->avail = b->size; + b->buf[0] = '\0'; +} + +/* + * Print message specified by @format and args. + * Automatically manage buffer space and transparently handle + * buffer overruns. + * + * Returns number of bytes that should have been printed. + */ +int +bprintf(struct buf_pr *b, char *format, ...) +{ + va_list args; + int i; + + va_start(args, format); + + i = vsnprintf(b->ptr, b->avail, format, args); + va_end(args); + + if (i > b->avail || i < 0) { + /* Overflow or print error */ + b->avail = 0; + } else { + b->ptr += i; + b->avail -= i; + } + + b->needed += i; + + return (i); +} + +/* + * Special values printer for tablearg-aware opcodes. + */ +void +bprint_uint_arg(struct buf_pr *bp, const char *str, uint32_t arg) +{ + + if (str != NULL) + bprintf(bp, "%s", str); + if (arg == IP_FW_TARG) + bprintf(bp, "tablearg"); + else + bprintf(bp, "%u", arg); +} + +/* + * Helper routine to print a possibly unaligned uint64_t on + * various platform. If width > 0, print the value with + * the desired width, followed by a space; + * otherwise, return the required width. + */ +int +pr_u64(struct buf_pr *b, uint64_t *pd, int width) +{ +#ifdef TCC +#define U64_FMT "I64" +#else +#define U64_FMT "llu" +#endif + uint64_t u; + unsigned long long d; + + bcopy (pd, &u, sizeof(u)); + d = u; + return (width > 0) ? + bprintf(b, "%*" U64_FMT " ", width, d) : + snprintf(NULL, 0, "%" U64_FMT, d) ; +#undef U64_FMT +} + + +void * +safe_calloc(size_t number, size_t size) +{ + void *ret = calloc(number, size); + + if (ret == NULL) + err(EX_OSERR, "calloc"); + return ret; +} + +void * +safe_realloc(void *ptr, size_t size) +{ + void *ret = realloc(ptr, size); + + if (ret == NULL) + err(EX_OSERR, "realloc"); + return ret; +} + +/* + * Compare things like interface or table names. + */ +int +stringnum_cmp(const char *a, const char *b) +{ + int la, lb; + + la = strlen(a); + lb = strlen(b); + + if (la > lb) + return (1); + else if (la < lb) + return (-01); + + return (strcmp(a, b)); +} + + +/* + * conditionally runs the command. + * Selected options or negative -> getsockopt + */ +int +do_cmd(int optname, void *optval, uintptr_t optlen) +{ + int i; + + if (co.test_only) + return 0; + + if (ipfw_socket == -1) + ipfw_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (ipfw_socket < 0) + err(EX_UNAVAILABLE, "socket"); + + if (optname == IP_FW_GET || optname == IP_DUMMYNET_GET || + optname == IP_FW_ADD || optname == IP_FW3 || + optname == IP_FW_NAT_GET_CONFIG || + optname < 0 || + optname == IP_FW_NAT_GET_LOG) { + if (optname < 0) + optname = -optname; + i = getsockopt(ipfw_socket, IPPROTO_IP, optname, optval, + (socklen_t *)optlen); + } else { + i = setsockopt(ipfw_socket, IPPROTO_IP, optname, optval, optlen); + } + return i; +} + +/* + * do_set3 - pass ipfw control cmd to kernel + * @optname: option name + * @optval: pointer to option data + * @optlen: option length + * + * Assumes op3 header is already embedded. + * Calls setsockopt() with IP_FW3 as kernel-visible opcode. + * Returns 0 on success or errno otherwise. + */ +int +do_set3(int optname, ip_fw3_opheader *op3, uintptr_t optlen) +{ + + if (co.test_only) + return (0); + + if (ipfw_socket == -1) + ipfw_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (ipfw_socket < 0) + err(EX_UNAVAILABLE, "socket"); + + op3->opcode = optname; + + return (setsockopt(ipfw_socket, IPPROTO_IP, IP_FW3, op3, optlen)); +} + +/* + * do_get3 - pass ipfw control cmd to kernel + * @optname: option name + * @optval: pointer to option data + * @optlen: pointer to option length + * + * Assumes op3 header is already embedded. + * Calls getsockopt() with IP_FW3 as kernel-visible opcode. + * Returns 0 on success or errno otherwise. + */ +int +do_get3(int optname, ip_fw3_opheader *op3, size_t *optlen) +{ + int error; + + if (co.test_only) + return (0); + + if (ipfw_socket == -1) + ipfw_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (ipfw_socket < 0) + err(EX_UNAVAILABLE, "socket"); + + op3->opcode = optname; + + error = getsockopt(ipfw_socket, IPPROTO_IP, IP_FW3, op3, + (socklen_t *)optlen); + + return (error); +} + +/** + * match_token takes a table and a string, returns the value associated + * with the string (-1 in case of failure). + */ +int +match_token(struct _s_x *table, const char *string) +{ + struct _s_x *pt; + uint i = strlen(string); + + for (pt = table ; i && pt->s != NULL ; pt++) + if (strlen(pt->s) == i && !bcmp(string, pt->s, i)) + return pt->x; + return (-1); +} + +/** + * match_token_relaxed takes a table and a string, returns the value associated + * with the string for the best match. + * + * Returns: + * value from @table for matched records + * -1 for non-matched records + * -2 if more than one records match @string. + */ +int +match_token_relaxed(struct _s_x *table, const char *string) +{ + struct _s_x *pt, *m; + int i, c; + + i = strlen(string); + c = 0; + + for (pt = table ; i != 0 && pt->s != NULL ; pt++) { + if (strncmp(pt->s, string, i) != 0) + continue; + m = pt; + c++; + } + + if (c == 1) + return (m->x); + + return (c > 0 ? -2: -1); +} + +int +get_token(struct _s_x *table, const char *string, const char *errbase) +{ + int tcmd; + + if ((tcmd = match_token_relaxed(table, string)) < 0) + errx(EX_USAGE, "%s %s %s", + (tcmd == 0) ? "invalid" : "ambiguous", errbase, string); + + return (tcmd); +} + +/** + * match_value takes a table and a value, returns the string associated + * with the value (NULL in case of failure). + */ +char const * +match_value(struct _s_x *p, int value) +{ + for (; p->s != NULL; p++) + if (p->x == value) + return p->s; + return NULL; +} + +size_t +concat_tokens(char *buf, size_t bufsize, struct _s_x *table, char *delimiter) +{ + struct _s_x *pt; + int l; + size_t sz; + + for (sz = 0, pt = table ; pt->s != NULL; pt++) { + l = snprintf(buf + sz, bufsize - sz, "%s%s", + (sz == 0) ? "" : delimiter, pt->s); + sz += l; + bufsize += l; + if (sz > bufsize) + return (bufsize); + } + + return (sz); +} + +/* + * helper function to process a set of flags and set bits in the + * appropriate masks. + */ +int +fill_flags(struct _s_x *flags, char *p, char **e, uint32_t *set, + uint32_t *clear) +{ + char *q; /* points to the separator */ + int val; + uint32_t *which; /* mask we are working on */ + + while (p && *p) { + if (*p == '!') { + p++; + which = clear; + } else + which = set; + q = strchr(p, ','); + if (q) + *q++ = '\0'; + val = match_token(flags, p); + if (val <= 0) { + if (e != NULL) + *e = p; + return (-1); + } + *which |= (uint32_t)val; + p = q; + } + return (0); +} + +void +print_flags_buffer(char *buf, size_t sz, struct _s_x *list, uint32_t set) +{ + char const *comma = ""; + int i, l; + + for (i = 0; list[i].x != 0; i++) { + if ((set & list[i].x) == 0) + continue; + + set &= ~list[i].x; + l = snprintf(buf, sz, "%s%s", comma, list[i].s); + if (l >= sz) + return; + comma = ","; + buf += l; + sz -=l; + } +} + +/* + * _substrcmp takes two strings and returns 1 if they do not match, + * and 0 if they match exactly or the first string is a sub-string + * of the second. A warning is printed to stderr in the case that the + * first string is a sub-string of the second. + * + * This function will be removed in the future through the usual + * deprecation process. + */ +int +_substrcmp(const char *str1, const char* str2) +{ + + if (strncmp(str1, str2, strlen(str1)) != 0) + return 1; + + if (strlen(str1) != strlen(str2)) + warnx("DEPRECATED: '%s' matched '%s' as a sub-string", + str1, str2); + return 0; +} + +/* + * _substrcmp2 takes three strings and returns 1 if the first two do not match, + * and 0 if they match exactly or the second string is a sub-string + * of the first. A warning is printed to stderr in the case that the + * first string does not match the third. + * + * This function exists to warn about the bizarre construction + * strncmp(str, "by", 2) which is used to allow people to use a shortcut + * for "bytes". The problem is that in addition to accepting "by", + * "byt", "byte", and "bytes", it also excepts "by_rabid_dogs" and any + * other string beginning with "by". + * + * This function will be removed in the future through the usual + * deprecation process. + */ +int +_substrcmp2(const char *str1, const char* str2, const char* str3) +{ + + if (strncmp(str1, str2, strlen(str2)) != 0) + return 1; + + if (strcmp(str1, str3) != 0) + warnx("DEPRECATED: '%s' matched '%s'", + str1, str3); + return 0; +} + +/* + * prints one port, symbolic or numeric + */ +static void +print_port(struct buf_pr *bp, int proto, uint16_t port) +{ + + if (proto == IPPROTO_ETHERTYPE) { + char const *s; + + if (co.do_resolv && (s = match_value(ether_types, port)) ) + bprintf(bp, "%s", s); + else + bprintf(bp, "0x%04x", port); + } else { + struct servent *se = NULL; + if (co.do_resolv) { + struct protoent *pe = getprotobynumber(proto); + + se = getservbyport(htons(port), pe ? pe->p_name : NULL); + } + if (se) + bprintf(bp, "%s", se->s_name); + else + bprintf(bp, "%d", port); + } +} + +static struct _s_x _port_name[] = { + {"dst-port", O_IP_DSTPORT}, + {"src-port", O_IP_SRCPORT}, + {"ipid", O_IPID}, + {"iplen", O_IPLEN}, + {"ipttl", O_IPTTL}, + {"mac-type", O_MAC_TYPE}, + {"tcpdatalen", O_TCPDATALEN}, + {"tcpwin", O_TCPWIN}, + {"tagged", O_TAGGED}, + {NULL, 0} +}; + +/* + * Print the values in a list 16-bit items of the types above. + * XXX todo: add support for mask. + */ +static void +print_newports(struct buf_pr *bp, ipfw_insn_u16 *cmd, int proto, int opcode) +{ + uint16_t *p = cmd->ports; + int i; + char const *sep; + + if (opcode != 0) { + sep = match_value(_port_name, opcode); + if (sep == NULL) + sep = "???"; + bprintf(bp, " %s", sep); + } + sep = " "; + for (i = F_LEN((ipfw_insn *)cmd) - 1; i > 0; i--, p += 2) { + bprintf(bp, "%s", sep); + print_port(bp, proto, p[0]); + if (p[0] != p[1]) { + bprintf(bp, "-"); + print_port(bp, proto, p[1]); + } + sep = ","; + } +} + +/* + * Like strtol, but also translates service names into port numbers + * for some protocols. + * In particular: + * proto == -1 disables the protocol check; + * proto == IPPROTO_ETHERTYPE looks up an internal table + * proto == matches the values there. + * Returns *end == s in case the parameter is not found. + */ +static int +strtoport(char *s, char **end, int base, int proto) +{ + char *p, *buf; + char *s1; + int i; + + *end = s; /* default - not found */ + if (*s == '\0') + return 0; /* not found */ + + if (isdigit(*s)) + return strtol(s, end, base); + + /* + * find separator. '\\' escapes the next char. + */ + for (s1 = s; *s1 && (isalnum(*s1) || *s1 == '\\') ; s1++) + if (*s1 == '\\' && s1[1] != '\0') + s1++; + + buf = safe_calloc(s1 - s + 1, 1); + + /* + * copy into a buffer skipping backslashes + */ + for (p = s, i = 0; p != s1 ; p++) + if (*p != '\\') + buf[i++] = *p; + buf[i++] = '\0'; + + if (proto == IPPROTO_ETHERTYPE) { + i = match_token(ether_types, buf); + free(buf); + if (i != -1) { /* found */ + *end = s1; + return i; + } + } else { + struct protoent *pe = NULL; + struct servent *se; + + if (proto != 0) + pe = getprotobynumber(proto); + setservent(1); + se = getservbyname(buf, pe ? pe->p_name : NULL); + free(buf); + if (se != NULL) { + *end = s1; + return ntohs(se->s_port); + } + } + return 0; /* not found */ +} + +/* + * Fill the body of the command with the list of port ranges. + */ +static int +fill_newports(ipfw_insn_u16 *cmd, char *av, int proto, int cblen) +{ + uint16_t a, b, *p = cmd->ports; + int i = 0; + char *s = av; + + while (*s) { + a = strtoport(av, &s, 0, proto); + if (s == av) /* empty or invalid argument */ + return (0); + + CHECK_LENGTH(cblen, i + 2); + + switch (*s) { + case '-': /* a range */ + av = s + 1; + b = strtoport(av, &s, 0, proto); + /* Reject expressions like '1-abc' or '1-2-3'. */ + if (s == av || (*s != ',' && *s != '\0')) + return (0); + p[0] = a; + p[1] = b; + break; + case ',': /* comma separated list */ + case '\0': + p[0] = p[1] = a; + break; + default: + warnx("port list: invalid separator <%c> in <%s>", + *s, av); + return (0); + } + + i++; + p += 2; + av = s + 1; + } + if (i > 0) { + if (i + 1 > F_LEN_MASK) + errx(EX_DATAERR, "too many ports/ranges\n"); + cmd->o.len |= i + 1; /* leave F_NOT and F_OR untouched */ + } + return (i); +} + +/* + * Fill the body of the command with the list of DiffServ codepoints. + */ +static void +fill_dscp(ipfw_insn *cmd, char *av, int cblen) +{ + uint32_t *low, *high; + char *s = av, *a; + int code; + + cmd->opcode = O_DSCP; + cmd->len |= F_INSN_SIZE(ipfw_insn_u32) + 1; + + CHECK_CMDLEN; + + low = (uint32_t *)(cmd + 1); + high = low + 1; + + *low = 0; + *high = 0; + + while (s != NULL) { + a = strchr(s, ','); + + if (a != NULL) + *a++ = '\0'; + + if (isalpha(*s)) { + if ((code = match_token(f_ipdscp, s)) == -1) + errx(EX_DATAERR, "Unknown DSCP code"); + } else { + code = strtoul(s, NULL, 10); + if (code < 0 || code > 63) + errx(EX_DATAERR, "Invalid DSCP value"); + } + + if (code >= 32) + *high |= 1 << (code - 32); + else + *low |= 1 << code; + + s = a; + } +} + +static struct _s_x icmpcodes[] = { + { "net", ICMP_UNREACH_NET }, + { "host", ICMP_UNREACH_HOST }, + { "protocol", ICMP_UNREACH_PROTOCOL }, + { "port", ICMP_UNREACH_PORT }, + { "needfrag", ICMP_UNREACH_NEEDFRAG }, + { "srcfail", ICMP_UNREACH_SRCFAIL }, + { "net-unknown", ICMP_UNREACH_NET_UNKNOWN }, + { "host-unknown", ICMP_UNREACH_HOST_UNKNOWN }, + { "isolated", ICMP_UNREACH_ISOLATED }, + { "net-prohib", ICMP_UNREACH_NET_PROHIB }, + { "host-prohib", ICMP_UNREACH_HOST_PROHIB }, + { "tosnet", ICMP_UNREACH_TOSNET }, + { "toshost", ICMP_UNREACH_TOSHOST }, + { "filter-prohib", ICMP_UNREACH_FILTER_PROHIB }, + { "host-precedence", ICMP_UNREACH_HOST_PRECEDENCE }, + { "precedence-cutoff", ICMP_UNREACH_PRECEDENCE_CUTOFF }, + { NULL, 0 } +}; + +static void +fill_reject_code(u_short *codep, char *str) +{ + int val; + char *s; + + val = strtoul(str, &s, 0); + if (s == str || *s != '\0' || val >= 0x100) + val = match_token(icmpcodes, str); + if (val < 0) + errx(EX_DATAERR, "unknown ICMP unreachable code ``%s''", str); + *codep = val; + return; +} + +static void +print_reject_code(struct buf_pr *bp, uint16_t code) +{ + char const *s; + + if ((s = match_value(icmpcodes, code)) != NULL) + bprintf(bp, "unreach %s", s); + else + bprintf(bp, "unreach %u", code); +} + +/* + * Returns the number of bits set (from left) in a contiguous bitmask, + * or -1 if the mask is not contiguous. + * XXX this needs a proper fix. + * This effectively works on masks in big-endian (network) format. + * when compiled on little endian architectures. + * + * First bit is bit 7 of the first byte -- note, for MAC addresses, + * the first bit on the wire is bit 0 of the first byte. + * len is the max length in bits. + */ +int +contigmask(uint8_t *p, int len) +{ + int i, n; + + for (i=0; iarg1 & 0xff; + uint8_t clear = (cmd->arg1 >> 8) & 0xff; + + if (list == f_tcpflags && set == TH_SYN && clear == TH_ACK) { + bprintf(bp, " setup"); + return; + } + + bprintf(bp, " %s ", name); + for (i=0; list[i].x != 0; i++) { + if (set & list[i].x) { + set &= ~list[i].x; + bprintf(bp, "%s%s", comma, list[i].s); + comma = ","; + } + if (clear & list[i].x) { + clear &= ~list[i].x; + bprintf(bp, "%s!%s", comma, list[i].s); + comma = ","; + } + } +} + + +/* + * Print the ip address contained in a command. + */ +static void +print_ip(struct buf_pr *bp, struct format_opts *fo, ipfw_insn_ip *cmd, + char const *s) +{ + struct hostent *he = NULL; + struct in_addr *ia; + uint32_t len = F_LEN((ipfw_insn *)cmd); + uint32_t *a = ((ipfw_insn_u32 *)cmd)->d; + char *t; + + if (cmd->o.opcode == O_IP_DST_LOOKUP && len > F_INSN_SIZE(ipfw_insn_u32)) { + uint32_t d = a[1]; + const char *arg = ""; + + if (d < sizeof(lookup_key)/sizeof(lookup_key[0])) + arg = match_value(rule_options, lookup_key[d]); + t = table_search_ctlv(fo->tstate, ((ipfw_insn *)cmd)->arg1); + bprintf(bp, "%s lookup %s %s", cmd->o.len & F_NOT ? " not": "", + arg, t); + return; + } + bprintf(bp, "%s%s ", cmd->o.len & F_NOT ? " not": "", s); + + if (cmd->o.opcode == O_IP_SRC_ME || cmd->o.opcode == O_IP_DST_ME) { + bprintf(bp, "me"); + return; + } + if (cmd->o.opcode == O_IP_SRC_LOOKUP || + cmd->o.opcode == O_IP_DST_LOOKUP) { + t = table_search_ctlv(fo->tstate, ((ipfw_insn *)cmd)->arg1); + bprintf(bp, "table(%s", t); + if (len == F_INSN_SIZE(ipfw_insn_u32)) + bprintf(bp, ",%u", *a); + bprintf(bp, ")"); + return; + } + if (cmd->o.opcode == O_IP_SRC_SET || cmd->o.opcode == O_IP_DST_SET) { + uint32_t x, *map = (uint32_t *)&(cmd->mask); + int i, j; + char comma = '{'; + + x = cmd->o.arg1 - 1; + x = htonl( ~x ); + cmd->addr.s_addr = htonl(cmd->addr.s_addr); + bprintf(bp, "%s/%d", inet_ntoa(cmd->addr), + contigmask((uint8_t *)&x, 32)); + x = cmd->addr.s_addr = htonl(cmd->addr.s_addr); + x &= 0xff; /* base */ + /* + * Print bits and ranges. + * Locate first bit set (i), then locate first bit unset (j). + * If we have 3+ consecutive bits set, then print them as a + * range, otherwise only print the initial bit and rescan. + */ + for (i=0; i < cmd->o.arg1; i++) + if (map[i/32] & (1<<(i & 31))) { + for (j=i+1; j < cmd->o.arg1; j++) + if (!(map[ j/32] & (1<<(j & 31)))) + break; + bprintf(bp, "%c%d", comma, i+x); + if (j>i+2) { /* range has at least 3 elements */ + bprintf(bp, "-%d", j-1+x); + i = j-1; + } + comma = ','; + } + bprintf(bp, "}"); + return; + } + /* + * len == 2 indicates a single IP, whereas lists of 1 or more + * addr/mask pairs have len = (2n+1). We convert len to n so we + * use that to count the number of entries. + */ + for (len = len / 2; len > 0; len--, a += 2) { + int mb = /* mask length */ + (cmd->o.opcode == O_IP_SRC || cmd->o.opcode == O_IP_DST) ? + 32 : contigmask((uint8_t *)&(a[1]), 32); + if (mb == 32 && co.do_resolv) + he = gethostbyaddr((char *)&(a[0]), sizeof(u_long), AF_INET); + if (he != NULL) /* resolved to name */ + bprintf(bp, "%s", he->h_name); + else if (mb == 0) /* any */ + bprintf(bp, "any"); + else { /* numeric IP followed by some kind of mask */ + ia = (struct in_addr *)&a[0]; + bprintf(bp, "%s", inet_ntoa(*ia)); + if (mb < 0) { + ia = (struct in_addr *)&a[1]; + bprintf(bp, ":%s", inet_ntoa(*ia)); + } else if (mb < 32) + bprintf(bp, "/%d", mb); + } + if (len > 1) + bprintf(bp, ","); + } +} + +/* + * prints a MAC address/mask pair + */ +static void +print_mac(struct buf_pr *bp, uint8_t *addr, uint8_t *mask) +{ + int l = contigmask(mask, 48); + + if (l == 0) + bprintf(bp, " any"); + else { + bprintf(bp, " %02x:%02x:%02x:%02x:%02x:%02x", + addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + if (l == -1) + bprintf(bp, "&%02x:%02x:%02x:%02x:%02x:%02x", + mask[0], mask[1], mask[2], + mask[3], mask[4], mask[5]); + else if (l < 48) + bprintf(bp, "/%d", l); + } +} + +static void +fill_icmptypes(ipfw_insn_u32 *cmd, char *av) +{ + uint8_t type; + + cmd->d[0] = 0; + while (*av) { + if (*av == ',') + av++; + + type = strtoul(av, &av, 0); + + if (*av != ',' && *av != '\0') + errx(EX_DATAERR, "invalid ICMP type"); + + if (type > 31) + errx(EX_DATAERR, "ICMP type out of range"); + + cmd->d[0] |= 1 << type; + } + cmd->o.opcode = O_ICMPTYPE; + cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32); +} + +static void +print_icmptypes(struct buf_pr *bp, ipfw_insn_u32 *cmd) +{ + int i; + char sep= ' '; + + bprintf(bp, " icmptypes"); + for (i = 0; i < 32; i++) { + if ( (cmd->d[0] & (1 << (i))) == 0) + continue; + bprintf(bp, "%c%d", sep, i); + sep = ','; + } +} + +static void +print_dscp(struct buf_pr *bp, ipfw_insn_u32 *cmd) +{ + int i = 0; + uint32_t *v; + char sep= ' '; + const char *code; + + bprintf(bp, " dscp"); + v = cmd->d; + while (i < 64) { + if (*v & (1 << i)) { + if ((code = match_value(f_ipdscp, i)) != NULL) + bprintf(bp, "%c%s", sep, code); + else + bprintf(bp, "%c%d", sep, i); + sep = ','; + } + + if ((++i % 32) == 0) + v++; + } +} + +/* + * show_ipfw() prints the body of an ipfw rule. + * Because the standard rule has at least proto src_ip dst_ip, we use + * a helper function to produce these entries if not provided explicitly. + * The first argument is the list of fields we have, the second is + * the list of fields we want to be printed. + * + * Special cases if we have provided a MAC header: + * + if the rule does not contain IP addresses/ports, do not print them; + * + if the rule does not contain an IP proto, print "all" instead of "ip"; + * + * Once we have 'have_options', IP header fields are printed as options. + */ +#define HAVE_PROTO 0x0001 +#define HAVE_SRCIP 0x0002 +#define HAVE_DSTIP 0x0004 +#define HAVE_PROTO4 0x0008 +#define HAVE_PROTO6 0x0010 +#define HAVE_IP 0x0100 +#define HAVE_OPTIONS 0x8000 + +static void +show_prerequisites(struct buf_pr *bp, int *flags, int want, int cmd) +{ + (void)cmd; /* UNUSED */ + if (co.comment_only) + return; + if ( (*flags & HAVE_IP) == HAVE_IP) + *flags |= HAVE_OPTIONS; + + if ( !(*flags & HAVE_OPTIONS)) { + if ( !(*flags & HAVE_PROTO) && (want & HAVE_PROTO)) { + if ( (*flags & HAVE_PROTO4)) + bprintf(bp, " ip4"); + else if ( (*flags & HAVE_PROTO6)) + bprintf(bp, " ip6"); + else + bprintf(bp, " ip"); + } + if ( !(*flags & HAVE_SRCIP) && (want & HAVE_SRCIP)) + bprintf(bp, " from any"); + if ( !(*flags & HAVE_DSTIP) && (want & HAVE_DSTIP)) + bprintf(bp, " to any"); + } + *flags |= want; +} + +static void +show_static_rule(struct cmdline_opts *co, struct format_opts *fo, + struct buf_pr *bp, struct ip_fw_rule *rule, struct ip_fw_bcounter *cntr) +{ + static int twidth = 0; + int l; + ipfw_insn *cmd, *has_eaction = NULL, *tagptr = NULL; + const char *comment = NULL; /* ptr to comment if we have one */ + int proto = 0; /* default */ + int flags = 0; /* prerequisites */ + ipfw_insn_log *logptr = NULL; /* set if we find an O_LOG */ +#ifndef NO_ALTQ + ipfw_insn_altq *altqptr = NULL; /* set if we find an O_ALTQ */ +#endif + int or_block = 0; /* we are in an or block */ + uint32_t uval; + + if ((fo->set_mask & (1 << rule->set)) == 0) { + /* disabled mask */ + if (!co->show_sets) + return; + else + bprintf(bp, "# DISABLED "); + } + bprintf(bp, "%05u ", rule->rulenum); + + /* Print counters if enabled */ + if (fo->pcwidth > 0 || fo->bcwidth > 0) { + pr_u64(bp, &cntr->pcnt, fo->pcwidth); + pr_u64(bp, &cntr->bcnt, fo->bcwidth); + } + + if (co->do_time == 2) + bprintf(bp, "%10u ", cntr->timestamp); + else if (co->do_time == 1) { + char timestr[30]; + time_t t = (time_t)0; + + if (twidth == 0) { + strcpy(timestr, ctime(&t)); + *strchr(timestr, '\n') = '\0'; + twidth = strlen(timestr); + } + if (cntr->timestamp > 0) { + t = _long_to_time(cntr->timestamp); + + strcpy(timestr, ctime(&t)); + *strchr(timestr, '\n') = '\0'; + bprintf(bp, "%s ", timestr); + } else { + bprintf(bp, "%*s", twidth, " "); + } + } + + if (co->show_sets) + bprintf(bp, "set %d ", rule->set); + + /* + * print the optional "match probability" + */ + if (rule->cmd_len > 0) { + cmd = rule->cmd ; + if (cmd->opcode == O_PROB) { + ipfw_insn_u32 *p = (ipfw_insn_u32 *)cmd; + double d = 1.0 * p->d[0]; + + d = (d / 0x7fffffff); + bprintf(bp, "prob %f ", d); + } + } + + /* + * first print actions + */ + for (l = rule->cmd_len - rule->act_ofs, cmd = ACTION_PTR(rule); + l > 0 ; l -= F_LEN(cmd), cmd += F_LEN(cmd)) { + switch(cmd->opcode) { + case O_CHECK_STATE: + bprintf(bp, "check-state"); + /* avoid printing anything else */ + flags = HAVE_PROTO | HAVE_SRCIP | + HAVE_DSTIP | HAVE_IP; + break; + + case O_ACCEPT: + bprintf(bp, "allow"); + break; + + case O_COUNT: + bprintf(bp, "count"); + break; + + case O_DENY: + bprintf(bp, "deny"); + break; + + case O_REJECT: + if (cmd->arg1 == ICMP_REJECT_RST) + bprintf(bp, "reset"); + else if (cmd->arg1 == ICMP_UNREACH_HOST) + bprintf(bp, "reject"); + else + print_reject_code(bp, cmd->arg1); + break; + + case O_UNREACH6: + if (cmd->arg1 == ICMP6_UNREACH_RST) + bprintf(bp, "reset6"); + else + print_unreach6_code(bp, cmd->arg1); + break; + + case O_SKIPTO: + bprint_uint_arg(bp, "skipto ", cmd->arg1); + break; + + case O_PIPE: + bprint_uint_arg(bp, "pipe ", cmd->arg1); + break; + + case O_QUEUE: + bprint_uint_arg(bp, "queue ", cmd->arg1); + break; + + case O_DIVERT: + bprint_uint_arg(bp, "divert ", cmd->arg1); + break; + + case O_TEE: + bprint_uint_arg(bp, "tee ", cmd->arg1); + break; + + case O_NETGRAPH: + bprint_uint_arg(bp, "netgraph ", cmd->arg1); + break; + + case O_NGTEE: + bprint_uint_arg(bp, "ngtee ", cmd->arg1); + break; + + case O_FORWARD_IP: + { + ipfw_insn_sa *s = (ipfw_insn_sa *)cmd; + + if (s->sa.sin_addr.s_addr == INADDR_ANY) { + bprintf(bp, "fwd tablearg"); + } else { + bprintf(bp, "fwd %s",inet_ntoa(s->sa.sin_addr)); + } + if (s->sa.sin_port) + bprintf(bp, ",%d", s->sa.sin_port); + } + break; + + case O_FORWARD_IP6: + { + char buf[INET6_ADDRSTRLEN + IF_NAMESIZE + 2]; + ipfw_insn_sa6 *s = (ipfw_insn_sa6 *)cmd; + + bprintf(bp, "fwd "); + if (getnameinfo((const struct sockaddr *)&s->sa, + sizeof(struct sockaddr_in6), buf, sizeof(buf), + NULL, 0, NI_NUMERICHOST) == 0) + bprintf(bp, "%s", buf); + if (s->sa.sin6_port) + bprintf(bp, ",%d", s->sa.sin6_port); + } + break; + + case O_LOG: /* O_LOG is printed last */ + logptr = (ipfw_insn_log *)cmd; + break; + +#ifndef NO_ALTQ + case O_ALTQ: /* O_ALTQ is printed after O_LOG */ + altqptr = (ipfw_insn_altq *)cmd; + break; +#endif + + case O_TAG: + tagptr = cmd; + break; + + case O_NAT: + if (cmd->arg1 != IP_FW_NAT44_GLOBAL) + bprint_uint_arg(bp, "nat ", cmd->arg1); + else + bprintf(bp, "nat global"); + break; + + case O_SETFIB: + if (cmd->arg1 == IP_FW_TARG) + bprint_uint_arg(bp, "setfib ", cmd->arg1); + else + bprintf(bp, "setfib %u", cmd->arg1 & 0x7FFF); + break; + + case O_EXTERNAL_ACTION: { + const char *ename; + + /* + * The external action can consists of two following + * each other opcodes - O_EXTERNAL_ACTION and + * O_EXTERNAL_INSTANCE. The first contains the ID of + * name of external action. The second contains the ID + * of name of external action instance. + * NOTE: in case when external action has no named + * instances support, the second opcode isn't needed. + */ + has_eaction = cmd; + ename = object_search_ctlv(fo->tstate, cmd->arg1, + IPFW_TLV_EACTION); + if (match_token(rule_eactions, ename) != -1) + bprintf(bp, "%s", ename); + else + bprintf(bp, "eaction %s", ename); + break; + } + + case O_EXTERNAL_INSTANCE: { + const char *ename; + + if (has_eaction == NULL) + break; + /* + * XXX: we need to teach ipfw(9) to rewrite opcodes + * in the user buffer on rule addition. When we add + * the rule, we specify zero TLV type for + * O_EXTERNAL_INSTANCE object. To show correct + * rule after `ipfw add` we need to search instance + * name with zero type. But when we do `ipfw show` + * we calculate TLV type using IPFW_TLV_EACTION_NAME() + * macro. + */ + ename = object_search_ctlv(fo->tstate, cmd->arg1, 0); + if (ename == NULL) + ename = object_search_ctlv(fo->tstate, + cmd->arg1, + IPFW_TLV_EACTION_NAME(has_eaction->arg1)); + bprintf(bp, " %s", ename); + break; + } + + case O_SETDSCP: + { + const char *code; + + if (cmd->arg1 == IP_FW_TARG) { + bprint_uint_arg(bp, "setdscp ", cmd->arg1); + break; + } + uval = cmd->arg1 & 0x3F; + if ((code = match_value(f_ipdscp, uval)) != NULL) + bprintf(bp, "setdscp %s", code); + else + bprint_uint_arg(bp, "setdscp ", uval); + } + break; + + case O_REASS: + bprintf(bp, "reass"); + break; + + case O_CALLRETURN: + if (cmd->len & F_NOT) + bprintf(bp, "return"); + else + bprint_uint_arg(bp, "call ", cmd->arg1); + break; + + default: + bprintf(bp, "** unrecognized action %d len %d ", + cmd->opcode, cmd->len); + } + } + if (logptr) { + if (logptr->max_log > 0) + bprintf(bp, " log logamount %d", logptr->max_log); + else + bprintf(bp, " log"); + } +#ifndef NO_ALTQ + if (altqptr) { + print_altq_cmd(bp, altqptr); + } +#endif + if (tagptr) { + if (tagptr->len & F_NOT) + bprint_uint_arg(bp, " untag ", tagptr->arg1); + else + bprint_uint_arg(bp, " tag ", tagptr->arg1); + } + + /* + * then print the body. + */ + for (l = rule->act_ofs, cmd = rule->cmd; + l > 0 ; l -= F_LEN(cmd) , cmd += F_LEN(cmd)) { + if ((cmd->len & F_OR) || (cmd->len & F_NOT)) + continue; + if (cmd->opcode == O_IP4) { + flags |= HAVE_PROTO4; + break; + } else if (cmd->opcode == O_IP6) { + flags |= HAVE_PROTO6; + break; + } + } + if (rule->flags & IPFW_RULE_NOOPT) { /* empty rules before options */ + if (!co->do_compact) { + show_prerequisites(bp, &flags, HAVE_PROTO, 0); + bprintf(bp, " from any to any"); + } + flags |= HAVE_IP | HAVE_OPTIONS | HAVE_PROTO | + HAVE_SRCIP | HAVE_DSTIP; + } + + if (co->comment_only) + comment = "..."; + + for (l = rule->act_ofs, cmd = rule->cmd; + l > 0 ; l -= F_LEN(cmd) , cmd += F_LEN(cmd)) { + /* useful alias */ + ipfw_insn_u32 *cmd32 = (ipfw_insn_u32 *)cmd; + + if (co->comment_only) { + if (cmd->opcode != O_NOP) + continue; + bprintf(bp, " // %s\n", (char *)(cmd + 1)); + return; + } + + show_prerequisites(bp, &flags, 0, cmd->opcode); + + switch(cmd->opcode) { + case O_PROB: + break; /* done already */ + + case O_PROBE_STATE: + break; /* no need to print anything here */ + + case O_IP_SRC: + case O_IP_SRC_LOOKUP: + case O_IP_SRC_MASK: + case O_IP_SRC_ME: + case O_IP_SRC_SET: + show_prerequisites(bp, &flags, HAVE_PROTO, 0); + if (!(flags & HAVE_SRCIP)) + bprintf(bp, " from"); + if ((cmd->len & F_OR) && !or_block) + bprintf(bp, " {"); + print_ip(bp, fo, (ipfw_insn_ip *)cmd, + (flags & HAVE_OPTIONS) ? " src-ip" : ""); + flags |= HAVE_SRCIP; + break; + + case O_IP_DST: + case O_IP_DST_LOOKUP: + case O_IP_DST_MASK: + case O_IP_DST_ME: + case O_IP_DST_SET: + show_prerequisites(bp, &flags, HAVE_PROTO|HAVE_SRCIP, 0); + if (!(flags & HAVE_DSTIP)) + bprintf(bp, " to"); + if ((cmd->len & F_OR) && !or_block) + bprintf(bp, " {"); + print_ip(bp, fo, (ipfw_insn_ip *)cmd, + (flags & HAVE_OPTIONS) ? " dst-ip" : ""); + flags |= HAVE_DSTIP; + break; + + case O_IP6_SRC: + case O_IP6_SRC_MASK: + case O_IP6_SRC_ME: + show_prerequisites(bp, &flags, HAVE_PROTO, 0); + if (!(flags & HAVE_SRCIP)) + bprintf(bp, " from"); + if ((cmd->len & F_OR) && !or_block) + bprintf(bp, " {"); + print_ip6(bp, (ipfw_insn_ip6 *)cmd, + (flags & HAVE_OPTIONS) ? " src-ip6" : ""); + flags |= HAVE_SRCIP | HAVE_PROTO; + break; + + case O_IP6_DST: + case O_IP6_DST_MASK: + case O_IP6_DST_ME: + show_prerequisites(bp, &flags, HAVE_PROTO|HAVE_SRCIP, 0); + if (!(flags & HAVE_DSTIP)) + bprintf(bp, " to"); + if ((cmd->len & F_OR) && !or_block) + bprintf(bp, " {"); + print_ip6(bp, (ipfw_insn_ip6 *)cmd, + (flags & HAVE_OPTIONS) ? " dst-ip6" : ""); + flags |= HAVE_DSTIP; + break; + + case O_FLOW6ID: + print_flow6id(bp, (ipfw_insn_u32 *) cmd ); + flags |= HAVE_OPTIONS; + break; + + case O_IP_DSTPORT: + show_prerequisites(bp, &flags, + HAVE_PROTO | HAVE_SRCIP | + HAVE_DSTIP | HAVE_IP, 0); + case O_IP_SRCPORT: + if (flags & HAVE_DSTIP) + flags |= HAVE_IP; + show_prerequisites(bp, &flags, + HAVE_PROTO | HAVE_SRCIP, 0); + if ((cmd->len & F_OR) && !or_block) + bprintf(bp, " {"); + if (cmd->len & F_NOT) + bprintf(bp, " not"); + print_newports(bp, (ipfw_insn_u16 *)cmd, proto, + (flags & HAVE_OPTIONS) ? cmd->opcode : 0); + break; + + case O_PROTO: { + struct protoent *pe = NULL; + + if ((cmd->len & F_OR) && !or_block) + bprintf(bp, " {"); + if (cmd->len & F_NOT) + bprintf(bp, " not"); + proto = cmd->arg1; + pe = getprotobynumber(cmd->arg1); + if ((flags & (HAVE_PROTO4 | HAVE_PROTO6)) && + !(flags & HAVE_PROTO)) + show_prerequisites(bp, &flags, + HAVE_PROTO | HAVE_IP | HAVE_SRCIP | + HAVE_DSTIP | HAVE_OPTIONS, 0); + if (flags & HAVE_OPTIONS) + bprintf(bp, " proto"); + if (pe) + bprintf(bp, " %s", pe->p_name); + else + bprintf(bp, " %u", cmd->arg1); + } + flags |= HAVE_PROTO; + break; + + default: /*options ... */ + if (!(cmd->len & (F_OR|F_NOT))) + if (((cmd->opcode == O_IP6) && + (flags & HAVE_PROTO6)) || + ((cmd->opcode == O_IP4) && + (flags & HAVE_PROTO4))) + break; + show_prerequisites(bp, &flags, HAVE_PROTO | HAVE_SRCIP | + HAVE_DSTIP | HAVE_IP | HAVE_OPTIONS, 0); + if ((cmd->len & F_OR) && !or_block) + bprintf(bp, " {"); + if (cmd->len & F_NOT && cmd->opcode != O_IN) + bprintf(bp, " not"); + switch(cmd->opcode) { + case O_MACADDR2: { + ipfw_insn_mac *m = (ipfw_insn_mac *)cmd; + + bprintf(bp, " MAC"); + print_mac(bp, m->addr, m->mask); + print_mac(bp, m->addr + 6, m->mask + 6); + } + break; + + case O_MAC_TYPE: + print_newports(bp, (ipfw_insn_u16 *)cmd, + IPPROTO_ETHERTYPE, cmd->opcode); + break; + + + case O_FRAG: + bprintf(bp, " frag"); + break; + + case O_FIB: + bprintf(bp, " fib %u", cmd->arg1 ); + break; + case O_SOCKARG: + bprintf(bp, " sockarg"); + break; + + case O_IN: + bprintf(bp, cmd->len & F_NOT ? " out" : " in"); + break; + + case O_DIVERTED: + switch (cmd->arg1) { + case 3: + bprintf(bp, " diverted"); + break; + case 1: + bprintf(bp, " diverted-loopback"); + break; + case 2: + bprintf(bp, " diverted-output"); + break; + default: + bprintf(bp, " diverted-?<%u>", cmd->arg1); + break; + } + break; + + case O_LAYER2: + bprintf(bp, " layer2"); + break; + case O_XMIT: + case O_RECV: + case O_VIA: + { + char const *s, *t; + ipfw_insn_if *cmdif = (ipfw_insn_if *)cmd; + + if (cmd->opcode == O_XMIT) + s = "xmit"; + else if (cmd->opcode == O_RECV) + s = "recv"; + else /* if (cmd->opcode == O_VIA) */ + s = "via"; + if (cmdif->name[0] == '\0') + bprintf(bp, " %s %s", s, + inet_ntoa(cmdif->p.ip)); + else if (cmdif->name[0] == '\1') { + /* interface table */ + t = table_search_ctlv(fo->tstate, + cmdif->p.kidx); + bprintf(bp, " %s table(%s)", s, t); + } else + bprintf(bp, " %s %s", s, cmdif->name); + + break; + } + case O_IP_FLOW_LOOKUP: + { + char *t; + + t = table_search_ctlv(fo->tstate, cmd->arg1); + bprintf(bp, " flow table(%s", t); + if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn_u32)) + bprintf(bp, ",%u", + ((ipfw_insn_u32 *)cmd)->d[0]); + bprintf(bp, ")"); + break; + } + case O_IPID: + if (F_LEN(cmd) == 1) + bprintf(bp, " ipid %u", cmd->arg1 ); + else + print_newports(bp, (ipfw_insn_u16 *)cmd, 0, + O_IPID); + break; + + case O_IPTTL: + if (F_LEN(cmd) == 1) + bprintf(bp, " ipttl %u", cmd->arg1 ); + else + print_newports(bp, (ipfw_insn_u16 *)cmd, 0, + O_IPTTL); + break; + + case O_IPVER: + bprintf(bp, " ipver %u", cmd->arg1 ); + break; + + case O_IPPRECEDENCE: + bprintf(bp, " ipprecedence %u", cmd->arg1 >> 5); + break; + + case O_DSCP: + print_dscp(bp, (ipfw_insn_u32 *)cmd); + break; + + case O_IPLEN: + if (F_LEN(cmd) == 1) + bprintf(bp, " iplen %u", cmd->arg1 ); + else + print_newports(bp, (ipfw_insn_u16 *)cmd, 0, + O_IPLEN); + break; + + case O_IPOPT: + print_flags(bp, "ipoptions", cmd, f_ipopts); + break; + + case O_IPTOS: + print_flags(bp, "iptos", cmd, f_iptos); + break; + + case O_ICMPTYPE: + print_icmptypes(bp, (ipfw_insn_u32 *)cmd); + break; + + case O_ESTAB: + bprintf(bp, " established"); + break; + + case O_TCPDATALEN: + if (F_LEN(cmd) == 1) + bprintf(bp, " tcpdatalen %u", cmd->arg1 ); + else + print_newports(bp, (ipfw_insn_u16 *)cmd, 0, + O_TCPDATALEN); + break; + + case O_TCPFLAGS: + print_flags(bp, "tcpflags", cmd, f_tcpflags); + break; + + case O_TCPOPTS: + print_flags(bp, "tcpoptions", cmd, f_tcpopts); + break; + + case O_TCPWIN: + if (F_LEN(cmd) == 1) + bprintf(bp, " tcpwin %u", cmd->arg1); + else + print_newports(bp, (ipfw_insn_u16 *)cmd, 0, + O_TCPWIN); + break; + + case O_TCPACK: + bprintf(bp, " tcpack %d", ntohl(cmd32->d[0])); + break; + + case O_TCPSEQ: + bprintf(bp, " tcpseq %d", ntohl(cmd32->d[0])); + break; + + case O_UID: + { + struct passwd *pwd = getpwuid(cmd32->d[0]); + + if (pwd) + bprintf(bp, " uid %s", pwd->pw_name); + else + bprintf(bp, " uid %u", cmd32->d[0]); + } + break; + + case O_GID: + { + struct group *grp = getgrgid(cmd32->d[0]); + + if (grp) + bprintf(bp, " gid %s", grp->gr_name); + else + bprintf(bp, " gid %u", cmd32->d[0]); + } + break; + + case O_JAIL: + bprintf(bp, " jail %d", cmd32->d[0]); + break; + + case O_VERREVPATH: + bprintf(bp, " verrevpath"); + break; + + case O_VERSRCREACH: + bprintf(bp, " versrcreach"); + break; + + case O_ANTISPOOF: + bprintf(bp, " antispoof"); + break; + + case O_IPSEC: + bprintf(bp, " ipsec"); + break; + + case O_NOP: + comment = (char *)(cmd + 1); + break; + + case O_KEEP_STATE: + bprintf(bp, " keep-state"); + break; + + case O_LIMIT: { + struct _s_x *p = limit_masks; + ipfw_insn_limit *c = (ipfw_insn_limit *)cmd; + uint8_t x = c->limit_mask; + char const *comma = " "; + + bprintf(bp, " limit"); + for (; p->x != 0 ; p++) + if ((x & p->x) == p->x) { + x &= ~p->x; + bprintf(bp, "%s%s", comma,p->s); + comma = ","; + } + bprint_uint_arg(bp, " ", c->conn_limit); + break; + } + + case O_IP6: + bprintf(bp, " ip6"); + break; + + case O_IP4: + bprintf(bp, " ip4"); + break; + + case O_ICMP6TYPE: + print_icmp6types(bp, (ipfw_insn_u32 *)cmd); + break; + + case O_EXT_HDR: + print_ext6hdr(bp, (ipfw_insn *)cmd); + break; + + case O_TAGGED: + if (F_LEN(cmd) == 1) + bprint_uint_arg(bp, " tagged ", + cmd->arg1); + else + print_newports(bp, (ipfw_insn_u16 *)cmd, + 0, O_TAGGED); + break; + + default: + bprintf(bp, " [opcode %d len %d]", + cmd->opcode, cmd->len); + } + } + if (cmd->len & F_OR) { + bprintf(bp, " or"); + or_block = 1; + } else if (or_block) { + bprintf(bp, " }"); + or_block = 0; + } + } + show_prerequisites(bp, &flags, HAVE_PROTO | HAVE_SRCIP | HAVE_DSTIP + | HAVE_IP, 0); + if (comment) + bprintf(bp, " // %s", comment); + bprintf(bp, "\n"); +} + +static void +show_dyn_state(struct cmdline_opts *co, struct format_opts *fo, + struct buf_pr *bp, ipfw_dyn_rule *d) +{ + struct protoent *pe; + struct in_addr a; + uint16_t rulenum; + char buf[INET6_ADDRSTRLEN]; + + if (!co->do_expired) { + if (!d->expire && !(d->dyn_type == O_LIMIT_PARENT)) + return; + } + bcopy(&d->rule, &rulenum, sizeof(rulenum)); + bprintf(bp, "%05d", rulenum); + if (fo->pcwidth > 0 || fo->bcwidth > 0) { + bprintf(bp, " "); + pr_u64(bp, &d->pcnt, fo->pcwidth); + pr_u64(bp, &d->bcnt, fo->bcwidth); + bprintf(bp, "(%ds)", d->expire); + } + switch (d->dyn_type) { + case O_LIMIT_PARENT: + bprintf(bp, " PARENT %d", d->count); + break; + case O_LIMIT: + bprintf(bp, " LIMIT"); + break; + case O_KEEP_STATE: /* bidir, no mask */ + bprintf(bp, " STATE"); + break; + } + + if ((pe = getprotobynumber(d->id.proto)) != NULL) + bprintf(bp, " %s", pe->p_name); + else + bprintf(bp, " proto %u", d->id.proto); + + if (d->id.addr_type == 4) { + a.s_addr = htonl(d->id.src_ip); + bprintf(bp, " %s %d", inet_ntoa(a), d->id.src_port); + + a.s_addr = htonl(d->id.dst_ip); + bprintf(bp, " <-> %s %d", inet_ntoa(a), d->id.dst_port); + } else if (d->id.addr_type == 6) { + bprintf(bp, " %s %d", inet_ntop(AF_INET6, &d->id.src_ip6, buf, + sizeof(buf)), d->id.src_port); + bprintf(bp, " <-> %s %d", inet_ntop(AF_INET6, &d->id.dst_ip6, + buf, sizeof(buf)), d->id.dst_port); + } else + bprintf(bp, " UNKNOWN <-> UNKNOWN\n"); +} + +static int +do_range_cmd(int cmd, ipfw_range_tlv *rt) +{ + ipfw_range_header rh; + size_t sz; + + memset(&rh, 0, sizeof(rh)); + memcpy(&rh.range, rt, sizeof(*rt)); + rh.range.head.length = sizeof(*rt); + rh.range.head.type = IPFW_TLV_RANGE; + sz = sizeof(rh); + + if (do_get3(cmd, &rh.opheader, &sz) != 0) + return (-1); + /* Save number of matched objects */ + rt->new_set = rh.range.new_set; + return (0); +} + +/* + * This one handles all set-related commands + * ipfw set { show | enable | disable } + * ipfw set swap X Y + * ipfw set move X to Y + * ipfw set move rule X to Y + */ +void +ipfw_sets_handler(char *av[]) +{ + uint32_t masks[2]; + int i; + uint8_t cmd, rulenum; + ipfw_range_tlv rt; + char *msg; + size_t size; + + av++; + memset(&rt, 0, sizeof(rt)); + + if (av[0] == NULL) + errx(EX_USAGE, "set needs command"); + if (_substrcmp(*av, "show") == 0) { + struct format_opts fo; + ipfw_cfg_lheader *cfg; + + memset(&fo, 0, sizeof(fo)); + if (ipfw_get_config(&co, &fo, &cfg, &size) != 0) + err(EX_OSERR, "requesting config failed"); + + for (i = 0, msg = "disable"; i < RESVD_SET; i++) + if ((cfg->set_mask & (1<set_mask != (uint32_t)-1) ? " enable" : "enable"; + for (i = 0; i < RESVD_SET; i++) + if ((cfg->set_mask & (1< RESVD_SET) + errx(EX_DATAERR, "invalid set number %s\n", av[0]); + if (!isdigit(*(av[1])) || rt.new_set > RESVD_SET) + errx(EX_DATAERR, "invalid set number %s\n", av[1]); + i = do_range_cmd(IP_FW_SET_SWAP, &rt); + } else if (_substrcmp(*av, "move") == 0) { + av++; + if (av[0] && _substrcmp(*av, "rule") == 0) { + rt.flags = IPFW_RCFLAG_RANGE; /* move rules to new set */ + cmd = IP_FW_XMOVE; + av++; + } else + cmd = IP_FW_SET_MOVE; /* Move set to new one */ + if (av[0] == NULL || av[1] == NULL || av[2] == NULL || + av[3] != NULL || _substrcmp(av[1], "to") != 0) + errx(EX_USAGE, "syntax: set move [rule] X to Y\n"); + rulenum = atoi(av[0]); + rt.new_set = atoi(av[2]); + if (cmd == IP_FW_XMOVE) { + rt.start_rule = rulenum; + rt.end_rule = rulenum; + } else + rt.set = rulenum; + rt.new_set = atoi(av[2]); + if (!isdigit(*(av[0])) || (cmd == 3 && rt.set > RESVD_SET) || + (cmd == 2 && rt.start_rule == IPFW_DEFAULT_RULE) ) + errx(EX_DATAERR, "invalid source number %s\n", av[0]); + if (!isdigit(*(av[2])) || rt.new_set > RESVD_SET) + errx(EX_DATAERR, "invalid dest. set %s\n", av[1]); + i = do_range_cmd(cmd, &rt); + if (i < 0) + err(EX_OSERR, "failed to move %s", + cmd == IP_FW_SET_MOVE ? "set": "rule"); + } else if (_substrcmp(*av, "disable") == 0 || + _substrcmp(*av, "enable") == 0 ) { + int which = _substrcmp(*av, "enable") == 0 ? 1 : 0; + + av++; + masks[0] = masks[1] = 0; + + while (av[0]) { + if (isdigit(**av)) { + i = atoi(*av); + if (i < 0 || i > RESVD_SET) + errx(EX_DATAERR, + "invalid set number %d\n", i); + masks[which] |= (1<dcnt++; + + if (fo->show_counters == 0) + return; + + if (co->use_set) { + /* skip states from another set */ + bcopy((char *)&d->rule + sizeof(uint16_t), &set, + sizeof(uint8_t)); + if (set != co->use_set - 1) + return; + } + + width = pr_u64(NULL, &d->pcnt, 0); + if (width > fo->pcwidth) + fo->pcwidth = width; + + width = pr_u64(NULL, &d->bcnt, 0); + if (width > fo->bcwidth) + fo->bcwidth = width; +} + +static int +foreach_state(struct cmdline_opts *co, struct format_opts *fo, + caddr_t base, size_t sz, state_cb dyn_bc, void *dyn_arg) +{ + int ttype; + state_cb *fptr; + void *farg; + ipfw_obj_tlv *tlv; + ipfw_obj_ctlv *ctlv; + + fptr = NULL; + ttype = 0; + + while (sz > 0) { + ctlv = (ipfw_obj_ctlv *)base; + switch (ctlv->head.type) { + case IPFW_TLV_DYNSTATE_LIST: + base += sizeof(*ctlv); + sz -= sizeof(*ctlv); + ttype = IPFW_TLV_DYN_ENT; + fptr = dyn_bc; + farg = dyn_arg; + break; + default: + return (sz); + } + + while (sz > 0) { + tlv = (ipfw_obj_tlv *)base; + if (tlv->type != ttype) + break; + + fptr(co, fo, farg, tlv + 1); + sz -= tlv->length; + base += tlv->length; + } + } + + return (sz); +} + +static void +prepare_format_opts(struct cmdline_opts *co, struct format_opts *fo, + ipfw_obj_tlv *rtlv, int rcnt, caddr_t dynbase, size_t dynsz) +{ + int bcwidth, pcwidth, width; + int n; + struct ip_fw_bcounter *cntr; + struct ip_fw_rule *r; + + bcwidth = 0; + pcwidth = 0; + if (fo->show_counters != 0) { + for (n = 0; n < rcnt; n++, + rtlv = (ipfw_obj_tlv *)((caddr_t)rtlv + rtlv->length)) { + cntr = (struct ip_fw_bcounter *)(rtlv + 1); + r = (struct ip_fw_rule *)((caddr_t)cntr + cntr->size); + /* skip rules from another set */ + if (co->use_set && r->set != co->use_set - 1) + continue; + + /* packet counter */ + width = pr_u64(NULL, &cntr->pcnt, 0); + if (width > pcwidth) + pcwidth = width; + + /* byte counter */ + width = pr_u64(NULL, &cntr->bcnt, 0); + if (width > bcwidth) + bcwidth = width; + } + } + fo->bcwidth = bcwidth; + fo->pcwidth = pcwidth; + + fo->dcnt = 0; + if (co->do_dynamic && dynsz > 0) + foreach_state(co, fo, dynbase, dynsz, prepare_format_dyn, NULL); +} + +static int +list_static_range(struct cmdline_opts *co, struct format_opts *fo, + struct buf_pr *bp, ipfw_obj_tlv *rtlv, int rcnt) +{ + int n, seen; + struct ip_fw_rule *r; + struct ip_fw_bcounter *cntr; + int c = 0; + + for (n = seen = 0; n < rcnt; n++, + rtlv = (ipfw_obj_tlv *)((caddr_t)rtlv + rtlv->length)) { + + if ((fo->show_counters | fo->show_time) != 0) { + cntr = (struct ip_fw_bcounter *)(rtlv + 1); + r = (struct ip_fw_rule *)((caddr_t)cntr + cntr->size); + } else { + cntr = NULL; + r = (struct ip_fw_rule *)(rtlv + 1); + } + if (r->rulenum > fo->last) + break; + if (co->use_set && r->set != co->use_set - 1) + continue; + if (r->rulenum >= fo->first && r->rulenum <= fo->last) { + show_static_rule(co, fo, bp, r, cntr); + printf("%s", bp->buf); + c += rtlv->length; + bp_flush(bp); + seen++; + } + } + + return (seen); +} + +static void +list_dyn_state(struct cmdline_opts *co, struct format_opts *fo, + void *_arg, void *_state) +{ + uint16_t rulenum; + uint8_t set; + ipfw_dyn_rule *d; + struct buf_pr *bp; + + d = (ipfw_dyn_rule *)_state; + bp = (struct buf_pr *)_arg; + + bcopy(&d->rule, &rulenum, sizeof(rulenum)); + if (rulenum > fo->last) + return; + if (co->use_set) { + bcopy((char *)&d->rule + sizeof(uint16_t), + &set, sizeof(uint8_t)); + if (set != co->use_set - 1) + return; + } + if (rulenum >= fo->first) { + show_dyn_state(co, fo, bp, d); + printf("%s\n", bp->buf); + bp_flush(bp); + } +} + +static int +list_dyn_range(struct cmdline_opts *co, struct format_opts *fo, + struct buf_pr *bp, caddr_t base, size_t sz) +{ + + sz = foreach_state(co, fo, base, sz, list_dyn_state, bp); + return (sz); +} + +void +ipfw_list(int ac, char *av[], int show_counters) +{ + ipfw_cfg_lheader *cfg; + struct format_opts sfo; + size_t sz; + int error; + int lac; + char **lav; + uint32_t rnum; + char *endptr; + + if (co.test_only) { + fprintf(stderr, "Testing only, list disabled\n"); + return; + } + if (co.do_pipe) { +#ifdef DUMMYNET + dummynet_list(ac, av, show_counters); +#else + fprintf(stderr, "dummynet_list not supported\n"); +#endif + return; + } + + ac--; + av++; + memset(&sfo, 0, sizeof(sfo)); + + /* Determine rule range to request */ + if (ac > 0) { + for (lac = ac, lav = av; lac != 0; lac--) { + rnum = strtoul(*lav++, &endptr, 10); + if (sfo.first == 0 || rnum < sfo.first) + sfo.first = rnum; + + if (*endptr == '-') + rnum = strtoul(endptr + 1, &endptr, 10); + if (sfo.last == 0 || rnum > sfo.last) + sfo.last = rnum; + } + } + + /* get configuraion from kernel */ + cfg = NULL; + sfo.show_counters = show_counters; + sfo.show_time = co.do_time; + sfo.flags = IPFW_CFG_GET_STATIC; + if (co.do_dynamic != 0) + sfo.flags |= IPFW_CFG_GET_STATES; + if ((sfo.show_counters | sfo.show_time) != 0) + sfo.flags |= IPFW_CFG_GET_COUNTERS; + if (ipfw_get_config(&co, &sfo, &cfg, &sz) != 0) + err(EX_OSERR, "retrieving config failed"); + + error = ipfw_show_config(&co, &sfo, cfg, sz, ac, av); + + free(cfg); + + if (error != EX_OK) + exit(error); +} + +static int +ipfw_show_config(struct cmdline_opts *co, struct format_opts *fo, + ipfw_cfg_lheader *cfg, size_t sz, int ac, char *av[]) +{ + caddr_t dynbase; + size_t dynsz; + int rcnt; + int exitval = EX_OK; + int lac; + char **lav; + char *endptr; + size_t readsz; + struct buf_pr bp; + ipfw_obj_ctlv *ctlv, *tstate; + ipfw_obj_tlv *rbase; + + /* + * Handle tablenames TLV first, if any + */ + tstate = NULL; + rbase = NULL; + dynbase = NULL; + dynsz = 0; + readsz = sizeof(*cfg); + rcnt = 0; + + fo->set_mask = cfg->set_mask; + + ctlv = (ipfw_obj_ctlv *)(cfg + 1); + + if (cfg->flags & IPFW_CFG_GET_STATIC) { + /* We've requested static rules */ + if (ctlv->head.type == IPFW_TLV_TBLNAME_LIST) { + object_sort_ctlv(ctlv); + fo->tstate = ctlv; + readsz += ctlv->head.length; + ctlv = (ipfw_obj_ctlv *)((caddr_t)ctlv + + ctlv->head.length); + } + + if (ctlv->head.type == IPFW_TLV_RULE_LIST) { + rbase = (ipfw_obj_tlv *)(ctlv + 1); + rcnt = ctlv->count; + readsz += ctlv->head.length; + ctlv = (ipfw_obj_ctlv *)((caddr_t)ctlv + + ctlv->head.length); + } + } + + if ((cfg->flags & IPFW_CFG_GET_STATES) && (readsz != sz)) { + /* We may have some dynamic states */ + dynsz = sz - readsz; + /* Skip empty header */ + if (dynsz != sizeof(ipfw_obj_ctlv)) + dynbase = (caddr_t)ctlv; + else + dynsz = 0; + } + + prepare_format_opts(co, fo, rbase, rcnt, dynbase, dynsz); + bp_alloc(&bp, 4096); + + /* if no rule numbers were specified, list all rules */ + if (ac == 0) { + fo->first = 0; + fo->last = IPFW_DEFAULT_RULE; + list_static_range(co, fo, &bp, rbase, rcnt); + + if (co->do_dynamic && dynsz > 0) { + printf("## Dynamic rules (%d %zu):\n", fo->dcnt, dynsz); + list_dyn_range(co, fo, &bp, dynbase, dynsz); + } + + bp_free(&bp); + return (EX_OK); + } + + /* display specific rules requested on command line */ + for (lac = ac, lav = av; lac != 0; lac--) { + /* convert command line rule # */ + fo->last = fo->first = strtoul(*lav++, &endptr, 10); + if (*endptr == '-') + fo->last = strtoul(endptr + 1, &endptr, 10); + if (*endptr) { + exitval = EX_USAGE; + warnx("invalid rule number: %s", *(lav - 1)); + continue; + } + + if (list_static_range(co, fo, &bp, rbase, rcnt) == 0) { + /* give precedence to other error(s) */ + if (exitval == EX_OK) + exitval = EX_UNAVAILABLE; + if (fo->first == fo->last) + warnx("rule %u does not exist", fo->first); + else + warnx("no rules in range %u-%u", + fo->first, fo->last); + } + } + + if (co->do_dynamic && dynsz > 0) { + printf("## Dynamic rules:\n"); + for (lac = ac, lav = av; lac != 0; lac--) { + fo->last = fo->first = strtoul(*lav++, &endptr, 10); + if (*endptr == '-') + fo->last = strtoul(endptr+1, &endptr, 10); + if (*endptr) + /* already warned */ + continue; + list_dyn_range(co, fo, &bp, dynbase, dynsz); + } + } + + bp_free(&bp); + return (exitval); +} + + +/* + * Retrieves current ipfw configuration of given type + * and stores its pointer to @pcfg. + * + * Caller is responsible for freeing @pcfg. + * + * Returns 0 on success. + */ + +static int +ipfw_get_config(struct cmdline_opts *co, struct format_opts *fo, + ipfw_cfg_lheader **pcfg, size_t *psize) +{ + ipfw_cfg_lheader *cfg; + size_t sz; + int i; + + + if (co->test_only != 0) { + fprintf(stderr, "Testing only, list disabled\n"); + return (0); + } + + /* Start with some data size */ + sz = 4096; + cfg = NULL; + + for (i = 0; i < 16; i++) { + if (cfg != NULL) + free(cfg); + if ((cfg = calloc(1, sz)) == NULL) + return (ENOMEM); + + cfg->flags = fo->flags; + cfg->start_rule = fo->first; + cfg->end_rule = fo->last; + + if (do_get3(IP_FW_XGET, &cfg->opheader, &sz) != 0) { + if (errno != ENOMEM) { + free(cfg); + return (errno); + } + + /* Buffer size is not enough. Try to increase */ + sz = sz * 2; + if (sz < cfg->size) + sz = cfg->size; + continue; + } + + *pcfg = cfg; + *psize = sz; + return (0); + } + + free(cfg); + return (ENOMEM); +} + +static int +lookup_host (char *host, struct in_addr *ipaddr) +{ + struct hostent *he; + + if (!inet_aton(host, ipaddr)) { +#ifndef FSTACK + if ((he = gethostbyname(host)) == NULL) + return(-1); + *ipaddr = *(struct in_addr *)he->h_addr_list[0]; +#else + return (-1); +#endif + } + return(0); +} + +struct tidx { + ipfw_obj_ntlv *idx; + uint32_t count; + uint32_t size; + uint16_t counter; + uint8_t set; +}; + +int +ipfw_check_object_name(const char *name) +{ + int c, i, l; + + /* + * Check that name is null-terminated and contains + * valid symbols only. Valid mask is: + * [a-zA-Z0-9\-_\.]{1,63} + */ + l = strlen(name); + if (l == 0 || l >= 64) + return (EINVAL); + for (i = 0; i < l; i++) { + c = name[i]; + if (isalpha(c) || isdigit(c) || c == '_' || + c == '-' || c == '.') + continue; + return (EINVAL); + } + return (0); +} + +static int +eaction_check_name(const char *name) +{ + + if (ipfw_check_object_name(name) != 0) + return (EINVAL); + /* Restrict some 'special' names */ + if (match_token(rule_actions, name) != -1 && + match_token(rule_action_params, name) != -1) + return (EINVAL); + return (0); +} + +static uint16_t +pack_object(struct tidx *tstate, char *name, int otype) +{ + int i; + ipfw_obj_ntlv *ntlv; + + for (i = 0; i < tstate->count; i++) { + if (strcmp(tstate->idx[i].name, name) != 0) + continue; + if (tstate->idx[i].set != tstate->set) + continue; + if (tstate->idx[i].head.type != otype) + continue; + + return (tstate->idx[i].idx); + } + + if (tstate->count + 1 > tstate->size) { + tstate->size += 4; + tstate->idx = realloc(tstate->idx, tstate->size * + sizeof(ipfw_obj_ntlv)); + if (tstate->idx == NULL) + return (0); + } + + ntlv = &tstate->idx[i]; + memset(ntlv, 0, sizeof(ipfw_obj_ntlv)); + strlcpy(ntlv->name, name, sizeof(ntlv->name)); + ntlv->head.type = otype; + ntlv->head.length = sizeof(ipfw_obj_ntlv); + ntlv->set = tstate->set; + ntlv->idx = ++tstate->counter; + tstate->count++; + + return (ntlv->idx); +} + +static uint16_t +pack_table(struct tidx *tstate, char *name) +{ + + if (table_check_name(name) != 0) + return (0); + + return (pack_object(tstate, name, IPFW_TLV_TBL_NAME)); +} + +static void +fill_table(ipfw_insn *cmd, char *av, uint8_t opcode, struct tidx *tstate) +{ + uint32_t *d = ((ipfw_insn_u32 *)cmd)->d; + uint16_t uidx; + char *p; + + if ((p = strchr(av + 6, ')')) == NULL) + errx(EX_DATAERR, "forgotten parenthesis: '%s'", av); + *p = '\0'; + p = strchr(av + 6, ','); + if (p) + *p++ = '\0'; + + if ((uidx = pack_table(tstate, av + 6)) == 0) + errx(EX_DATAERR, "Invalid table name: %s", av + 6); + + cmd->opcode = opcode; + cmd->arg1 = uidx; + if (p) { + cmd->len |= F_INSN_SIZE(ipfw_insn_u32); + d[0] = strtoul(p, NULL, 0); + } else + cmd->len |= F_INSN_SIZE(ipfw_insn); +} + + +/* + * fills the addr and mask fields in the instruction as appropriate from av. + * Update length as appropriate. + * The following formats are allowed: + * me returns O_IP_*_ME + * 1.2.3.4 single IP address + * 1.2.3.4:5.6.7.8 address:mask + * 1.2.3.4/24 address/mask + * 1.2.3.4/26{1,6,5,4,23} set of addresses in a subnet + * We can have multiple comma-separated address/mask entries. + */ +static void +fill_ip(ipfw_insn_ip *cmd, char *av, int cblen, struct tidx *tstate) +{ + int len = 0; + uint32_t *d = ((ipfw_insn_u32 *)cmd)->d; + + cmd->o.len &= ~F_LEN_MASK; /* zero len */ + + if (_substrcmp(av, "any") == 0) + return; + + if (_substrcmp(av, "me") == 0) { + cmd->o.len |= F_INSN_SIZE(ipfw_insn); + return; + } + + if (strncmp(av, "table(", 6) == 0) { + fill_table(&cmd->o, av, O_IP_DST_LOOKUP, tstate); + return; + } + + while (av) { + /* + * After the address we can have '/' or ':' indicating a mask, + * ',' indicating another address follows, '{' indicating a + * set of addresses of unspecified size. + */ + char *t = NULL, *p = strpbrk(av, "/:,{"); + int masklen; + char md, nd = '\0'; + + CHECK_LENGTH(cblen, F_INSN_SIZE(ipfw_insn) + 2 + len); + + if (p) { + md = *p; + *p++ = '\0'; + if ((t = strpbrk(p, ",{")) != NULL) { + nd = *t; + *t = '\0'; + } + } else + md = '\0'; + + if (lookup_host(av, (struct in_addr *)&d[0]) != 0) + errx(EX_NOHOST, "hostname ``%s'' unknown", av); + switch (md) { + case ':': + if (!inet_aton(p, (struct in_addr *)&d[1])) + errx(EX_DATAERR, "bad netmask ``%s''", p); + break; + case '/': + masklen = atoi(p); + if (masklen == 0) + d[1] = htonl(0U); /* mask */ + else if (masklen > 32) + errx(EX_DATAERR, "bad width ``%s''", p); + else + d[1] = htonl(~0U << (32 - masklen)); + break; + case '{': /* no mask, assume /24 and put back the '{' */ + d[1] = htonl(~0U << (32 - 24)); + *(--p) = md; + break; + + case ',': /* single address plus continuation */ + *(--p) = md; + /* FALLTHROUGH */ + case 0: /* initialization value */ + default: + d[1] = htonl(~0U); /* force /32 */ + break; + } + d[0] &= d[1]; /* mask base address with mask */ + if (t) + *t = nd; + /* find next separator */ + if (p) + p = strpbrk(p, ",{"); + if (p && *p == '{') { + /* + * We have a set of addresses. They are stored as follows: + * arg1 is the set size (powers of 2, 2..256) + * addr is the base address IN HOST FORMAT + * mask.. is an array of arg1 bits (rounded up to + * the next multiple of 32) with bits set + * for each host in the map. + */ + uint32_t *map = (uint32_t *)&cmd->mask; + int low, high; + int i = contigmask((uint8_t *)&(d[1]), 32); + + if (len > 0) + errx(EX_DATAERR, "address set cannot be in a list"); + if (i < 24 || i > 31) + errx(EX_DATAERR, "invalid set with mask %d\n", i); + cmd->o.arg1 = 1<<(32-i); /* map length */ + d[0] = ntohl(d[0]); /* base addr in host format */ + cmd->o.opcode = O_IP_DST_SET; /* default */ + cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32) + (cmd->o.arg1+31)/32; + for (i = 0; i < (cmd->o.arg1+31)/32 ; i++) + map[i] = 0; /* clear map */ + + av = p + 1; + low = d[0] & 0xff; + high = low + cmd->o.arg1 - 1; + /* + * Here, i stores the previous value when we specify a range + * of addresses within a mask, e.g. 45-63. i = -1 means we + * have no previous value. + */ + i = -1; /* previous value in a range */ + while (isdigit(*av)) { + char *s; + int a = strtol(av, &s, 0); + + if (s == av) { /* no parameter */ + if (*av != '}') + errx(EX_DATAERR, "set not closed\n"); + if (i != -1) + errx(EX_DATAERR, "incomplete range %d-", i); + break; + } + if (a < low || a > high) + errx(EX_DATAERR, "addr %d out of range [%d-%d]\n", + a, low, high); + a -= low; + if (i == -1) /* no previous in range */ + i = a; + else { /* check that range is valid */ + if (i > a) + errx(EX_DATAERR, "invalid range %d-%d", + i+low, a+low); + if (*s == '-') + errx(EX_DATAERR, "double '-' in range"); + } + for (; i <= a; i++) + map[i/32] |= 1<<(i & 31); + i = -1; + if (*s == '-') + i = a; + else if (*s == '}') + break; + av = s+1; + } + return; + } + av = p; + if (av) /* then *av must be a ',' */ + av++; + + /* Check this entry */ + if (d[1] == 0) { /* "any", specified as x.x.x.x/0 */ + /* + * 'any' turns the entire list into a NOP. + * 'not any' never matches, so it is removed from the + * list unless it is the only item, in which case we + * report an error. + */ + if (cmd->o.len & F_NOT) { /* "not any" never matches */ + if (av == NULL && len == 0) /* only this entry */ + errx(EX_DATAERR, "not any never matches"); + } + /* else do nothing and skip this entry */ + return; + } + /* A single IP can be stored in an optimized format */ + if (d[1] == (uint32_t)~0 && av == NULL && len == 0) { + cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32); + return; + } + len += 2; /* two words... */ + d += 2; + } /* end while */ + if (len + 1 > F_LEN_MASK) + errx(EX_DATAERR, "address list too long"); + cmd->o.len |= len+1; +} + + +/* n2mask sets n bits of the mask */ +void +n2mask(struct in6_addr *mask, int n) +{ + static int minimask[9] = + { 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff }; + u_char *p; + + memset(mask, 0, sizeof(struct in6_addr)); + p = (u_char *) mask; + for (; n > 0; p++, n -= 8) { + if (n >= 8) + *p = 0xff; + else + *p = minimask[n]; + } + return; +} + +static void +fill_flags_cmd(ipfw_insn *cmd, enum ipfw_opcodes opcode, + struct _s_x *flags, char *p) +{ + char *e; + uint32_t set = 0, clear = 0; + + if (fill_flags(flags, p, &e, &set, &clear) != 0) + errx(EX_DATAERR, "invalid flag %s", e); + + cmd->opcode = opcode; + cmd->len = (cmd->len & (F_NOT | F_OR)) | 1; + cmd->arg1 = (set & 0xff) | ( (clear & 0xff) << 8); +} + + +void +ipfw_delete(char *av[]) +{ + int i, j; + int exitval = EX_OK; + int do_set = 0; + char *sep; + ipfw_range_tlv rt; + + av++; + NEED1("missing rule specification"); + memset(&rt, 0, sizeof(rt)); + if ( *av && _substrcmp(*av, "set") == 0) { + /* Do not allow using the following syntax: + * ipfw set N delete set M + */ + if (co.use_set) + errx(EX_DATAERR, "invalid syntax"); + do_set = 1; /* delete set */ + av++; + } + + /* Rule number */ + while (*av && isdigit(**av)) { + i = strtol(*av, &sep, 10); + j = i; + if (*sep== '-') + j = strtol(sep + 1, NULL, 10); + av++; + if (co.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 (co.do_pipe) { +#ifdef DUMMYNET + exitval = ipfw_delete_pipe(co.do_pipe, i); +#else + exitval = EX_UNAVAILABLE; +#endif + } else { + if (do_set != 0) { + rt.set = i & 31; + rt.flags = IPFW_RCFLAG_SET; + } else { + rt.start_rule = i & 0xffff; + rt.end_rule = j & 0xffff; + if (rt.start_rule == 0 && rt.end_rule == 0) + rt.flags |= IPFW_RCFLAG_ALL; + else + rt.flags |= IPFW_RCFLAG_RANGE; + if (co.use_set != 0) { + rt.set = co.use_set - 1; + rt.flags |= IPFW_RCFLAG_SET; + } + } + i = do_range_cmd(IP_FW_XDEL, &rt); + if (i != 0) { + exitval = EX_UNAVAILABLE; + warn("rule %u: setsockopt(IP_FW_XDEL)", + rt.start_rule); + } else if (rt.new_set == 0) { + exitval = EX_UNAVAILABLE; + if (rt.start_rule != rt.end_rule) + warnx("no rules rules in %u-%u range", + rt.start_rule, rt.end_rule); + else + warnx("rule %u not found", + rt.start_rule); + } + } + } + if (exitval != EX_OK) + exit(exitval); +} + + +/* + * fill the interface structure. We do not check the name as we can + * create interfaces dynamically, so checking them at insert time + * makes relatively little sense. + * Interface names containing '*', '?', or '[' are assumed to be shell + * patterns which match interfaces. + */ +static void +fill_iface(ipfw_insn_if *cmd, char *arg, int cblen, struct tidx *tstate) +{ + char *p; + uint16_t uidx; + + cmd->name[0] = '\0'; + cmd->o.len |= F_INSN_SIZE(ipfw_insn_if); + + CHECK_CMDLEN; + + /* Parse the interface or address */ + if (strcmp(arg, "any") == 0) + cmd->o.len = 0; /* effectively ignore this command */ + else if (strncmp(arg, "table(", 6) == 0) { + if ((p = strchr(arg + 6, ')')) == NULL) + errx(EX_DATAERR, "forgotten parenthesis: '%s'", arg); + *p = '\0'; + p = strchr(arg + 6, ','); + if (p) + *p++ = '\0'; + if ((uidx = pack_table(tstate, arg + 6)) == 0) + errx(EX_DATAERR, "Invalid table name: %s", arg + 6); + + cmd->name[0] = '\1'; /* Special value indicating table */ + cmd->p.kidx = uidx; + } else if (!isdigit(*arg)) { + strlcpy(cmd->name, arg, sizeof(cmd->name)); + cmd->p.glob = strpbrk(arg, "*?[") != NULL ? 1 : 0; + } else if (!inet_aton(arg, &cmd->p.ip)) + errx(EX_DATAERR, "bad ip address ``%s''", arg); +} + +static void +get_mac_addr_mask(const char *p, uint8_t *addr, uint8_t *mask) +{ + int i; + size_t l; + char *ap, *ptr, *optr; + struct ether_addr *mac; + const char *macset = "0123456789abcdefABCDEF:"; + + if (strcmp(p, "any") == 0) { + for (i = 0; i < ETHER_ADDR_LEN; i++) + addr[i] = mask[i] = 0; + return; + } + + optr = ptr = strdup(p); + if ((ap = strsep(&ptr, "&/")) != NULL && *ap != 0) { + l = strlen(ap); + if (strspn(ap, macset) != l || (mac = ether_aton(ap)) == NULL) + errx(EX_DATAERR, "Incorrect MAC address"); + bcopy(mac, addr, ETHER_ADDR_LEN); + } else + errx(EX_DATAERR, "Incorrect MAC address"); + + if (ptr != NULL) { /* we have mask? */ + if (p[ptr - optr - 1] == '/') { /* mask len */ + long ml = strtol(ptr, &ap, 10); + if (*ap != 0 || ml > ETHER_ADDR_LEN * 8 || ml < 0) + errx(EX_DATAERR, "Incorrect mask length"); + for (i = 0; ml > 0 && i < ETHER_ADDR_LEN; ml -= 8, i++) + mask[i] = (ml >= 8) ? 0xff: (~0) << (8 - ml); + } else { /* mask */ + l = strlen(ptr); + if (strspn(ptr, macset) != l || + (mac = ether_aton(ptr)) == NULL) + errx(EX_DATAERR, "Incorrect mask"); + bcopy(mac, mask, ETHER_ADDR_LEN); + } + } else { /* default mask: ff:ff:ff:ff:ff:ff */ + for (i = 0; i < ETHER_ADDR_LEN; i++) + mask[i] = 0xff; + } + for (i = 0; i < ETHER_ADDR_LEN; i++) + addr[i] &= mask[i]; + + free(optr); +} + +/* + * helper function, updates the pointer to cmd with the length + * of the current command, and also cleans up the first word of + * the new command in case it has been clobbered before. + */ +static ipfw_insn * +next_cmd(ipfw_insn *cmd, int *len) +{ + *len -= F_LEN(cmd); + CHECK_LENGTH(*len, 0); + cmd += F_LEN(cmd); + bzero(cmd, sizeof(*cmd)); + return cmd; +} + +/* + * Takes arguments and copies them into a comment + */ +static void +fill_comment(ipfw_insn *cmd, char **av, int cblen) +{ + int i, l; + char *p = (char *)(cmd + 1); + + cmd->opcode = O_NOP; + cmd->len = (cmd->len & (F_NOT | F_OR)); + + /* Compute length of comment string. */ + for (i = 0, l = 0; av[i] != NULL; i++) + l += strlen(av[i]) + 1; + if (l == 0) + return; + if (l > 84) + errx(EX_DATAERR, + "comment too long (max 80 chars)"); + l = 1 + (l+3)/4; + cmd->len = (cmd->len & (F_NOT | F_OR)) | l; + CHECK_CMDLEN; + + for (i = 0; av[i] != NULL; i++) { + strcpy(p, av[i]); + p += strlen(av[i]); + *p++ = ' '; + } + *(--p) = '\0'; +} + +/* + * A function to fill simple commands of size 1. + * Existing flags are preserved. + */ +static void +fill_cmd(ipfw_insn *cmd, enum ipfw_opcodes opcode, int flags, uint16_t arg) +{ + cmd->opcode = opcode; + cmd->len = ((cmd->len | flags) & (F_NOT | F_OR)) | 1; + cmd->arg1 = arg; +} + +/* + * Fetch and add the MAC address and type, with masks. This generates one or + * two microinstructions, and returns the pointer to the last one. + */ +static ipfw_insn * +add_mac(ipfw_insn *cmd, char *av[], int cblen) +{ + ipfw_insn_mac *mac; + + if ( ( av[0] == NULL ) || ( av[1] == NULL ) ) + errx(EX_DATAERR, "MAC dst src"); + + cmd->opcode = O_MACADDR2; + cmd->len = (cmd->len & (F_NOT | F_OR)) | F_INSN_SIZE(ipfw_insn_mac); + CHECK_CMDLEN; + + mac = (ipfw_insn_mac *)cmd; + get_mac_addr_mask(av[0], mac->addr, mac->mask); /* dst */ + get_mac_addr_mask(av[1], &(mac->addr[ETHER_ADDR_LEN]), + &(mac->mask[ETHER_ADDR_LEN])); /* src */ + return cmd; +} + +static ipfw_insn * +add_mactype(ipfw_insn *cmd, char *av, int cblen) +{ + if (!av) + errx(EX_DATAERR, "missing MAC type"); + if (strcmp(av, "any") != 0) { /* we have a non-null type */ + fill_newports((ipfw_insn_u16 *)cmd, av, IPPROTO_ETHERTYPE, + cblen); + cmd->opcode = O_MAC_TYPE; + return cmd; + } else + return NULL; +} + +static ipfw_insn * +add_proto0(ipfw_insn *cmd, char *av, u_char *protop) +{ + struct protoent *pe; + char *ep; + int proto; + + proto = strtol(av, &ep, 10); + if (*ep != '\0' || proto <= 0) { + if ((pe = getprotobyname(av)) == NULL) + return NULL; + proto = pe->p_proto; + } + + fill_cmd(cmd, O_PROTO, 0, proto); + *protop = proto; + return cmd; +} + +static ipfw_insn * +add_proto(ipfw_insn *cmd, char *av, u_char *protop) +{ + u_char proto = IPPROTO_IP; + + if (_substrcmp(av, "all") == 0 || strcmp(av, "ip") == 0) + ; /* do not set O_IP4 nor O_IP6 */ + else if (strcmp(av, "ip4") == 0) + /* explicit "just IPv4" rule */ + fill_cmd(cmd, O_IP4, 0, 0); + else if (strcmp(av, "ip6") == 0) { + /* explicit "just IPv6" rule */ + proto = IPPROTO_IPV6; + fill_cmd(cmd, O_IP6, 0, 0); + } else + return add_proto0(cmd, av, protop); + + *protop = proto; + return cmd; +} + +static ipfw_insn * +add_proto_compat(ipfw_insn *cmd, char *av, u_char *protop) +{ + u_char proto = IPPROTO_IP; + + if (_substrcmp(av, "all") == 0 || strcmp(av, "ip") == 0) + ; /* do not set O_IP4 nor O_IP6 */ + else if (strcmp(av, "ipv4") == 0 || strcmp(av, "ip4") == 0) + /* explicit "just IPv4" rule */ + fill_cmd(cmd, O_IP4, 0, 0); + else if (strcmp(av, "ipv6") == 0 || strcmp(av, "ip6") == 0) { + /* explicit "just IPv6" rule */ + proto = IPPROTO_IPV6; + fill_cmd(cmd, O_IP6, 0, 0); + } else + return add_proto0(cmd, av, protop); + + *protop = proto; + return cmd; +} + +static ipfw_insn * +add_srcip(ipfw_insn *cmd, char *av, int cblen, struct tidx *tstate) +{ + fill_ip((ipfw_insn_ip *)cmd, av, cblen, tstate); + if (cmd->opcode == O_IP_DST_SET) /* set */ + cmd->opcode = O_IP_SRC_SET; + else if (cmd->opcode == O_IP_DST_LOOKUP) /* table */ + cmd->opcode = O_IP_SRC_LOOKUP; + else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn)) /* me */ + cmd->opcode = O_IP_SRC_ME; + else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn_u32)) /* one IP */ + cmd->opcode = O_IP_SRC; + else /* addr/mask */ + cmd->opcode = O_IP_SRC_MASK; + return cmd; +} + +static ipfw_insn * +add_dstip(ipfw_insn *cmd, char *av, int cblen, struct tidx *tstate) +{ + fill_ip((ipfw_insn_ip *)cmd, av, cblen, tstate); + if (cmd->opcode == O_IP_DST_SET) /* set */ + ; + else if (cmd->opcode == O_IP_DST_LOOKUP) /* table */ + ; + else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn)) /* me */ + cmd->opcode = O_IP_DST_ME; + else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn_u32)) /* one IP */ + cmd->opcode = O_IP_DST; + else /* addr/mask */ + cmd->opcode = O_IP_DST_MASK; + return cmd; +} + +static struct _s_x f_reserved_keywords[] = { + { "altq", TOK_OR }, + { "//", TOK_OR }, + { "diverted", TOK_OR }, + { "dst-port", TOK_OR }, + { "src-port", TOK_OR }, + { "established", TOK_OR }, + { "keep-state", TOK_OR }, + { "frag", TOK_OR }, + { "icmptypes", TOK_OR }, + { "in", TOK_OR }, + { "out", TOK_OR }, + { "ip6", TOK_OR }, + { "any", TOK_OR }, + { "to", TOK_OR }, + { "via", TOK_OR }, + { "{", TOK_OR }, + { NULL, 0 } /* terminator */ +}; + +static ipfw_insn * +add_ports(ipfw_insn *cmd, char *av, u_char proto, int opcode, int cblen) +{ + + if (match_token(f_reserved_keywords, av) != -1) + return (NULL); + + if (fill_newports((ipfw_insn_u16 *)cmd, av, proto, cblen)) { + /* XXX todo: check that we have a protocol with ports */ + cmd->opcode = opcode; + return cmd; + } + return NULL; +} + +static ipfw_insn * +add_src(ipfw_insn *cmd, char *av, u_char proto, int cblen, struct tidx *tstate) +{ + struct in6_addr a; + char *host, *ch, buf[INET6_ADDRSTRLEN]; + ipfw_insn *ret = NULL; + int len; + + /* Copy first address in set if needed */ + if ((ch = strpbrk(av, "/,")) != NULL) { + len = ch - av; + strlcpy(buf, av, sizeof(buf)); + if (len < sizeof(buf)) + buf[len] = '\0'; + host = buf; + } else + host = av; + + if (proto == IPPROTO_IPV6 || strcmp(av, "me6") == 0 || + inet_pton(AF_INET6, host, &a) == 1) + ret = add_srcip6(cmd, av, cblen); + /* XXX: should check for IPv4, not !IPv6 */ + if (ret == NULL && (proto == IPPROTO_IP || strcmp(av, "me") == 0 || + inet_pton(AF_INET6, host, &a) != 1)) + ret = add_srcip(cmd, av, cblen, tstate); + if (ret == NULL && strcmp(av, "any") != 0) + ret = cmd; + + return ret; +} + +static ipfw_insn * +add_dst(ipfw_insn *cmd, char *av, u_char proto, int cblen, struct tidx *tstate) +{ + struct in6_addr a; + char *host, *ch, buf[INET6_ADDRSTRLEN]; + ipfw_insn *ret = NULL; + int len; + + /* Copy first address in set if needed */ + if ((ch = strpbrk(av, "/,")) != NULL) { + len = ch - av; + strlcpy(buf, av, sizeof(buf)); + if (len < sizeof(buf)) + buf[len] = '\0'; + host = buf; + } else + host = av; + + if (proto == IPPROTO_IPV6 || strcmp(av, "me6") == 0 || + inet_pton(AF_INET6, host, &a) == 1) + ret = add_dstip6(cmd, av, cblen); + /* XXX: should check for IPv4, not !IPv6 */ + if (ret == NULL && (proto == IPPROTO_IP || strcmp(av, "me") == 0 || + inet_pton(AF_INET6, host, &a) != 1)) + ret = add_dstip(cmd, av, cblen, tstate); + if (ret == NULL && strcmp(av, "any") != 0) + ret = cmd; + + return ret; +} + +/* + * Parse arguments and assemble the microinstructions which make up a rule. + * Rules are added into the 'rulebuf' and then copied in the correct order + * into the actual rule. + * + * The syntax for a rule starts with the action, followed by + * optional action parameters, and the various match patterns. + * In the assembled microcode, the first opcode must be an O_PROBE_STATE + * (generated if the rule includes a keep-state option), then the + * various match patterns, log/altq actions, and the actual action. + * + */ +void +compile_rule(char *av[], uint32_t *rbuf, int *rbufsize, struct tidx *tstate) +{ + /* + * rules are added into the 'rulebuf' and then copied in + * the correct order into the actual rule. + * Some things that need to go out of order (prob, action etc.) + * go into actbuf[]. + */ + static uint32_t actbuf[255], cmdbuf[255]; + int rblen, ablen, cblen; + + ipfw_insn *src, *dst, *cmd, *action, *prev=NULL; + ipfw_insn *first_cmd; /* first match pattern */ + + struct ip_fw_rule *rule; + + /* + * various flags used to record that we entered some fields. + */ + ipfw_insn *have_state = NULL; /* check-state or keep-state */ + ipfw_insn *have_log = NULL, *have_altq = NULL, *have_tag = NULL; + size_t len; + + int i; + + int open_par = 0; /* open parenthesis ( */ + + /* proto is here because it is used to fetch ports */ + u_char proto = IPPROTO_IP; /* default protocol */ + + double match_prob = 1; /* match probability, default is always match */ + + bzero(actbuf, sizeof(actbuf)); /* actions go here */ + bzero(cmdbuf, sizeof(cmdbuf)); + bzero(rbuf, *rbufsize); + + rule = (struct ip_fw_rule *)rbuf; + cmd = (ipfw_insn *)cmdbuf; + action = (ipfw_insn *)actbuf; + + rblen = *rbufsize / sizeof(uint32_t); + rblen -= sizeof(struct ip_fw_rule) / sizeof(uint32_t); + ablen = sizeof(actbuf) / sizeof(actbuf[0]); + cblen = sizeof(cmdbuf) / sizeof(cmdbuf[0]); + cblen -= F_INSN_SIZE(ipfw_insn_u32) + 1; + +#define CHECK_RBUFLEN(len) { CHECK_LENGTH(rblen, len); rblen -= len; } +#define CHECK_ACTLEN CHECK_LENGTH(ablen, action->len) + + av++; + + /* [rule N] -- Rule number optional */ + if (av[0] && isdigit(**av)) { + rule->rulenum = atoi(*av); + av++; + } + + /* [set N] -- set number (0..RESVD_SET), optional */ + if (av[0] && av[1] && _substrcmp(*av, "set") == 0) { + int set = strtoul(av[1], NULL, 10); + if (set < 0 || set > RESVD_SET) + errx(EX_DATAERR, "illegal set %s", av[1]); + rule->set = set; + tstate->set = set; + av += 2; + } + + /* [prob D] -- match probability, optional */ + if (av[0] && av[1] && _substrcmp(*av, "prob") == 0) { + match_prob = strtod(av[1], NULL); + + if (match_prob <= 0 || match_prob > 1) + errx(EX_DATAERR, "illegal match prob. %s", av[1]); + av += 2; + } + + /* action -- mandatory */ + NEED1("missing action"); + i = match_token(rule_actions, *av); + av++; + action->len = 1; /* default */ + CHECK_ACTLEN; + switch(i) { + case TOK_CHECKSTATE: + have_state = action; + action->opcode = O_CHECK_STATE; + break; + + case TOK_ACCEPT: + action->opcode = O_ACCEPT; + break; + + case TOK_DENY: + action->opcode = O_DENY; + action->arg1 = 0; + break; + + case TOK_REJECT: + action->opcode = O_REJECT; + action->arg1 = ICMP_UNREACH_HOST; + break; + + case TOK_RESET: + action->opcode = O_REJECT; + action->arg1 = ICMP_REJECT_RST; + break; + + case TOK_RESET6: + action->opcode = O_UNREACH6; + action->arg1 = ICMP6_UNREACH_RST; + break; + + case TOK_UNREACH: + action->opcode = O_REJECT; + NEED1("missing reject code"); + fill_reject_code(&action->arg1, *av); + av++; + break; + + case TOK_UNREACH6: + action->opcode = O_UNREACH6; + NEED1("missing unreach code"); + fill_unreach6_code(&action->arg1, *av); + av++; + break; + + case TOK_COUNT: + action->opcode = O_COUNT; + break; + + case TOK_NAT: + action->opcode = O_NAT; + action->len = F_INSN_SIZE(ipfw_insn_nat); + CHECK_ACTLEN; + if (*av != NULL && _substrcmp(*av, "global") == 0) { + action->arg1 = IP_FW_NAT44_GLOBAL; + av++; + break; + } else + goto chkarg; + case TOK_QUEUE: + action->opcode = O_QUEUE; + goto chkarg; + case TOK_PIPE: + action->opcode = O_PIPE; + goto chkarg; + case TOK_SKIPTO: + action->opcode = O_SKIPTO; + goto chkarg; + case TOK_NETGRAPH: + action->opcode = O_NETGRAPH; + goto chkarg; + case TOK_NGTEE: + action->opcode = O_NGTEE; + goto chkarg; + case TOK_DIVERT: + action->opcode = O_DIVERT; + goto chkarg; + case TOK_TEE: + action->opcode = O_TEE; + goto chkarg; + case TOK_CALL: + action->opcode = O_CALLRETURN; +chkarg: + if (!av[0]) + errx(EX_USAGE, "missing argument for %s", *(av - 1)); + if (isdigit(**av)) { + action->arg1 = strtoul(*av, NULL, 10); + if (action->arg1 <= 0 || action->arg1 >= IP_FW_TABLEARG) + errx(EX_DATAERR, "illegal argument for %s", + *(av - 1)); + } else if (_substrcmp(*av, "tablearg") == 0) { + action->arg1 = IP_FW_TARG; + } else if (i == TOK_DIVERT || i == TOK_TEE) { + struct servent *s; + setservent(1); + s = getservbyname(av[0], "divert"); + if (s != NULL) + action->arg1 = ntohs(s->s_port); + else + errx(EX_DATAERR, "illegal divert/tee port"); + } else + errx(EX_DATAERR, "illegal argument for %s", *(av - 1)); + av++; + break; + + case TOK_FORWARD: { + /* + * Locate the address-port separator (':' or ','). + * Could be one of the following: + * hostname:port + * IPv4 a.b.c.d,port + * IPv4 a.b.c.d:port + * IPv6 w:x:y::z,port + * The ':' can only be used with hostname and IPv4 address. + * XXX-BZ Should we also support [w:x:y::z]:port? + */ + struct sockaddr_storage result; + struct addrinfo *res; + char *s, *end; + int family; + u_short port_number; + + NEED1("missing forward address[:port]"); + + /* + * locate the address-port separator (':' or ',') + */ + s = strchr(*av, ','); + if (s == NULL) { + /* Distinguish between IPv4:port and IPv6 cases. */ + s = strchr(*av, ':'); + if (s && strchr(s+1, ':')) + s = NULL; /* no port */ + } + + port_number = 0; + if (s != NULL) { + /* Terminate host portion and set s to start of port. */ + *(s++) = '\0'; + i = strtoport(s, &end, 0 /* base */, 0 /* proto */); + if (s == end) + errx(EX_DATAERR, + "illegal forwarding port ``%s''", s); + port_number = (u_short)i; + } + + if (_substrcmp(*av, "tablearg") == 0) { + family = PF_INET; + ((struct sockaddr_in*)&result)->sin_addr.s_addr = + INADDR_ANY; + } else { + /* + * Resolve the host name or address to a family and a + * network representation of the address. + */ + if (getaddrinfo(*av, NULL, NULL, &res)) + errx(EX_DATAERR, NULL); + /* Just use the first host in the answer. */ + family = res->ai_family; + memcpy(&result, res->ai_addr, res->ai_addrlen); + freeaddrinfo(res); + } + + if (family == PF_INET) { + ipfw_insn_sa *p = (ipfw_insn_sa *)action; + + action->opcode = O_FORWARD_IP; + action->len = F_INSN_SIZE(ipfw_insn_sa); + CHECK_ACTLEN; + + /* + * In the kernel we assume AF_INET and use only + * sin_port and sin_addr. Remember to set sin_len as + * the routing code seems to use it too. + */ + p->sa.sin_len = sizeof(struct sockaddr_in); + p->sa.sin_family = AF_INET; + p->sa.sin_port = port_number; + p->sa.sin_addr.s_addr = + ((struct sockaddr_in *)&result)->sin_addr.s_addr; + } else if (family == PF_INET6) { + ipfw_insn_sa6 *p = (ipfw_insn_sa6 *)action; + + action->opcode = O_FORWARD_IP6; + action->len = F_INSN_SIZE(ipfw_insn_sa6); + CHECK_ACTLEN; + + p->sa.sin6_len = sizeof(struct sockaddr_in6); + p->sa.sin6_family = AF_INET6; + p->sa.sin6_port = port_number; + p->sa.sin6_flowinfo = 0; + p->sa.sin6_scope_id = + ((struct sockaddr_in6 *)&result)->sin6_scope_id; + bcopy(&((struct sockaddr_in6*)&result)->sin6_addr, + &p->sa.sin6_addr, sizeof(p->sa.sin6_addr)); + } else { + errx(EX_DATAERR, "Invalid address family in forward action"); + } + av++; + break; + } + case TOK_COMMENT: + /* pretend it is a 'count' rule followed by the comment */ + action->opcode = O_COUNT; + av--; /* go back... */ + break; + + case TOK_SETFIB: + { + int numfibs; + size_t intsize = sizeof(int); + + action->opcode = O_SETFIB; + NEED1("missing fib number"); + if (_substrcmp(*av, "tablearg") == 0) { + action->arg1 = IP_FW_TARG; + } else { + action->arg1 = strtoul(*av, NULL, 10); + if (sysctlbyname("net.fibs", &numfibs, &intsize, + NULL, 0) == -1) + errx(EX_DATAERR, "fibs not suported.\n"); + if (action->arg1 >= numfibs) /* Temporary */ + errx(EX_DATAERR, "fib too large.\n"); + /* Add high-order bit to fib to make room for tablearg*/ + action->arg1 |= 0x8000; + } + av++; + break; + } + + case TOK_SETDSCP: + { + int code; + + action->opcode = O_SETDSCP; + NEED1("missing DSCP code"); + if (_substrcmp(*av, "tablearg") == 0) { + action->arg1 = IP_FW_TARG; + } else { + if (isalpha(*av[0])) { + if ((code = match_token(f_ipdscp, *av)) == -1) + errx(EX_DATAERR, "Unknown DSCP code"); + action->arg1 = code; + } else + action->arg1 = strtoul(*av, NULL, 10); + /* + * Add high-order bit to DSCP to make room + * for tablearg + */ + action->arg1 |= 0x8000; + } + av++; + break; + } + + case TOK_REASS: + action->opcode = O_REASS; + break; + + case TOK_RETURN: + fill_cmd(action, O_CALLRETURN, F_NOT, 0); + break; + + default: + av--; + if (match_token(rule_eactions, *av) == -1) + errx(EX_DATAERR, "invalid action %s\n", *av); + /* + * External actions support. + * XXX: we support only syntax with instance name. + * For known external actions (from rule_eactions list) + * we can handle syntax directly. But with `eaction' + * keyword we can use only `eaction ' + * syntax. + */ + case TOK_EACTION: { + uint16_t idx; + + NEED1("Missing eaction name"); + if (eaction_check_name(*av) != 0) + errx(EX_DATAERR, "Invalid eaction name %s", *av); + idx = pack_object(tstate, *av, IPFW_TLV_EACTION); + if (idx == 0) + errx(EX_DATAERR, "pack_object failed"); + fill_cmd(action, O_EXTERNAL_ACTION, 0, idx); + av++; + NEED1("Missing eaction instance name"); + action = next_cmd(action, &ablen); + action->len = 1; + CHECK_ACTLEN; + if (eaction_check_name(*av) != 0) + errx(EX_DATAERR, "Invalid eaction instance name %s", + *av); + /* + * External action instance object has TLV type depended + * from the external action name object index. Since we + * currently don't know this index, use zero as TLV type. + */ + idx = pack_object(tstate, *av, 0); + if (idx == 0) + errx(EX_DATAERR, "pack_object failed"); + fill_cmd(action, O_EXTERNAL_INSTANCE, 0, idx); + av++; + } + } + action = next_cmd(action, &ablen); + + /* + * [altq queuename] -- altq tag, optional + * [log [logamount N]] -- log, optional + * + * If they exist, it go first in the cmdbuf, but then it is + * skipped in the copy section to the end of the buffer. + */ + while (av[0] != NULL && (i = match_token(rule_action_params, *av)) != -1) { + av++; + switch (i) { + case TOK_LOG: + { + ipfw_insn_log *c = (ipfw_insn_log *)cmd; + int l; + + if (have_log) + errx(EX_DATAERR, + "log cannot be specified more than once"); + have_log = (ipfw_insn *)c; + cmd->len = F_INSN_SIZE(ipfw_insn_log); + CHECK_CMDLEN; + cmd->opcode = O_LOG; + if (av[0] && _substrcmp(*av, "logamount") == 0) { + av++; + NEED1("logamount requires argument"); + l = atoi(*av); + if (l < 0) + errx(EX_DATAERR, + "logamount must be positive"); + c->max_log = l; + av++; + } else { + len = sizeof(c->max_log); + if (sysctlbyname("net.inet.ip.fw.verbose_limit", + &c->max_log, &len, NULL, 0) == -1) { + if (co.test_only) { + c->max_log = 0; + break; + } + errx(1, "sysctlbyname(\"%s\")", + "net.inet.ip.fw.verbose_limit"); + } + } + } + break; + +#ifndef NO_ALTQ + case TOK_ALTQ: + { + ipfw_insn_altq *a = (ipfw_insn_altq *)cmd; + + NEED1("missing altq queue name"); + if (have_altq) + errx(EX_DATAERR, + "altq cannot be specified more than once"); + have_altq = (ipfw_insn *)a; + cmd->len = F_INSN_SIZE(ipfw_insn_altq); + CHECK_CMDLEN; + cmd->opcode = O_ALTQ; + a->qid = altq_name_to_qid(*av); + av++; + } + break; +#endif + + case TOK_TAG: + case TOK_UNTAG: { + uint16_t tag; + + if (have_tag) + errx(EX_USAGE, "tag and untag cannot be " + "specified more than once"); + GET_UINT_ARG(tag, IPFW_ARG_MIN, IPFW_ARG_MAX, i, + rule_action_params); + have_tag = cmd; + fill_cmd(cmd, O_TAG, (i == TOK_TAG) ? 0: F_NOT, tag); + av++; + break; + } + + default: + abort(); + } + cmd = next_cmd(cmd, &cblen); + } + + if (have_state) /* must be a check-state, we are done */ + goto done; + +#define OR_START(target) \ + if (av[0] && (*av[0] == '(' || *av[0] == '{')) { \ + if (open_par) \ + errx(EX_USAGE, "nested \"(\" not allowed\n"); \ + prev = NULL; \ + open_par = 1; \ + if ( (av[0])[1] == '\0') { \ + av++; \ + } else \ + (*av)++; \ + } \ + target: \ + + +#define CLOSE_PAR \ + if (open_par) { \ + if (av[0] && ( \ + strcmp(*av, ")") == 0 || \ + strcmp(*av, "}") == 0)) { \ + prev = NULL; \ + open_par = 0; \ + av++; \ + } else \ + errx(EX_USAGE, "missing \")\"\n"); \ + } + +#define NOT_BLOCK \ + if (av[0] && _substrcmp(*av, "not") == 0) { \ + if (cmd->len & F_NOT) \ + errx(EX_USAGE, "double \"not\" not allowed\n"); \ + cmd->len |= F_NOT; \ + av++; \ + } + +#define OR_BLOCK(target) \ + if (av[0] && _substrcmp(*av, "or") == 0) { \ + if (prev == NULL || open_par == 0) \ + errx(EX_DATAERR, "invalid OR block"); \ + prev->len |= F_OR; \ + av++; \ + goto target; \ + } \ + CLOSE_PAR; + + first_cmd = cmd; + +#if 0 + /* + * MAC addresses, optional. + * If we have this, we skip the part "proto from src to dst" + * and jump straight to the option parsing. + */ + NOT_BLOCK; + NEED1("missing protocol"); + if (_substrcmp(*av, "MAC") == 0 || + _substrcmp(*av, "mac") == 0) { + av++; /* the "MAC" keyword */ + add_mac(cmd, av); /* exits in case of errors */ + cmd = next_cmd(cmd); + av += 2; /* dst-mac and src-mac */ + NOT_BLOCK; + NEED1("missing mac type"); + if (add_mactype(cmd, av[0])) + cmd = next_cmd(cmd); + av++; /* any or mac-type */ + goto read_options; + } +#endif + + /* + * protocol, mandatory + */ + OR_START(get_proto); + NOT_BLOCK; + NEED1("missing protocol"); + if (add_proto_compat(cmd, *av, &proto)) { + av++; + if (F_LEN(cmd) != 0) { + prev = cmd; + cmd = next_cmd(cmd, &cblen); + } + } else if (first_cmd != cmd) { + errx(EX_DATAERR, "invalid protocol ``%s''", *av); + } else + goto read_options; + OR_BLOCK(get_proto); + + /* + * "from", mandatory + */ + if ((av[0] == NULL) || _substrcmp(*av, "from") != 0) + errx(EX_USAGE, "missing ``from''"); + av++; + + /* + * source IP, mandatory + */ + OR_START(source_ip); + NOT_BLOCK; /* optional "not" */ + NEED1("missing source address"); + if (add_src(cmd, *av, proto, cblen, tstate)) { + av++; + if (F_LEN(cmd) != 0) { /* ! any */ + prev = cmd; + cmd = next_cmd(cmd, &cblen); + } + } else + errx(EX_USAGE, "bad source address %s", *av); + OR_BLOCK(source_ip); + + /* + * source ports, optional + */ + NOT_BLOCK; /* optional "not" */ + if ( av[0] != NULL ) { + if (_substrcmp(*av, "any") == 0 || + add_ports(cmd, *av, proto, O_IP_SRCPORT, cblen)) { + av++; + if (F_LEN(cmd) != 0) + cmd = next_cmd(cmd, &cblen); + } + } + + /* + * "to", mandatory + */ + if ( (av[0] == NULL) || _substrcmp(*av, "to") != 0 ) + errx(EX_USAGE, "missing ``to''"); + av++; + + /* + * destination, mandatory + */ + OR_START(dest_ip); + NOT_BLOCK; /* optional "not" */ + NEED1("missing dst address"); + if (add_dst(cmd, *av, proto, cblen, tstate)) { + av++; + if (F_LEN(cmd) != 0) { /* ! any */ + prev = cmd; + cmd = next_cmd(cmd, &cblen); + } + } else + errx( EX_USAGE, "bad destination address %s", *av); + OR_BLOCK(dest_ip); + + /* + * dest. ports, optional + */ + NOT_BLOCK; /* optional "not" */ + if (av[0]) { + if (_substrcmp(*av, "any") == 0 || + add_ports(cmd, *av, proto, O_IP_DSTPORT, cblen)) { + av++; + if (F_LEN(cmd) != 0) + cmd = next_cmd(cmd, &cblen); + } + } + +read_options: + if (av[0] && first_cmd == cmd) { + /* + * nothing specified so far, store in the rule to ease + * printout later. + */ + rule->flags |= IPFW_RULE_NOOPT; + } + prev = NULL; + while ( av[0] != NULL ) { + char *s; + ipfw_insn_u32 *cmd32; /* alias for cmd */ + + s = *av; + cmd32 = (ipfw_insn_u32 *)cmd; + + if (*s == '!') { /* alternate syntax for NOT */ + if (cmd->len & F_NOT) + errx(EX_USAGE, "double \"not\" not allowed\n"); + cmd->len = F_NOT; + s++; + } + i = match_token(rule_options, s); + av++; + switch(i) { + case TOK_NOT: + if (cmd->len & F_NOT) + errx(EX_USAGE, "double \"not\" not allowed\n"); + cmd->len = F_NOT; + break; + + case TOK_OR: + if (open_par == 0 || prev == NULL) + errx(EX_USAGE, "invalid \"or\" block\n"); + prev->len |= F_OR; + break; + + case TOK_STARTBRACE: + if (open_par) + errx(EX_USAGE, "+nested \"(\" not allowed\n"); + open_par = 1; + break; + + case TOK_ENDBRACE: + if (!open_par) + errx(EX_USAGE, "+missing \")\"\n"); + open_par = 0; + prev = NULL; + break; + + case TOK_IN: + fill_cmd(cmd, O_IN, 0, 0); + break; + + case TOK_OUT: + cmd->len ^= F_NOT; /* toggle F_NOT */ + fill_cmd(cmd, O_IN, 0, 0); + break; + + case TOK_DIVERTED: + fill_cmd(cmd, O_DIVERTED, 0, 3); + break; + + case TOK_DIVERTEDLOOPBACK: + fill_cmd(cmd, O_DIVERTED, 0, 1); + break; + + case TOK_DIVERTEDOUTPUT: + fill_cmd(cmd, O_DIVERTED, 0, 2); + break; + + case TOK_FRAG: + fill_cmd(cmd, O_FRAG, 0, 0); + break; + + case TOK_LAYER2: + fill_cmd(cmd, O_LAYER2, 0, 0); + break; + + case TOK_XMIT: + case TOK_RECV: + case TOK_VIA: + NEED1("recv, xmit, via require interface name" + " or address"); + fill_iface((ipfw_insn_if *)cmd, av[0], cblen, tstate); + av++; + if (F_LEN(cmd) == 0) /* not a valid address */ + break; + if (i == TOK_XMIT) + cmd->opcode = O_XMIT; + else if (i == TOK_RECV) + cmd->opcode = O_RECV; + else if (i == TOK_VIA) + cmd->opcode = O_VIA; + break; + + case TOK_ICMPTYPES: + NEED1("icmptypes requires list of types"); + fill_icmptypes((ipfw_insn_u32 *)cmd, *av); + av++; + break; + + case TOK_ICMP6TYPES: + NEED1("icmptypes requires list of types"); + fill_icmp6types((ipfw_insn_icmp6 *)cmd, *av, cblen); + av++; + break; + + case TOK_IPTTL: + NEED1("ipttl requires TTL"); + if (strpbrk(*av, "-,")) { + if (!add_ports(cmd, *av, 0, O_IPTTL, cblen)) + errx(EX_DATAERR, "invalid ipttl %s", *av); + } else + fill_cmd(cmd, O_IPTTL, 0, strtoul(*av, NULL, 0)); + av++; + break; + + case TOK_IPID: + NEED1("ipid requires id"); + if (strpbrk(*av, "-,")) { + if (!add_ports(cmd, *av, 0, O_IPID, cblen)) + errx(EX_DATAERR, "invalid ipid %s", *av); + } else + fill_cmd(cmd, O_IPID, 0, strtoul(*av, NULL, 0)); + av++; + break; + + case TOK_IPLEN: + NEED1("iplen requires length"); + if (strpbrk(*av, "-,")) { + if (!add_ports(cmd, *av, 0, O_IPLEN, cblen)) + errx(EX_DATAERR, "invalid ip len %s", *av); + } else + fill_cmd(cmd, O_IPLEN, 0, strtoul(*av, NULL, 0)); + av++; + break; + + case TOK_IPVER: + NEED1("ipver requires version"); + fill_cmd(cmd, O_IPVER, 0, strtoul(*av, NULL, 0)); + av++; + break; + + case TOK_IPPRECEDENCE: + NEED1("ipprecedence requires value"); + fill_cmd(cmd, O_IPPRECEDENCE, 0, + (strtoul(*av, NULL, 0) & 7) << 5); + av++; + break; + + case TOK_DSCP: + NEED1("missing DSCP code"); + fill_dscp(cmd, *av, cblen); + av++; + break; + + case TOK_IPOPTS: + NEED1("missing argument for ipoptions"); + fill_flags_cmd(cmd, O_IPOPT, f_ipopts, *av); + av++; + break; + + case TOK_IPTOS: + NEED1("missing argument for iptos"); + fill_flags_cmd(cmd, O_IPTOS, f_iptos, *av); + av++; + break; + + case TOK_UID: + NEED1("uid requires argument"); + { + char *end; + uid_t uid; + struct passwd *pwd; + + cmd->opcode = O_UID; + uid = strtoul(*av, &end, 0); + pwd = (*end == '\0') ? getpwuid(uid) : getpwnam(*av); + if (pwd == NULL) + errx(EX_DATAERR, "uid \"%s\" nonexistent", *av); + cmd32->d[0] = pwd->pw_uid; + cmd->len |= F_INSN_SIZE(ipfw_insn_u32); + av++; + } + break; + + case TOK_GID: + NEED1("gid requires argument"); + { + char *end; + gid_t gid; + struct group *grp; + + cmd->opcode = O_GID; + gid = strtoul(*av, &end, 0); + grp = (*end == '\0') ? getgrgid(gid) : getgrnam(*av); + if (grp == NULL) + errx(EX_DATAERR, "gid \"%s\" nonexistent", *av); + cmd32->d[0] = grp->gr_gid; + cmd->len |= F_INSN_SIZE(ipfw_insn_u32); + av++; + } + break; + + case TOK_JAIL: + NEED1("jail requires argument"); + { + char *end; + int jid; + + cmd->opcode = O_JAIL; + jid = (int)strtol(*av, &end, 0); + if (jid < 0 || *end != '\0') + errx(EX_DATAERR, "jail requires prison ID"); + cmd32->d[0] = (uint32_t)jid; + cmd->len |= F_INSN_SIZE(ipfw_insn_u32); + av++; + } + break; + + case TOK_ESTAB: + fill_cmd(cmd, O_ESTAB, 0, 0); + break; + + case TOK_SETUP: + fill_cmd(cmd, O_TCPFLAGS, 0, + (TH_SYN) | ( (TH_ACK) & 0xff) <<8 ); + break; + + case TOK_TCPDATALEN: + NEED1("tcpdatalen requires length"); + if (strpbrk(*av, "-,")) { + if (!add_ports(cmd, *av, 0, O_TCPDATALEN, cblen)) + errx(EX_DATAERR, "invalid tcpdata len %s", *av); + } else + fill_cmd(cmd, O_TCPDATALEN, 0, + strtoul(*av, NULL, 0)); + av++; + break; + + case TOK_TCPOPTS: + NEED1("missing argument for tcpoptions"); + fill_flags_cmd(cmd, O_TCPOPTS, f_tcpopts, *av); + av++; + break; + + case TOK_TCPSEQ: + case TOK_TCPACK: + NEED1("tcpseq/tcpack requires argument"); + cmd->len = F_INSN_SIZE(ipfw_insn_u32); + cmd->opcode = (i == TOK_TCPSEQ) ? O_TCPSEQ : O_TCPACK; + cmd32->d[0] = htonl(strtoul(*av, NULL, 0)); + av++; + break; + + case TOK_TCPWIN: + NEED1("tcpwin requires length"); + if (strpbrk(*av, "-,")) { + if (!add_ports(cmd, *av, 0, O_TCPWIN, cblen)) + errx(EX_DATAERR, "invalid tcpwin len %s", *av); + } else + fill_cmd(cmd, O_TCPWIN, 0, + strtoul(*av, NULL, 0)); + av++; + break; + + case TOK_TCPFLAGS: + NEED1("missing argument for tcpflags"); + cmd->opcode = O_TCPFLAGS; + fill_flags_cmd(cmd, O_TCPFLAGS, f_tcpflags, *av); + av++; + break; + + case TOK_KEEPSTATE: + if (open_par) + errx(EX_USAGE, "keep-state cannot be part " + "of an or block"); + if (have_state) + errx(EX_USAGE, "only one of keep-state " + "and limit is allowed"); + have_state = cmd; + fill_cmd(cmd, O_KEEP_STATE, 0, 0); + break; + + case TOK_LIMIT: { + ipfw_insn_limit *c = (ipfw_insn_limit *)cmd; + int val; + + if (open_par) + errx(EX_USAGE, + "limit cannot be part of an or block"); + if (have_state) + errx(EX_USAGE, "only one of keep-state and " + "limit is allowed"); + have_state = cmd; + + cmd->len = F_INSN_SIZE(ipfw_insn_limit); + CHECK_CMDLEN; + cmd->opcode = O_LIMIT; + c->limit_mask = c->conn_limit = 0; + + while ( av[0] != NULL ) { + if ((val = match_token(limit_masks, *av)) <= 0) + break; + c->limit_mask |= val; + av++; + } + + if (c->limit_mask == 0) + errx(EX_USAGE, "limit: missing limit mask"); + + GET_UINT_ARG(c->conn_limit, IPFW_ARG_MIN, IPFW_ARG_MAX, + TOK_LIMIT, rule_options); + + av++; + break; + } + + case TOK_PROTO: + NEED1("missing protocol"); + if (add_proto(cmd, *av, &proto)) { + av++; + } else + errx(EX_DATAERR, "invalid protocol ``%s''", + *av); + break; + + case TOK_SRCIP: + NEED1("missing source IP"); + if (add_srcip(cmd, *av, cblen, tstate)) { + av++; + } + break; + + case TOK_DSTIP: + NEED1("missing destination IP"); + if (add_dstip(cmd, *av, cblen, tstate)) { + av++; + } + break; + + case TOK_SRCIP6: + NEED1("missing source IP6"); + if (add_srcip6(cmd, *av, cblen)) { + av++; + } + break; + + case TOK_DSTIP6: + NEED1("missing destination IP6"); + if (add_dstip6(cmd, *av, cblen)) { + av++; + } + break; + + case TOK_SRCPORT: + NEED1("missing source port"); + if (_substrcmp(*av, "any") == 0 || + add_ports(cmd, *av, proto, O_IP_SRCPORT, cblen)) { + av++; + } else + errx(EX_DATAERR, "invalid source port %s", *av); + break; + + case TOK_DSTPORT: + NEED1("missing destination port"); + if (_substrcmp(*av, "any") == 0 || + add_ports(cmd, *av, proto, O_IP_DSTPORT, cblen)) { + av++; + } else + errx(EX_DATAERR, "invalid destination port %s", + *av); + break; + + case TOK_MAC: + if (add_mac(cmd, av, cblen)) + av += 2; + break; + + case TOK_MACTYPE: + NEED1("missing mac type"); + if (!add_mactype(cmd, *av, cblen)) + errx(EX_DATAERR, "invalid mac type %s", *av); + av++; + break; + + case TOK_VERREVPATH: + fill_cmd(cmd, O_VERREVPATH, 0, 0); + break; + + case TOK_VERSRCREACH: + fill_cmd(cmd, O_VERSRCREACH, 0, 0); + break; + + case TOK_ANTISPOOF: + fill_cmd(cmd, O_ANTISPOOF, 0, 0); + break; + + case TOK_IPSEC: + fill_cmd(cmd, O_IPSEC, 0, 0); + break; + + case TOK_IPV6: + fill_cmd(cmd, O_IP6, 0, 0); + break; + + case TOK_IPV4: + fill_cmd(cmd, O_IP4, 0, 0); + break; + + case TOK_EXT6HDR: + fill_ext6hdr( cmd, *av ); + av++; + break; + + case TOK_FLOWID: + if (proto != IPPROTO_IPV6 ) + errx( EX_USAGE, "flow-id filter is active " + "only for ipv6 protocol\n"); + fill_flow6( (ipfw_insn_u32 *) cmd, *av, cblen); + av++; + break; + + case TOK_COMMENT: + fill_comment(cmd, av, cblen); + av[0]=NULL; + break; + + case TOK_TAGGED: + if (av[0] && strpbrk(*av, "-,")) { + if (!add_ports(cmd, *av, 0, O_TAGGED, cblen)) + errx(EX_DATAERR, "tagged: invalid tag" + " list: %s", *av); + } + else { + uint16_t tag; + + GET_UINT_ARG(tag, IPFW_ARG_MIN, IPFW_ARG_MAX, + TOK_TAGGED, rule_options); + fill_cmd(cmd, O_TAGGED, 0, tag); + } + av++; + break; + + case TOK_FIB: + NEED1("fib requires fib number"); + fill_cmd(cmd, O_FIB, 0, strtoul(*av, NULL, 0)); + av++; + break; + case TOK_SOCKARG: + fill_cmd(cmd, O_SOCKARG, 0, 0); + break; + + case TOK_LOOKUP: { + ipfw_insn_u32 *c = (ipfw_insn_u32 *)cmd; + int j; + + if (!av[0] || !av[1]) + errx(EX_USAGE, "format: lookup argument tablenum"); + cmd->opcode = O_IP_DST_LOOKUP; + cmd->len |= F_INSN_SIZE(ipfw_insn) + 2; + i = match_token(rule_options, *av); + for (j = 0; lookup_key[j] >= 0 ; j++) { + if (i == lookup_key[j]) + break; + } + if (lookup_key[j] <= 0) + errx(EX_USAGE, "format: cannot lookup on %s", *av); + __PAST_END(c->d, 1) = j; // i converted to option + av++; + + if ((j = pack_table(tstate, *av)) == 0) + errx(EX_DATAERR, "Invalid table name: %s", *av); + + cmd->arg1 = j; + av++; + } + break; + case TOK_FLOW: + NEED1("missing table name"); + if (strncmp(*av, "table(", 6) != 0) + errx(EX_DATAERR, + "enclose table name into \"table()\""); + fill_table(cmd, *av, O_IP_FLOW_LOOKUP, tstate); + av++; + break; + + default: + errx(EX_USAGE, "unrecognised option [%d] %s\n", i, s); + } + if (F_LEN(cmd) > 0) { /* prepare to advance */ + prev = cmd; + cmd = next_cmd(cmd, &cblen); + } + } + +done: + /* + * Now copy stuff into the rule. + * If we have a keep-state option, the first instruction + * must be a PROBE_STATE (which is generated here). + * If we have a LOG option, it was stored as the first command, + * and now must be moved to the top of the action part. + */ + dst = (ipfw_insn *)rule->cmd; + + /* + * First thing to write into the command stream is the match probability. + */ + if (match_prob != 1) { /* 1 means always match */ + dst->opcode = O_PROB; + dst->len = 2; + *((int32_t *)(dst+1)) = (int32_t)(match_prob * 0x7fffffff); + dst += dst->len; + } + + /* + * generate O_PROBE_STATE if necessary + */ + if (have_state && have_state->opcode != O_CHECK_STATE) { + fill_cmd(dst, O_PROBE_STATE, 0, 0); + dst = next_cmd(dst, &rblen); + } + + /* copy all commands but O_LOG, O_KEEP_STATE, O_LIMIT, O_ALTQ, O_TAG */ + for (src = (ipfw_insn *)cmdbuf; src != cmd; src += i) { + i = F_LEN(src); + CHECK_RBUFLEN(i); + + switch (src->opcode) { + case O_LOG: + case O_KEEP_STATE: + case O_LIMIT: + case O_ALTQ: + case O_TAG: + break; + default: + bcopy(src, dst, i * sizeof(uint32_t)); + dst += i; + } + } + + /* + * put back the have_state command as last opcode + */ + if (have_state && have_state->opcode != O_CHECK_STATE) { + i = F_LEN(have_state); + CHECK_RBUFLEN(i); + bcopy(have_state, dst, i * sizeof(uint32_t)); + dst += i; + } + /* + * start action section + */ + rule->act_ofs = dst - rule->cmd; + + /* put back O_LOG, O_ALTQ, O_TAG if necessary */ + if (have_log) { + i = F_LEN(have_log); + CHECK_RBUFLEN(i); + bcopy(have_log, dst, i * sizeof(uint32_t)); + dst += i; + } + if (have_altq) { + i = F_LEN(have_altq); + CHECK_RBUFLEN(i); + bcopy(have_altq, dst, i * sizeof(uint32_t)); + dst += i; + } + if (have_tag) { + i = F_LEN(have_tag); + CHECK_RBUFLEN(i); + bcopy(have_tag, dst, i * sizeof(uint32_t)); + dst += i; + } + + /* + * copy all other actions + */ + for (src = (ipfw_insn *)actbuf; src != action; src += i) { + i = F_LEN(src); + CHECK_RBUFLEN(i); + bcopy(src, dst, i * sizeof(uint32_t)); + dst += i; + } + + rule->cmd_len = (uint32_t *)dst - (uint32_t *)(rule->cmd); + *rbufsize = (char *)dst - (char *)rule; +} + +static int +compare_ntlv(const void *_a, const void *_b) +{ + ipfw_obj_ntlv *a, *b; + + a = (ipfw_obj_ntlv *)_a; + b = (ipfw_obj_ntlv *)_b; + + if (a->set < b->set) + return (-1); + else if (a->set > b->set) + return (1); + + if (a->idx < b->idx) + return (-1); + else if (a->idx > b->idx) + return (1); + + if (a->head.type < b->head.type) + return (-1); + else if (a->head.type > b->head.type) + return (1); + + return (0); +} + +/* + * Provide kernel with sorted list of referenced objects + */ +static void +object_sort_ctlv(ipfw_obj_ctlv *ctlv) +{ + + qsort(ctlv + 1, ctlv->count, ctlv->objsize, compare_ntlv); +} + +struct object_kt { + uint16_t uidx; + uint16_t type; +}; +static int +compare_object_kntlv(const void *k, const void *v) +{ + ipfw_obj_ntlv *ntlv; + struct object_kt key; + + key = *((struct object_kt *)k); + ntlv = (ipfw_obj_ntlv *)v; + + if (key.uidx < ntlv->idx) + return (-1); + else if (key.uidx > ntlv->idx) + return (1); + + if (key.type < ntlv->head.type) + return (-1); + else if (key.type > ntlv->head.type) + return (1); + + return (0); +} + +/* + * Finds object name in @ctlv by @idx and @type. + * Uses the following facts: + * 1) All TLVs are the same size + * 2) Kernel implementation provides already sorted list. + * + * Returns table name or NULL. + */ +static char * +object_search_ctlv(ipfw_obj_ctlv *ctlv, uint16_t idx, uint16_t type) +{ + ipfw_obj_ntlv *ntlv; + struct object_kt key; + + key.uidx = idx; + key.type = type; + + ntlv = bsearch(&key, (ctlv + 1), ctlv->count, ctlv->objsize, + compare_object_kntlv); + + if (ntlv != NULL) + return (ntlv->name); + + return (NULL); +} + +static char * +table_search_ctlv(ipfw_obj_ctlv *ctlv, uint16_t idx) +{ + + return (object_search_ctlv(ctlv, idx, IPFW_TLV_TBL_NAME)); +} + +/* + * Adds one or more rules to ipfw chain. + * Data layout: + * Request: + * [ + * ip_fw3_opheader + * [ ipfw_obj_ctlv(IPFW_TLV_TBL_LIST) ipfw_obj_ntlv x N ] (optional *1) + * [ ipfw_obj_ctlv(IPFW_TLV_RULE_LIST) [ ip_fw_rule ip_fw_insn ] x N ] (*2) (*3) + * ] + * Reply: + * [ + * ip_fw3_opheader + * [ ipfw_obj_ctlv(IPFW_TLV_TBL_LIST) ipfw_obj_ntlv x N ] (optional) + * [ ipfw_obj_ctlv(IPFW_TLV_RULE_LIST) [ ip_fw_rule ip_fw_insn ] x N ] + * ] + * + * Rules in reply are modified to store their actual ruleset number. + * + * (*1) TLVs inside IPFW_TLV_TBL_LIST needs to be sorted ascending + * according to their idx field and there has to be no duplicates. + * (*2) Numbered rules inside IPFW_TLV_RULE_LIST needs to be sorted ascending. + * (*3) Each ip_fw structure needs to be aligned to u64 boundary. + */ +void +ipfw_add(char *av[]) +{ + uint32_t rulebuf[1024]; + int rbufsize, default_off, tlen, rlen; + size_t sz; + struct tidx ts; + struct ip_fw_rule *rule; + caddr_t tbuf; + ip_fw3_opheader *op3; + ipfw_obj_ctlv *ctlv, *tstate; + + rbufsize = sizeof(rulebuf); + memset(rulebuf, 0, rbufsize); + memset(&ts, 0, sizeof(ts)); + + /* Optimize case with no tables */ + default_off = sizeof(ipfw_obj_ctlv) + sizeof(ip_fw3_opheader); + op3 = (ip_fw3_opheader *)rulebuf; + ctlv = (ipfw_obj_ctlv *)(op3 + 1); + rule = (struct ip_fw_rule *)(ctlv + 1); + rbufsize -= default_off; + + compile_rule(av, (uint32_t *)rule, &rbufsize, &ts); + /* Align rule size to u64 boundary */ + rlen = roundup2(rbufsize, sizeof(uint64_t)); + + tbuf = NULL; + sz = 0; + tstate = NULL; + if (ts.count != 0) { + /* Some tables. We have to alloc more data */ + tlen = ts.count * sizeof(ipfw_obj_ntlv); + sz = default_off + sizeof(ipfw_obj_ctlv) + tlen + rlen; + + if ((tbuf = calloc(1, sz)) == NULL) + err(EX_UNAVAILABLE, "malloc() failed for IP_FW_ADD"); + op3 = (ip_fw3_opheader *)tbuf; + /* Tables first */ + ctlv = (ipfw_obj_ctlv *)(op3 + 1); + ctlv->head.type = IPFW_TLV_TBLNAME_LIST; + ctlv->head.length = sizeof(ipfw_obj_ctlv) + tlen; + ctlv->count = ts.count; + ctlv->objsize = sizeof(ipfw_obj_ntlv); + memcpy(ctlv + 1, ts.idx, tlen); + object_sort_ctlv(ctlv); + tstate = ctlv; + /* Rule next */ + ctlv = (ipfw_obj_ctlv *)((caddr_t)ctlv + ctlv->head.length); + ctlv->head.type = IPFW_TLV_RULE_LIST; + ctlv->head.length = sizeof(ipfw_obj_ctlv) + rlen; + ctlv->count = 1; + memcpy(ctlv + 1, rule, rbufsize); + } else { + /* Simply add header */ + sz = rlen + default_off; + memset(ctlv, 0, sizeof(*ctlv)); + ctlv->head.type = IPFW_TLV_RULE_LIST; + ctlv->head.length = sizeof(ipfw_obj_ctlv) + rlen; + ctlv->count = 1; + } + + if (do_get3(IP_FW_XADD, op3, &sz) != 0) + err(EX_UNAVAILABLE, "getsockopt(%s)", "IP_FW_XADD"); + + if (!co.do_quiet) { + struct format_opts sfo; + struct buf_pr bp; + memset(&sfo, 0, sizeof(sfo)); + sfo.tstate = tstate; + sfo.set_mask = (uint32_t)(-1); + bp_alloc(&bp, 4096); + show_static_rule(&co, &sfo, &bp, rule, NULL); + printf("%s", bp.buf); + bp_free(&bp); + } + + if (tbuf != NULL) + free(tbuf); + + if (ts.idx != NULL) + free(ts.idx); +} + +/* + * clear the counters or the log counters. + * optname has the following values: + * 0 (zero both counters and logging) + * 1 (zero logging only) + */ +void +ipfw_zero(int ac, char *av[], int optname) +{ + ipfw_range_tlv rt; + uint32_t arg; + int failed = EX_OK; + char const *errstr; + char const *name = optname ? "RESETLOG" : "ZERO"; + + optname = optname ? IP_FW_XRESETLOG : IP_FW_XZERO; + memset(&rt, 0, sizeof(rt)); + + av++; ac--; + + if (ac == 0) { + /* clear all entries */ + rt.flags = IPFW_RCFLAG_ALL; + if (do_range_cmd(optname, &rt) < 0) + err(EX_UNAVAILABLE, "setsockopt(IP_FW_X%s)", name); + if (!co.do_quiet) + printf("%s.\n", optname == IP_FW_XZERO ? + "Accounting cleared":"Logging counts reset"); + + return; + } + + while (ac) { + /* Rule number */ + if (isdigit(**av)) { + arg = strtonum(*av, 0, 0xffff, &errstr); + if (errstr) + errx(EX_DATAERR, + "invalid rule number %s\n", *av); + rt.start_rule = arg; + rt.end_rule = arg; + rt.flags |= IPFW_RCFLAG_RANGE; + if (co.use_set != 0) { + rt.set = co.use_set - 1; + rt.flags |= IPFW_RCFLAG_SET; + } + if (do_range_cmd(optname, &rt) != 0) { + warn("rule %u: setsockopt(IP_FW_X%s)", + arg, name); + failed = EX_UNAVAILABLE; + } else if (rt.new_set == 0) { + printf("Entry %d not found\n", arg); + failed = EX_UNAVAILABLE; + } else if (!co.do_quiet) + printf("Entry %d %s.\n", arg, + optname == IP_FW_XZERO ? + "cleared" : "logging count reset"); + } else { + errx(EX_USAGE, "invalid rule number ``%s''", *av); + } + av++; ac--; + } + if (failed != EX_OK) + exit(failed); +} + +void +ipfw_flush(int force) +{ + ipfw_range_tlv rt; + + if (!force && !co.do_quiet) { /* need to ask user */ + int c; + + printf("Are you sure? [yn] "); + fflush(stdout); + do { + c = toupper(getc(stdin)); + while (c != '\n' && getc(stdin) != '\n') + if (feof(stdin)) + return; /* and do not flush */ + } while (c != 'Y' && c != 'N'); + printf("\n"); + if (c == 'N') /* user said no */ + return; + } + if (co.do_pipe) { +#ifdef DUMMYNET + dummynet_flush(); +#else + fprintf(stderr, "dummynet_flush not supported\n"); +#endif + return; + } + /* `ipfw set N flush` - is the same that `ipfw delete set N` */ + memset(&rt, 0, sizeof(rt)); + if (co.use_set != 0) { + rt.set = co.use_set - 1; + rt.flags = IPFW_RCFLAG_SET; + } else + rt.flags = IPFW_RCFLAG_ALL; + if (do_range_cmd(IP_FW_XDEL, &rt) != 0) + err(EX_UNAVAILABLE, "setsockopt(IP_FW_XDEL)"); + if (!co.do_quiet) + printf("Flushed all %s.\n", co.do_pipe ? "pipes" : "rules"); +} + +static struct _s_x intcmds[] = { + { "talist", TOK_TALIST }, + { "iflist", TOK_IFLIST }, + { "olist", TOK_OLIST }, + { "vlist", TOK_VLIST }, + { NULL, 0 } +}; + +static struct _s_x otypes[] = { + { "EACTION", IPFW_TLV_EACTION }, + { NULL, 0 } +}; + +static const char* +lookup_eaction_name(ipfw_obj_ntlv *ntlv, int cnt, uint16_t type) +{ + const char *name; + int i; + + name = NULL; + for (i = 0; i < cnt; i++) { + if (ntlv[i].head.type != IPFW_TLV_EACTION) + continue; + if (IPFW_TLV_EACTION_NAME(ntlv[i].idx) != type) + continue; + name = ntlv[i].name; + break; + } + return (name); +} + +static void +ipfw_list_objects(int ac, char *av[]) +{ + ipfw_obj_lheader req, *olh; + ipfw_obj_ntlv *ntlv; + const char *name; + size_t sz; + int i; + + memset(&req, 0, sizeof(req)); + sz = sizeof(req); + if (do_get3(IP_FW_DUMP_SRVOBJECTS, &req.opheader, &sz) != 0) + if (errno != ENOMEM) + return; + + sz = req.size; + if ((olh = calloc(1, sz)) == NULL) + return; + + olh->size = sz; + if (do_get3(IP_FW_DUMP_SRVOBJECTS, &olh->opheader, &sz) != 0) { + free(olh); + return; + } + + if (olh->count > 0) + printf("Objects list:\n"); + else + printf("There are no objects\n"); + ntlv = (ipfw_obj_ntlv *)(olh + 1); + for (i = 0; i < olh->count; i++) { + name = match_value(otypes, ntlv->head.type); + if (name == NULL) + name = lookup_eaction_name( + (ipfw_obj_ntlv *)(olh + 1), olh->count, + ntlv->head.type); + if (name == NULL) + printf(" kidx: %4d\ttype: %10d\tname: %s\n", + ntlv->idx, ntlv->head.type, ntlv->name); + else + printf(" kidx: %4d\ttype: %10s\tname: %s\n", + ntlv->idx, name, ntlv->name); + ntlv++; + } + free(olh); +} + +void +ipfw_internal_handler(int ac, char *av[]) +{ + int tcmd; + + ac--; av++; + NEED1("internal cmd required"); + + if ((tcmd = match_token(intcmds, *av)) == -1) + errx(EX_USAGE, "invalid internal sub-cmd: %s", *av); + + switch (tcmd) { + case TOK_IFLIST: + ipfw_list_tifaces(); + break; + case TOK_TALIST: + ipfw_list_ta(ac, av); + break; + case TOK_OLIST: + ipfw_list_objects(ac, av); + break; + case TOK_VLIST: + ipfw_list_values(ac, av); + break; + } +} + +static int +ipfw_get_tracked_ifaces(ipfw_obj_lheader **polh) +{ + ipfw_obj_lheader req, *olh; + size_t sz; + + memset(&req, 0, sizeof(req)); + sz = sizeof(req); + + if (do_get3(IP_FW_XIFLIST, &req.opheader, &sz) != 0) { + if (errno != ENOMEM) + return (errno); + } + + sz = req.size; + if ((olh = calloc(1, sz)) == NULL) + return (ENOMEM); + + olh->size = sz; + if (do_get3(IP_FW_XIFLIST, &olh->opheader, &sz) != 0) { + free(olh); + return (errno); + } + + *polh = olh; + return (0); +} + +static int +ifinfo_cmp(const void *a, const void *b) +{ + ipfw_iface_info *ia, *ib; + + ia = (ipfw_iface_info *)a; + ib = (ipfw_iface_info *)b; + + return (stringnum_cmp(ia->ifname, ib->ifname)); +} + +/* + * Retrieves table list from kernel, + * optionally sorts it and calls requested function for each table. + * Returns 0 on success. + */ +static void +ipfw_list_tifaces() +{ + ipfw_obj_lheader *olh; + ipfw_iface_info *info; + int i, error; + + if ((error = ipfw_get_tracked_ifaces(&olh)) != 0) + err(EX_OSERR, "Unable to request ipfw tracked interface list"); + + + qsort(olh + 1, olh->count, olh->objsize, ifinfo_cmp); + + info = (ipfw_iface_info *)(olh + 1); + for (i = 0; i < olh->count; i++) { + if (info->flags & IPFW_IFFLAG_RESOLVED) + printf("%s ifindex: %d refcount: %u changes: %u\n", + info->ifname, info->ifindex, info->refcnt, + info->gencnt); + else + printf("%s ifindex: unresolved refcount: %u changes: %u\n", + info->ifname, info->refcnt, info->gencnt); + info = (ipfw_iface_info *)((caddr_t)info + olh->objsize); + } + + free(olh); +} + + + + diff --git a/tools/ipfw/ipfw2.h b/tools/ipfw/ipfw2.h new file mode 100644 index 000000000..23e8dd2de --- /dev/null +++ b/tools/ipfw/ipfw2.h @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2002-2003 Luigi Rizzo + * Copyright (c) 1996 Alex Nash, Paul Traina, Poul-Henning Kamp + * Copyright (c) 1994 Ugen J.S.Antsilevich + * + * Idea and grammar partially left from: + * Copyright (c) 1993 Daniel Boulet + * + * Redistribution and use in source forms, with and without modification, + * are permitted provided that this entire comment appears intact. + * + * Redistribution in binary form may occur without any restrictions. + * Obviously, it would be nice if you gave credit where credit is due + * but requiring it would be too onerous. + * + * This software is provided ``AS IS'' without any warranties of any kind. + * + * NEW command line interface for IP firewall facility + * + * $FreeBSD$ + */ + +/* + * Options that can be set on the command line. + * When reading commands from a file, a subset of the options can also + * be applied globally by specifying them before the file name. + * After that, each line can contain its own option that changes + * the global value. + * XXX The context is not restored after each line. + */ + +struct cmdline_opts { + /* boolean options: */ + int do_value_as_ip; /* show table value as IP */ + int do_resolv; /* try to resolve all ip to names */ + int do_time; /* Show time stamps */ + int do_quiet; /* Be quiet in add and flush */ + int do_pipe; /* this cmd refers to a pipe/queue/sched */ + int do_nat; /* this cmd refers to a nat config */ + int do_dynamic; /* display dynamic rules */ + int do_expired; /* display expired dynamic rules */ + int do_compact; /* show rules in compact mode */ + int do_force; /* do not ask for confirmation */ + int show_sets; /* display the set each rule belongs to */ + int test_only; /* only check syntax */ + int comment_only; /* only print action and comment */ + int verbose; /* be verbose on some commands */ + + /* The options below can have multiple values. */ + + int do_sort; /* field to sort results (0 = no) */ + /* valid fields are 1 and above */ + + int use_set; /* work with specified set number */ + /* 0 means all sets, otherwise apply to set use_set - 1 */ + +}; + +extern struct cmdline_opts co; + +/* + * _s_x is a structure that stores a string <-> token pairs, used in + * various places in the parser. Entries are stored in arrays, + * with an entry with s=NULL as terminator. + * The search routines are match_token() and match_value(). + * Often, an element with x=0 contains an error string. + * + */ +struct _s_x { + char const *s; + int x; +}; + +extern struct _s_x f_ipdscp[]; + +enum tokens { + TOK_NULL=0, + + TOK_OR, + TOK_NOT, + TOK_STARTBRACE, + TOK_ENDBRACE, + + TOK_ACCEPT, + TOK_COUNT, + TOK_EACTION, + TOK_PIPE, + TOK_LINK, + TOK_QUEUE, + TOK_FLOWSET, + TOK_SCHED, + TOK_DIVERT, + TOK_TEE, + TOK_NETGRAPH, + TOK_NGTEE, + TOK_FORWARD, + TOK_SKIPTO, + TOK_DENY, + TOK_REJECT, + TOK_RESET, + TOK_UNREACH, + TOK_CHECKSTATE, + TOK_NAT, + TOK_REASS, + TOK_CALL, + TOK_RETURN, + + TOK_ALTQ, + TOK_LOG, + TOK_TAG, + TOK_UNTAG, + + TOK_TAGGED, + TOK_UID, + TOK_GID, + TOK_JAIL, + TOK_IN, + TOK_LIMIT, + TOK_KEEPSTATE, + TOK_LAYER2, + TOK_OUT, + TOK_DIVERTED, + TOK_DIVERTEDLOOPBACK, + TOK_DIVERTEDOUTPUT, + TOK_XMIT, + TOK_RECV, + TOK_VIA, + TOK_FRAG, + TOK_IPOPTS, + TOK_IPLEN, + TOK_IPID, + TOK_IPPRECEDENCE, + TOK_DSCP, + TOK_IPTOS, + TOK_IPTTL, + TOK_IPVER, + TOK_ESTAB, + TOK_SETUP, + TOK_TCPDATALEN, + TOK_TCPFLAGS, + TOK_TCPOPTS, + TOK_TCPSEQ, + TOK_TCPACK, + TOK_TCPWIN, + TOK_ICMPTYPES, + TOK_MAC, + TOK_MACTYPE, + TOK_VERREVPATH, + TOK_VERSRCREACH, + TOK_ANTISPOOF, + TOK_IPSEC, + TOK_COMMENT, + + TOK_PLR, + TOK_NOERROR, + TOK_BUCKETS, + TOK_DSTIP, + TOK_SRCIP, + TOK_DSTPORT, + TOK_SRCPORT, + TOK_ALL, + TOK_MASK, + TOK_FLOW_MASK, + TOK_SCHED_MASK, + TOK_BW, + TOK_DELAY, + TOK_PROFILE, + TOK_BURST, + TOK_RED, + TOK_GRED, + TOK_ECN, + TOK_DROPTAIL, + TOK_PROTO, +#ifdef NEW_AQM + /* AQM tokens*/ + TOK_NO_ECN, + TOK_CODEL, + TOK_FQ_CODEL, + TOK_TARGET, + TOK_INTERVAL, + TOK_FLOWS, + TOK_QUANTUM, + + TOK_PIE, + TOK_FQ_PIE, + TOK_TUPDATE, + TOK_MAX_BURST, + TOK_MAX_ECNTH, + TOK_ALPHA, + TOK_BETA, + TOK_CAPDROP, + TOK_NO_CAPDROP, + TOK_ONOFF, + TOK_DRE, + TOK_TS, + TOK_DERAND, + TOK_NO_DERAND, +#endif + /* dummynet tokens */ + TOK_WEIGHT, + TOK_LMAX, + TOK_PRI, + TOK_TYPE, + TOK_SLOTSIZE, + + TOK_IP, + TOK_IF, + TOK_ALOG, + TOK_DENY_INC, + TOK_SAME_PORTS, + TOK_UNREG_ONLY, + TOK_SKIP_GLOBAL, + TOK_RESET_ADDR, + TOK_ALIAS_REV, + TOK_PROXY_ONLY, + TOK_REDIR_ADDR, + TOK_REDIR_PORT, + TOK_REDIR_PROTO, + + TOK_IPV6, + TOK_FLOWID, + TOK_ICMP6TYPES, + TOK_EXT6HDR, + TOK_DSTIP6, + TOK_SRCIP6, + + TOK_IPV4, + TOK_UNREACH6, + TOK_RESET6, + + TOK_FIB, + TOK_SETFIB, + TOK_LOOKUP, + TOK_SOCKARG, + TOK_SETDSCP, + TOK_FLOW, + TOK_IFLIST, + /* Table tokens */ + TOK_CREATE, + TOK_DESTROY, + TOK_LIST, + TOK_INFO, + TOK_DETAIL, + TOK_MODIFY, + TOK_FLUSH, + TOK_SWAP, + TOK_ADD, + TOK_DEL, + TOK_VALTYPE, + TOK_ALGO, + TOK_TALIST, + TOK_ATOMIC, + TOK_LOCK, + TOK_UNLOCK, + TOK_VLIST, + TOK_OLIST, +}; + +/* + * the following macro returns an error message if we run out of + * arguments. + */ +#define NEED(_p, msg) {if (!_p) errx(EX_USAGE, msg);} +#define NEED1(msg) {if (!(*av)) errx(EX_USAGE, msg);} + +struct buf_pr { + char *buf; /* allocated buffer */ + char *ptr; /* current pointer */ + size_t size; /* total buffer size */ + size_t avail; /* available storage */ + size_t needed; /* length needed */ +}; + +int pr_u64(struct buf_pr *bp, uint64_t *pd, int width); +int bp_alloc(struct buf_pr *b, size_t size); +void bp_free(struct buf_pr *b); +int bprintf(struct buf_pr *b, char *format, ...); + + +/* memory allocation support */ +void *safe_calloc(size_t number, size_t size); +void *safe_realloc(void *ptr, size_t size); + +/* string comparison functions used for historical compatibility */ +int _substrcmp(const char *str1, const char* str2); +int _substrcmp2(const char *str1, const char* str2, const char* str3); +int stringnum_cmp(const char *a, const char *b); + +/* utility functions */ +int match_token(struct _s_x *table, const char *string); +int match_token_relaxed(struct _s_x *table, const char *string); +int get_token(struct _s_x *table, const char *string, const char *errbase); +char const *match_value(struct _s_x *p, int value); +size_t concat_tokens(char *buf, size_t bufsize, struct _s_x *table, + char *delimiter); +int fill_flags(struct _s_x *flags, char *p, char **e, uint32_t *set, + uint32_t *clear); +void print_flags_buffer(char *buf, size_t sz, struct _s_x *list, uint32_t set); + +struct _ip_fw3_opheader; +int do_cmd(int optname, void *optval, uintptr_t optlen); +int do_set3(int optname, struct _ip_fw3_opheader *op3, uintptr_t optlen); +int do_get3(int optname, struct _ip_fw3_opheader *op3, size_t *optlen); + +struct in6_addr; +void n2mask(struct in6_addr *mask, int n); +int contigmask(uint8_t *p, int len); + +/* + * Forward declarations to avoid include way too many headers. + * C does not allow duplicated typedefs, so we use the base struct + * that the typedef points to. + * Should the typedefs use a different type, the compiler will + * still detect the change when compiling the body of the + * functions involved, so we do not lose error checking. + */ +struct _ipfw_insn; +struct _ipfw_insn_altq; +struct _ipfw_insn_u32; +struct _ipfw_insn_ip6; +struct _ipfw_insn_icmp6; + +/* + * The reserved set numer. This is a constant in ip_fw.h + * but we store it in a variable so other files do not depend + * in that header just for one constant. + */ +extern int resvd_set_number; + +/* first-level command handlers */ +void ipfw_add(char *av[]); +void ipfw_show_nat(int ac, char **av); +void ipfw_config_pipe(int ac, char **av); +void ipfw_config_nat(int ac, char **av); +void ipfw_sets_handler(char *av[]); +void ipfw_table_handler(int ac, char *av[]); +void ipfw_sysctl_handler(char *av[], int which); +void ipfw_delete(char *av[]); +void ipfw_flush(int force); +void ipfw_zero(int ac, char *av[], int optname); +void ipfw_list(int ac, char *av[], int show_counters); +void ipfw_internal_handler(int ac, char *av[]); +int ipfw_check_object_name(const char *name); + +#ifdef PF +/* altq.c */ +void altq_set_enabled(int enabled); +u_int32_t altq_name_to_qid(const char *name); +void print_altq_cmd(struct buf_pr *bp, struct _ipfw_insn_altq *altqptr); +#else +#define NO_ALTQ +#endif + +/* dummynet.c */ +void dummynet_list(int ac, char *av[], int show_counters); +void dummynet_flush(void); +int ipfw_delete_pipe(int pipe_or_queue, int n); + +/* ipv6.c */ +void print_unreach6_code(struct buf_pr *bp, uint16_t code); +void print_ip6(struct buf_pr *bp, struct _ipfw_insn_ip6 *cmd, char const *s); +void print_flow6id(struct buf_pr *bp, struct _ipfw_insn_u32 *cmd); +void print_icmp6types(struct buf_pr *bp, struct _ipfw_insn_u32 *cmd); +void print_ext6hdr(struct buf_pr *bp, struct _ipfw_insn *cmd ); + +struct _ipfw_insn *add_srcip6(struct _ipfw_insn *cmd, char *av, int cblen); +struct _ipfw_insn *add_dstip6(struct _ipfw_insn *cmd, char *av, int cblen); + +void fill_flow6(struct _ipfw_insn_u32 *cmd, char *av, int cblen); +void fill_unreach6_code(u_short *codep, char *str); +void fill_icmp6types(struct _ipfw_insn_icmp6 *cmd, char *av, int cblen); +int fill_ext6hdr(struct _ipfw_insn *cmd, char *av); + +/* ipfw2.c */ +void bp_flush(struct buf_pr *b); + +/* tables.c */ +struct _ipfw_obj_ctlv; +int table_check_name(const char *tablename); +void ipfw_list_ta(int ac, char *av[]); +void ipfw_list_values(int ac, char *av[]); + +#ifdef FSTACK +int ff_socket(int domain, int type, int protocol); +int ff_getsockopt(int sockfd, int level, int optname, + void *optval, socklen_t *optlen); +int ff_setsockopt(int sockfd, int level, int optname, + const void *optval, socklen_t optlen); + +#define socket(a,b,c) ff_socket(a,b,c) +#define setsockopt(a,b,c,d,e) ff_setsockopt(a,b,c,d,e) +#define getsockopt(a,b,c,d,e) ff_getsockopt(a,b,c,d,e) +#endif + diff --git a/tools/ipfw/ipv6.c b/tools/ipfw/ipv6.c new file mode 100644 index 000000000..6d884ee91 --- /dev/null +++ b/tools/ipfw/ipv6.c @@ -0,0 +1,536 @@ +/* + * Copyright (c) 2002-2003 Luigi Rizzo + * Copyright (c) 1996 Alex Nash, Paul Traina, Poul-Henning Kamp + * Copyright (c) 1994 Ugen J.S.Antsilevich + * + * Idea and grammar partially left from: + * Copyright (c) 1993 Daniel Boulet + * + * Redistribution and use in source forms, with and without modification, + * are permitted provided that this entire comment appears intact. + * + * Redistribution in binary form may occur without any restrictions. + * Obviously, it would be nice if you gave credit where credit is due + * but requiring it would be too onerous. + * + * This software is provided ``AS IS'' without any warranties of any kind. + * + * NEW command line interface for IP firewall facility + * + * $FreeBSD$ + * + * ipv6 support + */ + +#include +#include + +#include "ipfw2.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define CHECK_LENGTH(v, len) do { \ + if ((v) < (len)) \ + errx(EX_DATAERR, "Rule too long"); \ + } while (0) + +static struct _s_x icmp6codes[] = { + { "no-route", ICMP6_DST_UNREACH_NOROUTE }, + { "admin-prohib", ICMP6_DST_UNREACH_ADMIN }, + { "address", ICMP6_DST_UNREACH_ADDR }, + { "port", ICMP6_DST_UNREACH_NOPORT }, + { NULL, 0 } +}; + +void +fill_unreach6_code(u_short *codep, char *str) +{ + int val; + char *s; + + val = strtoul(str, &s, 0); + if (s == str || *s != '\0' || val >= 0x100) + val = match_token(icmp6codes, str); + if (val < 0) + errx(EX_DATAERR, "unknown ICMPv6 unreachable code ``%s''", str); + *codep = val; + return; +} + +void +print_unreach6_code(struct buf_pr *bp, uint16_t code) +{ + char const *s = match_value(icmp6codes, code); + + if (s != NULL) + bprintf(bp, "unreach6 %s", s); + else + bprintf(bp, "unreach6 %u", code); +} + +/* + * Print the ip address contained in a command. + */ +void +print_ip6(struct buf_pr *bp, ipfw_insn_ip6 *cmd, char const *s) +{ + struct hostent *he = NULL; + int len = F_LEN((ipfw_insn *) cmd) - 1; + struct in6_addr *a = &(cmd->addr6); + char trad[255]; + + bprintf(bp, "%s%s ", cmd->o.len & F_NOT ? " not": "", s); + + if (cmd->o.opcode == O_IP6_SRC_ME || cmd->o.opcode == O_IP6_DST_ME) { + bprintf(bp, "me6"); + return; + } + if (cmd->o.opcode == O_IP6) { + bprintf(bp, " ip6"); + return; + } + + /* + * len == 4 indicates a single IP, whereas lists of 1 or more + * addr/mask pairs have len = (2n+1). We convert len to n so we + * use that to count the number of entries. + */ + + for (len = len / 4; len > 0; len -= 2, a += 2) { + int mb = /* mask length */ + (cmd->o.opcode == O_IP6_SRC || cmd->o.opcode == O_IP6_DST) ? + 128 : contigmask((uint8_t *)&(a[1]), 128); + + if (mb == 128 && co.do_resolv) + he = gethostbyaddr((char *)a, sizeof(*a), AF_INET6); + if (he != NULL) /* resolved to name */ + bprintf(bp, "%s", he->h_name); + else if (mb == 0) /* any */ + bprintf(bp, "any"); + else { /* numeric IP followed by some kind of mask */ + if (inet_ntop(AF_INET6, a, trad, sizeof( trad ) ) == NULL) + bprintf(bp, "Error ntop in print_ip6\n"); + bprintf(bp, "%s", trad ); + if (mb < 0) /* XXX not really legal... */ + bprintf(bp, ":%s", + inet_ntop(AF_INET6, &a[1], trad, sizeof(trad))); + else if (mb < 128) + bprintf(bp, "/%d", mb); + } + if (len > 2) + bprintf(bp, ","); + } +} + +void +fill_icmp6types(ipfw_insn_icmp6 *cmd, char *av, int cblen) +{ + uint8_t type; + + CHECK_LENGTH(cblen, F_INSN_SIZE(ipfw_insn_icmp6)); + + bzero(cmd, sizeof(*cmd)); + while (*av) { + if (*av == ',') + av++; + type = strtoul(av, &av, 0); + if (*av != ',' && *av != '\0') + errx(EX_DATAERR, "invalid ICMP6 type"); + /* + * XXX: shouldn't this be 0xFF? I can't see any reason why + * we shouldn't be able to filter all possiable values + * regardless of the ability of the rest of the kernel to do + * anything useful with them. + */ + if (type > ICMP6_MAXTYPE) + errx(EX_DATAERR, "ICMP6 type out of range"); + cmd->d[type / 32] |= ( 1 << (type % 32)); + } + cmd->o.opcode = O_ICMP6TYPE; + cmd->o.len |= F_INSN_SIZE(ipfw_insn_icmp6); +} + + +void +print_icmp6types(struct buf_pr *bp, ipfw_insn_u32 *cmd) +{ + int i, j; + char sep= ' '; + + bprintf(bp, " ip6 icmp6types"); + for (i = 0; i < 7; i++) + for (j=0; j < 32; ++j) { + if ( (cmd->d[i] & (1 << (j))) == 0) + continue; + bprintf(bp, "%c%d", sep, (i*32 + j)); + sep = ','; + } +} + +void +print_flow6id(struct buf_pr *bp, ipfw_insn_u32 *cmd) +{ + uint16_t i, limit = cmd->o.arg1; + char sep = ','; + + bprintf(bp, " flow-id "); + for( i=0; i < limit; ++i) { + if (i == limit - 1) + sep = ' '; + bprintf(bp, "%d%c", cmd->d[i], sep); + } +} + +/* structure and define for the extension header in ipv6 */ +static struct _s_x ext6hdrcodes[] = { + { "frag", EXT_FRAGMENT }, + { "hopopt", EXT_HOPOPTS }, + { "route", EXT_ROUTING }, + { "dstopt", EXT_DSTOPTS }, + { "ah", EXT_AH }, + { "esp", EXT_ESP }, + { "rthdr0", EXT_RTHDR0 }, + { "rthdr2", EXT_RTHDR2 }, + { NULL, 0 } +}; + +/* fills command for the extension header filtering */ +int +fill_ext6hdr( ipfw_insn *cmd, char *av) +{ + int tok; + char *s = av; + + cmd->arg1 = 0; + + while(s) { + av = strsep( &s, ",") ; + tok = match_token(ext6hdrcodes, av); + switch (tok) { + case EXT_FRAGMENT: + cmd->arg1 |= EXT_FRAGMENT; + break; + + case EXT_HOPOPTS: + cmd->arg1 |= EXT_HOPOPTS; + break; + + case EXT_ROUTING: + cmd->arg1 |= EXT_ROUTING; + break; + + case EXT_DSTOPTS: + cmd->arg1 |= EXT_DSTOPTS; + break; + + case EXT_AH: + cmd->arg1 |= EXT_AH; + break; + + case EXT_ESP: + cmd->arg1 |= EXT_ESP; + break; + + case EXT_RTHDR0: + cmd->arg1 |= EXT_RTHDR0; + break; + + case EXT_RTHDR2: + cmd->arg1 |= EXT_RTHDR2; + break; + + default: + errx( EX_DATAERR, "invalid option for ipv6 exten header" ); + break; + } + } + if (cmd->arg1 == 0 ) + return 0; + cmd->opcode = O_EXT_HDR; + cmd->len |= F_INSN_SIZE( ipfw_insn ); + return 1; +} + +void +print_ext6hdr(struct buf_pr *bp, ipfw_insn *cmd ) +{ + char sep = ' '; + + bprintf(bp, " extension header:"); + if (cmd->arg1 & EXT_FRAGMENT ) { + bprintf(bp, "%cfragmentation", sep); + sep = ','; + } + if (cmd->arg1 & EXT_HOPOPTS ) { + bprintf(bp, "%chop options", sep); + sep = ','; + } + if (cmd->arg1 & EXT_ROUTING ) { + bprintf(bp, "%crouting options", sep); + sep = ','; + } + if (cmd->arg1 & EXT_RTHDR0 ) { + bprintf(bp, "%crthdr0", sep); + sep = ','; + } + if (cmd->arg1 & EXT_RTHDR2 ) { + bprintf(bp, "%crthdr2", sep); + sep = ','; + } + if (cmd->arg1 & EXT_DSTOPTS ) { + bprintf(bp, "%cdestination options", sep); + sep = ','; + } + if (cmd->arg1 & EXT_AH ) { + bprintf(bp, "%cauthentication header", sep); + sep = ','; + } + if (cmd->arg1 & EXT_ESP ) { + bprintf(bp, "%cencapsulated security payload", sep); + } +} + +/* Try to find ipv6 address by hostname */ +static int +lookup_host6 (char *host, struct in6_addr *ip6addr) +{ + struct hostent *he; + + if (!inet_pton(AF_INET6, host, ip6addr)) { + if ((he = gethostbyname2(host, AF_INET6)) == NULL) + return(-1); + memcpy(ip6addr, he->h_addr_list[0], sizeof( struct in6_addr)); + } + return(0); +} + + +/* + * fill the addr and mask fields in the instruction as appropriate from av. + * Update length as appropriate. + * The following formats are allowed: + * any matches any IP6. Actually returns an empty instruction. + * me returns O_IP6_*_ME + * + * 03f1::234:123:0342 single IP6 address + * 03f1::234:123:0342/24 address/mask + * 03f1::234:123:0342/24,03f1::234:123:0343/ List of address + * + * Set of address (as in ipv6) not supported because ipv6 address + * are typically random past the initial prefix. + * Return 1 on success, 0 on failure. + */ +static int +fill_ip6(ipfw_insn_ip6 *cmd, char *av, int cblen) +{ + int len = 0; + struct in6_addr *d = &(cmd->addr6); + /* + * Needed for multiple address. + * Note d[1] points to struct in6_add r mask6 of cmd + */ + + cmd->o.len &= ~F_LEN_MASK; /* zero len */ + + if (strcmp(av, "any") == 0) + return (1); + + + if (strcmp(av, "me") == 0) { /* Set the data for "me" opt*/ + cmd->o.len |= F_INSN_SIZE(ipfw_insn); + return (1); + } + + if (strcmp(av, "me6") == 0) { /* Set the data for "me" opt*/ + cmd->o.len |= F_INSN_SIZE(ipfw_insn); + return (1); + } + + if (strncmp(av, "table(", 6) == 0) { + char *p = strchr(av + 6, ','); + uint32_t *dm = ((ipfw_insn_u32 *)cmd)->d; + + if (p) + *p++ = '\0'; + cmd->o.opcode = O_IP_DST_LOOKUP; + cmd->o.arg1 = strtoul(av + 6, NULL, 0); + if (p) { + cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32); + dm[0] = strtoul(p, NULL, 0); + } else + cmd->o.len |= F_INSN_SIZE(ipfw_insn); + return (1); + } + + av = strdup(av); + while (av) { + /* + * After the address we can have '/' indicating a mask, + * or ',' indicating another address follows. + */ + + char *p; + int masklen; + char md = '\0'; + + CHECK_LENGTH(cblen, 1 + len + 2 * F_INSN_SIZE(struct in6_addr)); + + if ((p = strpbrk(av, "/,")) ) { + md = *p; /* save the separator */ + *p = '\0'; /* terminate address string */ + p++; /* and skip past it */ + } + /* now p points to NULL, mask or next entry */ + + /* lookup stores address in *d as a side effect */ + if (lookup_host6(av, d) != 0) { + /* XXX: failed. Free memory and go */ + errx(EX_DATAERR, "bad address \"%s\"", av); + } + /* next, look at the mask, if any */ + masklen = (md == '/') ? atoi(p) : 128; + if (masklen > 128 || masklen < 0) + errx(EX_DATAERR, "bad width \"%s\''", p); + else + n2mask(&d[1], masklen); + + APPLY_MASK(d, &d[1]) /* mask base address with mask */ + + /* find next separator */ + + if (md == '/') { /* find separator past the mask */ + p = strpbrk(p, ","); + if (p != NULL) + p++; + } + av = p; + + /* Check this entry */ + if (masklen == 0) { + /* + * 'any' turns the entire list into a NOP. + * 'not any' never matches, so it is removed from the + * list unless it is the only item, in which case we + * report an error. + */ + if (cmd->o.len & F_NOT && av == NULL && len == 0) + errx(EX_DATAERR, "not any never matches"); + continue; + } + + /* + * A single IP can be stored alone + */ + if (masklen == 128 && av == NULL && len == 0) { + len = F_INSN_SIZE(struct in6_addr); + break; + } + + /* Update length and pointer to arguments */ + len += F_INSN_SIZE(struct in6_addr)*2; + d += 2; + } /* end while */ + + /* + * Total length of the command, remember that 1 is the size of + * the base command. + */ + if (len + 1 > F_LEN_MASK) + errx(EX_DATAERR, "address list too long"); + cmd->o.len |= len+1; + free(av); + return (1); +} + +/* + * fills command for ipv6 flow-id filtering + * note that the 20 bit flow number is stored in a array of u_int32_t + * it's supported lists of flow-id, so in the o.arg1 we store how many + * additional flow-id we want to filter, the basic is 1 + */ +void +fill_flow6( ipfw_insn_u32 *cmd, char *av, int cblen) +{ + u_int32_t type; /* Current flow number */ + u_int16_t nflow = 0; /* Current flow index */ + char *s = av; + cmd->d[0] = 0; /* Initializing the base number*/ + + while (s) { + CHECK_LENGTH(cblen, F_INSN_SIZE(ipfw_insn_u32) + nflow + 1); + + av = strsep( &s, ",") ; + type = strtoul(av, &av, 0); + if (*av != ',' && *av != '\0') + errx(EX_DATAERR, "invalid ipv6 flow number %s", av); + if (type > 0xfffff) + errx(EX_DATAERR, "flow number out of range %s", av); + cmd->d[nflow] |= type; + nflow++; + } + if( nflow > 0 ) { + cmd->o.opcode = O_FLOW6ID; + cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32) + nflow; + cmd->o.arg1 = nflow; + } + else { + errx(EX_DATAERR, "invalid ipv6 flow number %s", av); + } +} + +ipfw_insn * +add_srcip6(ipfw_insn *cmd, char *av, int cblen) +{ + + fill_ip6((ipfw_insn_ip6 *)cmd, av, cblen); + if (cmd->opcode == O_IP_DST_SET) /* set */ + cmd->opcode = O_IP_SRC_SET; + else if (cmd->opcode == O_IP_DST_LOOKUP) /* table */ + cmd->opcode = O_IP_SRC_LOOKUP; + else if (F_LEN(cmd) == 0) { /* any */ + } else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn)) { /* "me" */ + cmd->opcode = O_IP6_SRC_ME; + } else if (F_LEN(cmd) == + (F_INSN_SIZE(struct in6_addr) + F_INSN_SIZE(ipfw_insn))) { + /* single IP, no mask*/ + cmd->opcode = O_IP6_SRC; + } else { /* addr/mask opt */ + cmd->opcode = O_IP6_SRC_MASK; + } + return cmd; +} + +ipfw_insn * +add_dstip6(ipfw_insn *cmd, char *av, int cblen) +{ + + fill_ip6((ipfw_insn_ip6 *)cmd, av, cblen); + if (cmd->opcode == O_IP_DST_SET) /* set */ + ; + else if (cmd->opcode == O_IP_DST_LOOKUP) /* table */ + ; + else if (F_LEN(cmd) == 0) { /* any */ + } else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn)) { /* "me" */ + cmd->opcode = O_IP6_DST_ME; + } else if (F_LEN(cmd) == + (F_INSN_SIZE(struct in6_addr) + F_INSN_SIZE(ipfw_insn))) { + /* single IP, no mask*/ + cmd->opcode = O_IP6_DST; + } else { /* addr/mask opt */ + cmd->opcode = O_IP6_DST_MASK; + } + return cmd; +} diff --git a/tools/ipfw/main.c b/tools/ipfw/main.c new file mode 100644 index 000000000..e8cb3d122 --- /dev/null +++ b/tools/ipfw/main.c @@ -0,0 +1,663 @@ +/* + * Copyright (c) 2002-2003,2010 Luigi Rizzo + * Copyright (c) 1996 Alex Nash, Paul Traina, Poul-Henning Kamp + * Copyright (c) 1994 Ugen J.S.Antsilevich + * + * Idea and grammar partially left from: + * Copyright (c) 1993 Daniel Boulet + * + * Redistribution and use in source forms, with and without modification, + * are permitted provided that this entire comment appears intact. + * + * Redistribution in binary form may occur without any restrictions. + * Obviously, it would be nice if you gave credit where credit is due + * but requiring it would be too onerous. + * + * This software is provided ``AS IS'' without any warranties of any kind. + * + * Command line interface for IP firewall facility + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef FSTACK +#include +#include "ff_ipc.h" +#endif + +#include "ipfw2.h" + +static void +help(void) +{ + fprintf(stderr, +"ipfw syntax summary (but please do read the ipfw(8) manpage):\n\n" +#ifndef FSTACK +"\tipfw [-abcdefhnNqStTv] \n\n" +#else +"\tipfw -P [-abcdefhnNqStTv] \n\n" +#endif +"where is one of the following:\n\n" +"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" +"set N {show|list|zero|resetlog|delete} [N{,N}] | flush\n" +"table N {add ip[/bits] [value] | delete ip[/bits] | flush | list}\n" +"table all {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 | nat N | setfib FIB | reass\n" +"PARAMS: [log [logamount LOGLIMIT]] [altq QUEUE_NAME]\n" +"ADDR: [ MAC dst src ether_type ] \n" +" [ ip from IPADDR [ PORT ] to IPADDR [ PORTLIST ] ]\n" +" [ ipv6|ip6 from IP6ADDR [ PORT ] to IP6ADDR [ PORTLIST ] ]\n" +"IPADDR: [not] { any | me | ip/bits{x,y,z} | table(t[,v]) | IPLIST }\n" +"IP6ADDR: [not] { any | me | me6 | ip6/bits | IP6LIST }\n" +"IP6LIST: { ip6 | ip6/bits }[,IP6LIST]\n" +"IPLIST: { ip | ip/bits | ip:mask }[,IPLIST]\n" +"OPTION_LIST: OPTION [OPTION_LIST]\n" +"OPTION: bridged | diverted | diverted-loopback | diverted-output |\n" +" {dst-ip|src-ip} IPADDR | {dst-ip6|src-ip6|dst-ipv6|src-ipv6} IP6ADDR |\n" +" {dst-port|src-port} LIST |\n" +" estab | frag | {gid|uid} N | icmptypes LIST | in | out | ipid LIST |\n" +" iplen LIST | ipoptions SPEC | ipprecedence | ipsec | iptos SPEC |\n" +" ipttl LIST | ipversion VER | keep-state | layer2 | limit ... |\n" +" icmp6types LIST | ext6hdr LIST | flow-id N[,N] | fib FIB |\n" +" mac ... | mac-type LIST | proto LIST | {recv|xmit|via} {IF|IPADDR} |\n" +" setup | {tcpack|tcpseq|tcpwin} NN | tcpflags SPEC | tcpoptions SPEC |\n" +" tcpdatalen LIST | verrevpath | versrcreach | antispoof\n" +); + + exit(0); +} + +/* + * Called with the arguments, including program name because getopt + * wants it to be present. + * Returns 0 if successful, 1 if empty command, errx() in case of errors. + * First thing we do is process parameters creating an argv[] array + * which includes the program name and a NULL entry at the end. + * If we are called with a single string, we split it on whitespace. + * Also, arguments with a trailing ',' are joined to the next one. + * The pointers (av[]) and data are in a single chunk of memory. + * av[0] points to the original program name, all other entries + * point into the allocated chunk. + */ +static int +ipfw_main(int oldac, char **oldav) +{ + int ch, ac; + const char *errstr; + char **av, **save_av; + int do_acct = 0; /* Show packet/byte count */ + int try_next = 0; /* set if pipe cmd not found */ + int av_size; /* compute the av size */ + char *av_p; /* used to build the av list */ + +#define WHITESP " \t\f\v\n\r" + if (oldac < 2) + return 1; /* need at least one argument */ + + if (oldac == 2) { + /* + * If we are called with one argument, try to split it into + * words for subsequent parsing. Spaces after a ',' are + * removed by copying the string in-place. + */ + char *arg = oldav[1]; /* The string is the first arg. */ + int l = strlen(arg); + int copy = 0; /* 1 if we need to copy, 0 otherwise */ + int i, j; + + for (i = j = 0; i < l; i++) { + if (arg[i] == '#') /* comment marker */ + break; + if (copy) { + arg[j++] = arg[i]; + copy = !strchr("," WHITESP, arg[i]); + } else { + copy = !strchr(WHITESP, arg[i]); + if (copy) + arg[j++] = arg[i]; + } + } + if (!copy && j > 0) /* last char was a 'blank', remove it */ + j--; + l = j; /* the new argument length */ + arg[j++] = '\0'; + if (l == 0) /* empty string! */ + return 1; + + /* + * First, count number of arguments. Because of the previous + * processing, this is just the number of blanks plus 1. + */ + for (i = 0, ac = 1; i < l; i++) + if (strchr(WHITESP, arg[i]) != NULL) + ac++; + + /* + * Allocate the argument list structure as a single block + * of memory, containing pointers and the argument + * strings. We include one entry for the program name + * because getopt expects it, and a NULL at the end + * to simplify further parsing. + */ + ac++; /* add 1 for the program name */ + av_size = (ac+1) * sizeof(char *) + l + 1; + av = safe_calloc(av_size, 1); + + /* + * Init the argument pointer to the end of the array + * and copy arguments from arg[] to av[]. For each one, + * j is the initial character, i is the one past the end. + */ + av_p = (char *)&av[ac+1]; + for (ac = 1, i = j = 0; i < l; i++) { + if (strchr(WHITESP, arg[i]) != NULL || i == l-1) { + if (i == l-1) + i++; + bcopy(arg+j, av_p, i-j); + av[ac] = av_p; + av_p += i-j; /* the length of the string */ + *av_p++ = '\0'; + ac++; + j = i + 1; + } + } + } else { + /* + * If an argument ends with ',' join with the next one. + */ + int first, i, l=0; + + /* + * Allocate the argument list structure as a single block + * of memory, containing both pointers and the argument + * strings. We include some space for the program name + * because getopt expects it. + * We add an extra pointer to the end of the array, + * to make simpler further parsing. + */ + for (i=0; i= 2 && !strcmp(av[1], "sysctl")) { + char *s; + int i; + + if (ac != 3) { + printf( "sysctl emulation usage:\n" + " ipfw sysctl name[=value]\n" + " ipfw sysctl -a\n"); + return 0; + } + s = strchr(av[2], '='); + if (s == NULL) { + s = !strcmp(av[2], "-a") ? NULL : av[2]; + sysctlbyname(s, NULL, NULL, NULL, 0); + } else { /* ipfw sysctl x.y.z=value */ + /* assume an INT value, will extend later */ + if (s[1] == '\0') { + printf("ipfw sysctl: missing value\n\n"); + return 0; + } + *s = '\0'; + i = strtol(s+1, NULL, 0); + sysctlbyname(av[2], NULL, NULL, &i, sizeof(int)); + } + return 0; + } +#endif + + /* Save arguments for final freeing of memory. */ + save_av = av; + + optind = optreset = 1; /* restart getopt() */ +#ifndef FSTACK + while ((ch = getopt(ac, av, "abcdefhinNp:qs:STtv")) != -1) +#else + while ((ch = getopt(ac, av, "abcdefhinNp:qs:STtvP:")) != -1) +#endif + switch (ch) { + case 'a': + do_acct = 1; + break; + + case 'b': + co.comment_only = 1; + co.do_compact = 1; + break; + + case 'c': + co.do_compact = 1; + break; + + case 'd': + co.do_dynamic = 1; + break; + + case 'e': + co.do_expired = 1; + break; + + case 'f': + co.do_force = 1; + break; + + case 'h': /* help */ + free(save_av); + help(); + break; /* NOTREACHED */ + + case 'i': + co.do_value_as_ip = 1; + break; + + case 'n': + co.test_only = 1; + break; + + case 'N': + co.do_resolv = 1; + break; + + case 'p': + errx(EX_USAGE, "An absolute pathname must be used " + "with -p option."); + /* NOTREACHED */ + + case 'q': + co.do_quiet = 1; + break; + + case 's': /* sort */ + co.do_sort = atoi(optarg); + break; + + case 'S': + co.show_sets = 1; + break; + + case 't': + co.do_time = 1; + break; + + case 'T': + co.do_time = 2; /* numeric timestamp */ + break; + + case 'v': /* verbose */ + co.verbose = 1; + break; +#ifdef FSTACK + case 'P': + ff_set_proc_id(atoi(optarg)); + break; +#endif + default: + free(save_av); + return 1; + } + + ac -= optind; + av += optind; + NEED1("bad arguments, for usage summary ``ipfw''"); + + /* + * An undocumented behaviour of ipfw1 was to allow rule numbers first, + * e.g. "100 add allow ..." instead of "add 100 allow ...". + * In case, swap first and second argument to get the normal form. + */ + if (ac > 1 && isdigit(*av[0])) { + char *p = av[0]; + + av[0] = av[1]; + av[1] = p; + } + + /* + * Optional: pipe, queue or nat. + */ + co.do_nat = 0; + co.do_pipe = 0; + co.use_set = 0; + if (!strncmp(*av, "nat", strlen(*av))) + co.do_nat = 1; + else if (!strncmp(*av, "pipe", strlen(*av))) + co.do_pipe = 1; + else if (_substrcmp(*av, "queue") == 0) + co.do_pipe = 2; + else if (_substrcmp(*av, "flowset") == 0) + co.do_pipe = 2; + else if (_substrcmp(*av, "sched") == 0) + co.do_pipe = 3; + else if (!strncmp(*av, "set", strlen(*av))) { + if (ac > 1 && isdigit(av[1][0])) { + co.use_set = strtonum(av[1], 0, resvd_set_number, + &errstr); + if (errstr) + errx(EX_DATAERR, + "invalid set number %s\n", av[1]); + ac -= 2; av += 2; co.use_set++; + } + } + + if (co.do_pipe || co.do_nat) { + ac--; + av++; + } + NEED1("missing command"); + + /* + * 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 ((co.do_pipe || co.do_nat) && ac > 1 && isdigit(*av[0])) { + char *p = av[0]; + + av[0] = av[1]; + av[1] = p; + } + + if (co.use_set == 0) { + if (_substrcmp(*av, "add") == 0) + ipfw_add(av); + else if (co.do_nat && _substrcmp(*av, "show") == 0) + ipfw_show_nat(ac, av); + else if (co.do_pipe && _substrcmp(*av, "config") == 0) +#ifdef DUMMYNET + ipfw_config_pipe(ac, av); +#else + { + errx(EX_UNAVAILABLE, "ipfw_config_pipe not supported"); + } +#endif + else if (co.do_nat && _substrcmp(*av, "config") == 0) + ipfw_config_nat(ac, av); + else if (_substrcmp(*av, "set") == 0) + ipfw_sets_handler(av); + else if (_substrcmp(*av, "table") == 0) + ipfw_table_handler(ac, av); + else if (_substrcmp(*av, "enable") == 0) + ipfw_sysctl_handler(av, 1); + else if (_substrcmp(*av, "disable") == 0) + ipfw_sysctl_handler(av, 0); + else + try_next = 1; + } + + if (co.use_set || try_next) { + if (_substrcmp(*av, "delete") == 0) + ipfw_delete(av); + else if (_substrcmp(*av, "flush") == 0) + ipfw_flush(co.do_force); + else if (_substrcmp(*av, "zero") == 0) + ipfw_zero(ac, av, 0 /* IP_FW_ZERO */); + else if (_substrcmp(*av, "resetlog") == 0) + ipfw_zero(ac, av, 1 /* IP_FW_RESETLOG */); + else if (_substrcmp(*av, "print") == 0 || + _substrcmp(*av, "list") == 0) + ipfw_list(ac, av, do_acct); + else if (_substrcmp(*av, "show") == 0) + ipfw_list(ac, av, 1 /* show counters */); + else if (_substrcmp(*av, "table") == 0) + ipfw_table_handler(ac, av); + else if (_substrcmp(*av, "internal") == 0) + ipfw_internal_handler(ac, av); + else + errx(EX_USAGE, "bad command `%s'", *av); + } + + /* Free memory allocated in the argument parsing. */ + free(save_av); + return 0; +} + + +static void +ipfw_readfile(int ac, char *av[]) +{ +#define MAX_ARGS 32 + char buf[4096]; + char *progname = av[0]; /* original program name */ + const char *cmd = NULL; /* preprocessor name, if any */ + const char *filename = av[ac-1]; /* file to read */ + int c, lineno=0; + FILE *f = NULL; + pid_t preproc = 0; + +#ifndef FSTACK + while ((c = getopt(ac, av, "cfNnp:qS")) != -1) { +#else + while ((c = getopt(ac, av, "cfNnp:qSP:")) != -1) { +#endif + switch(c) { + case 'c': + co.do_compact = 1; + break; + + case 'f': + co.do_force = 1; + break; + + case 'N': + co.do_resolv = 1; + break; + + case 'n': + co.test_only = 1; + break; + + case 'p': + /* + * ipfw -p cmd [args] filename + * + * We are done with getopt(). All arguments + * except the filename go to the preprocessor, + * so we need to do the following: + * - check that a filename is actually present; + * - advance av by optind-1 to skip arguments + * already processed; + * - decrease ac by optind, to remove the args + * already processed and the final filename; + * - set the last entry in av[] to NULL so + * popen() can detect the end of the array; + * - set optind=ac to let getopt() terminate. + */ + if (optind == ac) + errx(EX_USAGE, "no filename argument"); + cmd = optarg; + av[ac-1] = NULL; + av += optind - 1; + ac -= optind; + optind = ac; + break; + + case 'q': + co.do_quiet = 1; + break; + + case 'S': + co.show_sets = 1; + break; + +#ifdef FSTACK + case 'P': + ff_set_proc_id(atoi(optarg)); + break; +#endif + + default: + errx(EX_USAGE, "bad arguments, for usage" + " summary ``ipfw''"); + } + + } + + if (cmd == NULL && ac != optind + 1) + errx(EX_USAGE, "extraneous filename arguments %s", av[ac-1]); + + if ((f = fopen(filename, "r")) == NULL) + err(EX_UNAVAILABLE, "fopen: %s", filename); + + if (cmd != NULL) { /* pipe through preprocessor */ + int pipedes[2]; + + if (pipe(pipedes) == -1) + err(EX_OSERR, "cannot create pipe"); + + preproc = fork(); + if (preproc == -1) + err(EX_OSERR, "cannot fork"); + + if (preproc == 0) { + /* + * Child, will run the preprocessor with the + * file on stdin and the pipe on stdout. + */ + if (dup2(fileno(f), 0) == -1 + || dup2(pipedes[1], 1) == -1) + err(EX_OSERR, "dup2()"); + fclose(f); + close(pipedes[1]); + close(pipedes[0]); + execvp(cmd, av); + err(EX_OSERR, "execvp(%s) failed", cmd); + } else { /* parent, will reopen f as the pipe */ + fclose(f); + close(pipedes[1]); + if ((f = fdopen(pipedes[0], "r")) == NULL) { + int savederrno = errno; + + (void)kill(preproc, SIGTERM); + errno = savederrno; + err(EX_OSERR, "fdopen()"); + } + } + } + + while (fgets(buf, sizeof(buf), f)) { /* read commands */ + char linename[20]; + char *args[2]; + + lineno++; + snprintf(linename, sizeof(linename), "Line %d", lineno); +#ifndef FSTACK + setprogname(linename); /* XXX */ +#endif + args[0] = progname; + args[1] = buf; + ipfw_main(2, args); + } + fclose(f); + if (cmd != NULL) { + int status; + + if (waitpid(preproc, &status, 0) == -1) + errx(EX_OSERR, "waitpid()"); + if (WIFEXITED(status) && WEXITSTATUS(status) != EX_OK) + errx(EX_UNAVAILABLE, + "preprocessor exited with status %d", + WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + errx(EX_UNAVAILABLE, + "preprocessor exited with signal %d", + WTERMSIG(status)); + } +} + +int +main(int ac, char *av[]) +{ +#if defined(_WIN32) && defined(TCC) + { + WSADATA wsaData; + int ret=0; + unsigned short wVersionRequested = MAKEWORD(2, 2); + ret = WSAStartup(wVersionRequested, &wsaData); + if (ret != 0) { + /* Tell the user that we could not find a usable */ + /* Winsock DLL. */ + printf("WSAStartup failed with error: %d\n", ret); + return 1; + } + } +#endif + /* + * If the last argument is an absolute pathname, interpret it + * as a file to be preprocessed. + */ + + if (ac > 1 && av[ac - 1][0] == '/') { + if (access(av[ac - 1], R_OK) == 0) + ipfw_readfile(ac, av); + else + err(EX_USAGE, "pathname: %s", av[ac - 1]); + } else { + if (ipfw_main(ac, av)) { + errx(EX_USAGE, + "usage: ipfw [options]\n" + "do \"ipfw -h\" or \"man ipfw\" for details"); + } + } + return EX_OK; +} diff --git a/tools/ipfw/nat.c b/tools/ipfw/nat.c new file mode 100644 index 000000000..2704ddb65 --- /dev/null +++ b/tools/ipfw/nat.c @@ -0,0 +1,1117 @@ +/* + * Copyright (c) 2002-2003 Luigi Rizzo + * Copyright (c) 1996 Alex Nash, Paul Traina, Poul-Henning Kamp + * Copyright (c) 1994 Ugen J.S.Antsilevich + * + * Idea and grammar partially left from: + * Copyright (c) 1993 Daniel Boulet + * + * Redistribution and use in source forms, with and without modification, + * are permitted provided that this entire comment appears intact. + * + * Redistribution in binary form may occur without any restrictions. + * Obviously, it would be nice if you gave credit where credit is due + * but requiring it would be too onerous. + * + * This software is provided ``AS IS'' without any warranties of any kind. + * + * NEW command line interface for IP firewall facility + * + * $FreeBSD$ + * + * In-kernel nat support + */ + +#include +#include +#include + +#include "ipfw2.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include /* def. of struct route */ +#include +#include +#include +#include + +typedef int (nat_cb_t)(struct nat44_cfg_nat *cfg, void *arg); +static void nat_show_cfg(struct nat44_cfg_nat *n, void *arg); +static void nat_show_log(struct nat44_cfg_nat *n, void *arg); +static int nat_show_data(struct nat44_cfg_nat *cfg, void *arg); +static int natname_cmp(const void *a, const void *b); +static int nat_foreach(nat_cb_t *f, void *arg, int sort); +static int nat_get_cmd(char *name, uint16_t cmd, ipfw_obj_header **ooh); + +static 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 }, + { "skip_global", TOK_SKIP_GLOBAL }, + { "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 */ +}; + + +/* + * 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 nat44_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; + + 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"); + buf = safe_calloc(1, needed); + 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 (co.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; + 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 (co.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) + n->ip.s_addr = htonl(INADDR_ANY); + else + 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; +#ifdef FSTACK + else + errx (1, "invalid addr %d", addr->s_addr); +#else + + hp = gethostbyname (str); + if (!hp) + errx (1, "unknown host %s", str); + + memcpy (addr, hp->h_addr, sizeof (struct in_addr)); +#endif +} + +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; + + if (!strcmp (str, "sctp")) + return IPPROTO_SCTP; + errx (EX_DATAERR, "unknown protocol %s. Expected sctp, 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. */ + +/* + * 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 nat44_cfg_redir) and zero or more server pool entry + * (struct nat44_cfg_spool) in buf. + * + * The format of data in buf is: + * + * nat44_cfg_nat nat44_cfg_redir nat44_cfg_spool ...... nat44_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 +estimate_redir_addr(int *ac, char ***av) +{ + size_t space = sizeof(struct nat44_cfg_redir); + char *sep = **av; + u_int c = 0; + + (void)ac; /* UNUSED */ + while ((sep = strchr(sep, ',')) != NULL) { + c++; + sep++; + } + + if (c > 0) + c++; + + space += c * sizeof(struct nat44_cfg_spool); + + return (space); +} + +static int +setup_redir_addr(char *buf, int *ac, char ***av) +{ + struct nat44_cfg_redir *r; + char *sep; + size_t space; + + r = (struct nat44_cfg_redir *)buf; + r->mode = REDIR_ADDR; + /* Skip nat44_cfg_redir at beginning of buf. */ + buf = &buf[sizeof(struct nat44_cfg_redir)]; + space = sizeof(struct nat44_cfg_redir); + + /* Extract local address. */ + if (strchr(**av, ',') != NULL) { + struct nat44_cfg_spool *spool; + + /* Setup LSNAT server pool. */ + r->laddr.s_addr = INADDR_NONE; + sep = strtok(**av, ","); + while (sep != NULL) { + spool = (struct nat44_cfg_spool *)buf; + space += sizeof(struct nat44_cfg_spool); + StrToAddr(sep, &spool->addr); + spool->port = ~0; + r->spool_cnt++; + /* Point to the next possible nat44_cfg_spool. */ + buf = &buf[sizeof(struct nat44_cfg_spool)]; + sep = strtok(NULL, ","); + } + } else + StrToAddr(**av, &r->laddr); + (*av)++; (*ac)--; + + /* Extract public address. */ + StrToAddr(**av, &r->paddr); + (*av)++; (*ac)--; + + return (space); +} + +static int +estimate_redir_port(int *ac, char ***av) +{ + size_t space = sizeof(struct nat44_cfg_redir); + char *sep = **av; + u_int c = 0; + + (void)ac; /* UNUSED */ + while ((sep = strchr(sep, ',')) != NULL) { + c++; + sep++; + } + + if (c > 0) + c++; + + space += c * sizeof(struct nat44_cfg_spool); + + return (space); +} + +static int +setup_redir_port(char *buf, int *ac, char ***av) +{ + struct nat44_cfg_redir *r; + char *sep, *protoName, *lsnat = NULL; + size_t space; + u_short numLocalPorts; + port_range portRange; + + numLocalPorts = 0; + + r = (struct nat44_cfg_redir *)buf; + r->mode = REDIR_PORT; + /* Skip nat44_cfg_redir at beginning of buf. */ + buf = &buf[sizeof(struct nat44_cfg_redir)]; + space = sizeof(struct nat44_cfg_redir); + + /* + * Extract protocol. + */ + r->proto = StrToProto(**av); + protoName = **av; + (*av)++; (*ac)--; + + /* + * Extract local address. + */ + if (strchr(**av, ',') != NULL) { + r->laddr.s_addr = INADDR_NONE; + r->lport = ~0; + numLocalPorts = 1; + lsnat = **av; + } else { + /* + * The sctp nat does not allow the port numbers to be mapped to + * new port numbers. Therefore, no ports are to be specified + * in the target port field. + */ + if (r->proto == IPPROTO_SCTP) { + if (strchr(**av, ':')) + errx(EX_DATAERR, "redirect_port:" + "port numbers do not change in sctp, so do " + "not specify them as part of the target"); + else + StrToAddr(**av, &r->laddr); + } 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); + } + } + (*av)++; (*ac)--; + + /* + * Extract public port and optionally address. + */ + if (strchr(**av, ':') != NULL) { + 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); + if (r->proto == IPPROTO_SCTP) { /* so the logic below still works */ + numLocalPorts = GETNUMPORTS(portRange); + r->lport = r->pport; + } + r->pport_cnt = GETNUMPORTS(portRange); + (*av)++; (*ac)--; + + /* + * Extract remote address and optionally port. + */ + /* + * NB: isdigit(**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 && isdigit(***av)) { + if (strchr(**av, ':') != NULL) { + 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); + } + (*av)++; (*ac)--; + } 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 != NULL) { + struct nat44_cfg_spool *spool; + + sep = strtok(lsnat, ","); + while (sep != NULL) { + spool = (struct nat44_cfg_spool *)buf; + space += sizeof(struct nat44_cfg_spool); + /* + * The sctp nat does not allow the port numbers to + * be mapped to new port numbers. Therefore, no ports + * are to be specified in the target port field. + */ + if (r->proto == IPPROTO_SCTP) { + if (strchr (sep, ':')) { + errx(EX_DATAERR, "redirect_port:" + "port numbers do not change in " + "sctp, so do not specify them as " + "part of the target"); + } else { + StrToAddr(sep, &spool->addr); + spool->port = r->pport; + } + } else { + if (StrToAddrAndPortRange(sep, &spool->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"); + spool->port = GETLOPORT(portRange); + } + r->spool_cnt++; + /* Point to the next possible nat44_cfg_spool. */ + buf = &buf[sizeof(struct nat44_cfg_spool)]; + sep = strtok(NULL, ","); + } + } + + return (space); +} + +static int +setup_redir_proto(char *buf, int *ac, char ***av) +{ + struct nat44_cfg_redir *r; + struct protoent *protoent; + size_t space; + + r = (struct nat44_cfg_redir *)buf; + r->mode = REDIR_PROTO; + /* Skip nat44_cfg_redir at beginning of buf. */ + buf = &buf[sizeof(struct nat44_cfg_redir)]; + space = sizeof(struct nat44_cfg_redir); + + /* + * Extract protocol. + */ + protoent = getprotobyname(**av); + if (protoent == NULL) + errx(EX_DATAERR, "redirect_proto: unknown protocol %s", **av); + else + r->proto = protoent->p_proto; + + (*av)++; (*ac)--; + + /* + * Extract local address. + */ + StrToAddr(**av, &r->laddr); + + (*av)++; (*ac)--; + + /* + * 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 (isdigit(***av)) { + StrToAddr(**av, &r->paddr); + (*av)++; (*ac)--; + + /* + * Extract optional remote address. + */ + /* see above in setup_redir_port() */ + if (*ac != 0 && isdigit(***av)) { + StrToAddr(**av, &r->raddr); + (*av)++; (*ac)--; + } + } + } + + return (space); +} + +static void +nat_show_log(struct nat44_cfg_nat *n, void *arg) +{ + char *buf; + + buf = (char *)(n + 1); + if (buf[0] != '\0') + printf("nat %s: %s\n", n->name, buf); +} + +static void +nat_show_cfg(struct nat44_cfg_nat *n, void *arg) +{ + int i, cnt, off; + struct nat44_cfg_redir *t; + struct nat44_cfg_spool *s; + caddr_t buf; + struct protoent *p; + + buf = (caddr_t)n; + off = sizeof(*n); + printf("ipfw nat %s config", n->name); + 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_SKIP_GLOBAL) { + printf(" skip_global"); + n->mode &= ~PKT_ALIAS_SKIP_GLOBAL; + } 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 nat44_cfg_redir *)&buf[off]; + off += sizeof(struct nat44_cfg_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 nat44_cfg_spool *)&buf[off]; + if (i) + printf(","); + else + printf(" "); + printf("%s", inet_ntoa(s->addr)); + off += sizeof(struct nat44_cfg_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 nat44_cfg_spool *)&buf[off]; + if (i) + printf(","); + printf("%s:%u", inet_ntoa(s->addr), + s->port); + off += sizeof(struct nat44_cfg_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"); +} + +void +ipfw_config_nat(int ac, char **av) +{ + ipfw_obj_header *oh; + struct nat44_cfg_nat *n; /* Nat instance configuration. */ + int i, off, tok, ac1; + char *id, *buf, **av1, *end; + size_t len; + + av++; + ac--; + /* Nat id. */ + if (ac == 0) + errx(EX_DATAERR, "missing nat id"); + id = *av; + i = (int)strtol(id, &end, 0); + if (i <= 0 || *end != '\0') + errx(EX_DATAERR, "illegal nat id: %s", id); + av++; + ac--; + if (ac == 0) + errx(EX_DATAERR, "missing option"); + + len = sizeof(*oh) + sizeof(*n); + ac1 = ac; + av1 = av; + while (ac1 > 0) { + tok = match_token(nat_params, *av1); + ac1--; + av1++; + switch (tok) { + case TOK_IP: + case TOK_IF: + ac1--; + av1++; + break; + case TOK_ALOG: + case TOK_DENY_INC: + case TOK_SAME_PORTS: + case TOK_SKIP_GLOBAL: + case TOK_UNREG_ONLY: + case TOK_RESET_ADDR: + case TOK_ALIAS_REV: + case TOK_PROXY_ONLY: + break; + case TOK_REDIR_ADDR: + if (ac1 < 2) + errx(EX_DATAERR, "redirect_addr: " + "not enough arguments"); + len += estimate_redir_addr(&ac1, &av1); + av1 += 2; + ac1 -= 2; + break; + case TOK_REDIR_PORT: + if (ac1 < 3) + errx(EX_DATAERR, "redirect_port: " + "not enough arguments"); + av1++; + ac1--; + len += estimate_redir_port(&ac1, &av1); + av1 += 2; + ac1 -= 2; + /* Skip optional remoteIP/port */ + if (ac1 != 0 && isdigit(**av1)) { + av1++; + ac1--; + } + break; + case TOK_REDIR_PROTO: + if (ac1 < 2) + errx(EX_DATAERR, "redirect_proto: " + "not enough arguments"); + len += sizeof(struct nat44_cfg_redir); + av1 += 2; + ac1 -= 2; + /* Skip optional remoteIP/port */ + if (ac1 != 0 && isdigit(**av1)) { + av1++; + ac1--; + } + if (ac1 != 0 && isdigit(**av1)) { + av1++; + ac1--; + } + break; + default: + errx(EX_DATAERR, "unrecognised option ``%s''", av1[-1]); + } + } + + if ((buf = malloc(len)) == NULL) + errx(EX_OSERR, "malloc failed"); + + /* Offset in buf: save space for header at the beginning. */ + off = sizeof(*oh) + sizeof(*n); + memset(buf, 0, len); + oh = (ipfw_obj_header *)buf; + n = (struct nat44_cfg_nat *)(oh + 1); + oh->ntlv.head.length = sizeof(oh->ntlv); + snprintf(oh->ntlv.name, sizeof(oh->ntlv.name), "%d", i); + snprintf(n->name, sizeof(n->name), "%d", i); + + 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: + if (ac == 0) + errx(EX_DATAERR, "missing option"); + 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_SKIP_GLOBAL: + n->mode |= PKT_ALIAS_SKIP_GLOBAL; + 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], &ac, &av); + break; + case TOK_REDIR_PORT: + i = setup_redir_port(&buf[off], &ac, &av); + break; + case TOK_REDIR_PROTO: + i = setup_redir_proto(&buf[off], &ac, &av); + break; + } + n->redir_cnt++; + off += i; + break; + } + } + + i = do_set3(IP_FW_NAT44_XCONFIG, &oh->opheader, len); + if (i != 0) + err(1, "setsockopt(%s)", "IP_FW_NAT44_XCONFIG"); + + if (!co.do_quiet) { + /* After every modification, we show the resultant rule. */ + int _ac = 3; + const char *_av[] = {"show", "config", id}; + ipfw_show_nat(_ac, (char **)(void *)_av); + } +} + +struct nat_list_arg { + uint16_t cmd; + int is_all; +}; + +static int +nat_show_data(struct nat44_cfg_nat *cfg, void *arg) +{ + struct nat_list_arg *nla; + ipfw_obj_header *oh; + + nla = (struct nat_list_arg *)arg; + + switch (nla->cmd) { + case IP_FW_NAT44_XGETCONFIG: + if (nat_get_cmd(cfg->name, nla->cmd, &oh) != 0) { + warnx("Error getting nat instance %s info", cfg->name); + break; + } + nat_show_cfg((struct nat44_cfg_nat *)(oh + 1), NULL); + free(oh); + break; + case IP_FW_NAT44_XGETLOG: + if (nat_get_cmd(cfg->name, nla->cmd, &oh) == 0) { + nat_show_log((struct nat44_cfg_nat *)(oh + 1), NULL); + free(oh); + break; + } + /* Handle error */ + if (nla->is_all != 0 && errno == ENOENT) + break; + warn("Error getting nat instance %s info", cfg->name); + break; + } + + return (0); +} + +/* + * Compare nat names. + * Honor number comparison. + */ +static int +natname_cmp(const void *a, const void *b) +{ + struct nat44_cfg_nat *ia, *ib; + + ia = (struct nat44_cfg_nat *)a; + ib = (struct nat44_cfg_nat *)b; + + return (stringnum_cmp(ia->name, ib->name)); +} + +/* + * Retrieves nat list from kernel, + * optionally sorts it and calls requested function for each table. + * Returns 0 on success. + */ +static int +nat_foreach(nat_cb_t *f, void *arg, int sort) +{ + ipfw_obj_lheader *olh; + struct nat44_cfg_nat *cfg; + size_t sz; + int i, error; + + /* Start with reasonable default */ + sz = sizeof(*olh) + 16 * sizeof(struct nat44_cfg_nat); + + for (;;) { + if ((olh = calloc(1, sz)) == NULL) + return (ENOMEM); + + olh->size = sz; + if (do_get3(IP_FW_NAT44_LIST_NAT, &olh->opheader, &sz) != 0) { + sz = olh->size; + free(olh); + if (errno == ENOMEM) + continue; + return (errno); + } + + if (sort != 0) + qsort(olh + 1, olh->count, olh->objsize, natname_cmp); + + cfg = (struct nat44_cfg_nat*)(olh + 1); + for (i = 0; i < olh->count; i++) { + error = f(cfg, arg); /* Ignore errors for now */ + cfg = (struct nat44_cfg_nat *)((caddr_t)cfg + + olh->objsize); + } + + free(olh); + break; + } + + return (0); +} + +static int +nat_get_cmd(char *name, uint16_t cmd, ipfw_obj_header **ooh) +{ + ipfw_obj_header *oh; + struct nat44_cfg_nat *cfg; + size_t sz; + + /* Start with reasonable default */ + sz = sizeof(*oh) + sizeof(*cfg) + 128; + + for (;;) { + if ((oh = calloc(1, sz)) == NULL) + return (ENOMEM); + cfg = (struct nat44_cfg_nat *)(oh + 1); + oh->ntlv.head.length = sizeof(oh->ntlv); + strlcpy(oh->ntlv.name, name, sizeof(oh->ntlv.name)); + strlcpy(cfg->name, name, sizeof(cfg->name)); + + if (do_get3(cmd, &oh->opheader, &sz) != 0) { + sz = cfg->size; + free(oh); + if (errno == ENOMEM) + continue; + return (errno); + } + + *ooh = oh; + break; + } + + return (0); +} + +void +ipfw_show_nat(int ac, char **av) +{ + ipfw_obj_header *oh; + char *name; + int cmd; + struct nat_list_arg nla; + + ac--; + av++; + + if (co.test_only) + return; + + /* Parse parameters. */ + cmd = 0; /* XXX: Change to IP_FW_NAT44_XGETLOG @ MFC */ + name = NULL; + for ( ; ac != 0; ac--, av++) { + if (!strncmp(av[0], "config", strlen(av[0]))) { + cmd = IP_FW_NAT44_XGETCONFIG; + continue; + } + if (strcmp(av[0], "log") == 0) { + cmd = IP_FW_NAT44_XGETLOG; + continue; + } + if (name != NULL) + err(EX_USAGE,"only one instance name may be specified"); + name = av[0]; + } + + if (cmd == 0) + errx(EX_USAGE, "Please specify action. Available: config,log"); + + if (name == NULL) { + memset(&nla, 0, sizeof(nla)); + nla.cmd = cmd; + nla.is_all = 1; + nat_foreach(nat_show_data, &nla, 1); + } else { + if (nat_get_cmd(name, cmd, &oh) != 0) + err(EX_OSERR, "Error getting nat %s instance info", name); + nat_show_cfg((struct nat44_cfg_nat *)(oh + 1), NULL); + free(oh); + } +} + diff --git a/tools/ipfw/tables.c b/tools/ipfw/tables.c new file mode 100644 index 000000000..c09938267 --- /dev/null +++ b/tools/ipfw/tables.c @@ -0,0 +1,2011 @@ +/* + * Copyright (c) 2014 Yandex LLC + * Copyright (c) 2014 Alexander V. Chernikov + * + * Redistribution and use in source forms, with and without modification, + * are permitted provided that this entire comment appears intact. + * + * Redistribution in binary form may occur without any restrictions. + * Obviously, it would be nice if you gave credit where credit is due + * but requiring it would be too onerous. + * + * This software is provided ``AS IS'' without any warranties of any kind. + * + * in-kernel ipfw tables support. + * + * $FreeBSD$ + */ + + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ipfw2.h" + +static void table_modify_record(ipfw_obj_header *oh, int ac, char *av[], + int add, int quiet, int update, int atomic); +static int table_flush(ipfw_obj_header *oh); +static int table_destroy(ipfw_obj_header *oh); +static int table_do_create(ipfw_obj_header *oh, ipfw_xtable_info *i); +static int table_do_modify(ipfw_obj_header *oh, ipfw_xtable_info *i); +static int table_do_swap(ipfw_obj_header *oh, char *second); +static void table_create(ipfw_obj_header *oh, int ac, char *av[]); +static void table_modify(ipfw_obj_header *oh, int ac, char *av[]); +static void table_lookup(ipfw_obj_header *oh, int ac, char *av[]); +static void table_lock(ipfw_obj_header *oh, int lock); +static int table_swap(ipfw_obj_header *oh, char *second); +static int table_get_info(ipfw_obj_header *oh, ipfw_xtable_info *i); +static int table_show_info(ipfw_xtable_info *i, void *arg); +static void table_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name, + uint32_t set, uint16_t uidx); + +static int table_flush_one(ipfw_xtable_info *i, void *arg); +static int table_show_one(ipfw_xtable_info *i, void *arg); +static int table_do_get_list(ipfw_xtable_info *i, ipfw_obj_header **poh); +static void table_show_list(ipfw_obj_header *oh, int need_header); +static void table_show_entry(ipfw_xtable_info *i, ipfw_obj_tentry *tent); + +static void tentry_fill_key(ipfw_obj_header *oh, ipfw_obj_tentry *tent, + char *key, int add, uint8_t *ptype, uint32_t *pvmask, ipfw_xtable_info *xi); +static void tentry_fill_value(ipfw_obj_header *oh, ipfw_obj_tentry *tent, + char *arg, uint8_t type, uint32_t vmask); +static void table_show_value(char *buf, size_t bufsize, ipfw_table_value *v, + uint32_t vmask, int print_ip); + +typedef int (table_cb_t)(ipfw_xtable_info *i, void *arg); +static int tables_foreach(table_cb_t *f, void *arg, int sort); + +#ifndef s6_addr32 +#define s6_addr32 __u6_addr.__u6_addr32 +#endif + +static struct _s_x tabletypes[] = { + { "addr", IPFW_TABLE_ADDR }, + { "iface", IPFW_TABLE_INTERFACE }, + { "number", IPFW_TABLE_NUMBER }, + { "flow", IPFW_TABLE_FLOW }, + { NULL, 0 } +}; + +static struct _s_x tablevaltypes[] = { + { "skipto", IPFW_VTYPE_SKIPTO }, + { "pipe", IPFW_VTYPE_PIPE }, + { "fib", IPFW_VTYPE_FIB }, + { "nat", IPFW_VTYPE_NAT }, + { "dscp", IPFW_VTYPE_DSCP }, + { "tag", IPFW_VTYPE_TAG }, + { "divert", IPFW_VTYPE_DIVERT }, + { "netgraph", IPFW_VTYPE_NETGRAPH }, + { "limit", IPFW_VTYPE_LIMIT }, + { "ipv4", IPFW_VTYPE_NH4 }, + { "ipv6", IPFW_VTYPE_NH6 }, + { NULL, 0 } +}; + +static struct _s_x tablecmds[] = { + { "add", TOK_ADD }, + { "delete", TOK_DEL }, + { "create", TOK_CREATE }, + { "destroy", TOK_DESTROY }, + { "flush", TOK_FLUSH }, + { "modify", TOK_MODIFY }, + { "swap", TOK_SWAP }, + { "info", TOK_INFO }, + { "detail", TOK_DETAIL }, + { "list", TOK_LIST }, + { "lookup", TOK_LOOKUP }, + { "atomic", TOK_ATOMIC }, + { "lock", TOK_LOCK }, + { "unlock", TOK_UNLOCK }, + { NULL, 0 } +}; + +static int +lookup_host (char *host, struct in_addr *ipaddr) +{ + struct hostent *he; + + if (!inet_aton(host, ipaddr)) { +#ifndef FSTACK + if ((he = gethostbyname(host)) == NULL) + return(-1); + *ipaddr = *(struct in_addr *)he->h_addr_list[0]; +#else + return (-1); +#endif + } + return(0); +} + +/* + * This one handles all table-related commands + * ipfw table NAME create ... + * ipfw table NAME modify ... + * ipfw table NAME destroy + * ipfw table NAME swap NAME + * ipfw table NAME lock + * ipfw table NAME unlock + * ipfw table NAME add addr[/masklen] [value] + * ipfw table NAME add [addr[/masklen] value] [addr[/masklen] value] .. + * ipfw table NAME delete addr[/masklen] [addr[/masklen]] .. + * ipfw table NAME lookup addr + * ipfw table {NAME | all} flush + * ipfw table {NAME | all} list + * ipfw table {NAME | all} info + * ipfw table {NAME | all} detail + */ +void +ipfw_table_handler(int ac, char *av[]) +{ + int do_add, is_all; + int atomic, error, tcmd; + ipfw_xtable_info i; + ipfw_obj_header oh; + char *tablename; + uint32_t set; + void *arg; + + memset(&oh, 0, sizeof(oh)); + is_all = 0; + if (co.use_set != 0) + set = co.use_set - 1; + else + set = 0; + + ac--; av++; + NEED1("table needs name"); + tablename = *av; + + if (table_check_name(tablename) == 0) { + table_fill_ntlv(&oh.ntlv, *av, set, 1); + oh.idx = 1; + } else { + if (strcmp(tablename, "all") == 0) + is_all = 1; + else + errx(EX_USAGE, "table name %s is invalid", tablename); + } + ac--; av++; + NEED1("table needs command"); + + tcmd = get_token(tablecmds, *av, "table command"); + /* Check if atomic operation was requested */ + atomic = 0; + if (tcmd == TOK_ATOMIC) { + ac--; av++; + NEED1("atomic needs command"); + tcmd = get_token(tablecmds, *av, "table command"); + switch (tcmd) { + case TOK_ADD: + break; + default: + errx(EX_USAGE, "atomic is not compatible with %s", *av); + } + atomic = 1; + } + + switch (tcmd) { + case TOK_LIST: + case TOK_INFO: + case TOK_DETAIL: + case TOK_FLUSH: + break; + default: + if (is_all != 0) + errx(EX_USAGE, "table name required"); + } + + switch (tcmd) { + case TOK_ADD: + case TOK_DEL: + do_add = **av == 'a'; + ac--; av++; + table_modify_record(&oh, ac, av, do_add, co.do_quiet, + co.do_quiet, atomic); + break; + case TOK_CREATE: + ac--; av++; + table_create(&oh, ac, av); + break; + case TOK_MODIFY: + ac--; av++; + table_modify(&oh, ac, av); + break; + case TOK_DESTROY: + if (table_destroy(&oh) == 0) + break; + if (errno != ESRCH) + err(EX_OSERR, "failed to destroy table %s", tablename); + /* ESRCH isn't fatal, warn if not quiet mode */ + if (co.do_quiet == 0) + warn("failed to destroy table %s", tablename); + break; + case TOK_FLUSH: + if (is_all == 0) { + if ((error = table_flush(&oh)) == 0) + break; + if (errno != ESRCH) + err(EX_OSERR, "failed to flush table %s info", + tablename); + /* ESRCH isn't fatal, warn if not quiet mode */ + if (co.do_quiet == 0) + warn("failed to flush table %s info", + tablename); + } else { + error = tables_foreach(table_flush_one, &oh, 1); + if (error != 0) + err(EX_OSERR, "failed to flush tables list"); + /* XXX: we ignore errors here */ + } + break; + case TOK_SWAP: + ac--; av++; + NEED1("second table name required"); + table_swap(&oh, *av); + break; + case TOK_LOCK: + case TOK_UNLOCK: + table_lock(&oh, (tcmd == TOK_LOCK)); + break; + case TOK_DETAIL: + case TOK_INFO: + arg = (tcmd == TOK_DETAIL) ? (void *)1 : NULL; + if (is_all == 0) { + if ((error = table_get_info(&oh, &i)) != 0) + err(EX_OSERR, "failed to request table info"); + table_show_info(&i, arg); + } else { + error = tables_foreach(table_show_info, arg, 1); + if (error != 0) + err(EX_OSERR, "failed to request tables list"); + } + break; + case TOK_LIST: + if (is_all == 0) { + ipfw_xtable_info i; + if ((error = table_get_info(&oh, &i)) != 0) + err(EX_OSERR, "failed to request table info"); + table_show_one(&i, NULL); + } else { + error = tables_foreach(table_show_one, NULL, 1); + if (error != 0) + err(EX_OSERR, "failed to request tables list"); + } + break; + case TOK_LOOKUP: + ac--; av++; + table_lookup(&oh, ac, av); + break; + } +} + +static void +table_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name, uint32_t set, + uint16_t uidx) +{ + + ntlv->head.type = IPFW_TLV_TBL_NAME; + ntlv->head.length = sizeof(ipfw_obj_ntlv); + ntlv->idx = uidx; + ntlv->set = set; + strlcpy(ntlv->name, name, sizeof(ntlv->name)); +} + +static void +table_fill_objheader(ipfw_obj_header *oh, ipfw_xtable_info *i) +{ + + oh->idx = 1; + table_fill_ntlv(&oh->ntlv, i->tablename, i->set, 1); +} + +static struct _s_x tablenewcmds[] = { + { "type", TOK_TYPE }, + { "valtype", TOK_VALTYPE }, + { "algo", TOK_ALGO }, + { "limit", TOK_LIMIT }, + { "locked", TOK_LOCK }, + { NULL, 0 } +}; + +static struct _s_x flowtypecmds[] = { + { "src-ip", IPFW_TFFLAG_SRCIP }, + { "proto", IPFW_TFFLAG_PROTO }, + { "src-port", IPFW_TFFLAG_SRCPORT }, + { "dst-ip", IPFW_TFFLAG_DSTIP }, + { "dst-port", IPFW_TFFLAG_DSTPORT }, + { NULL, 0 } +}; + +int +table_parse_type(uint8_t ttype, char *p, uint8_t *tflags) +{ + uint32_t fset, fclear; + char *e; + + /* Parse type options */ + switch(ttype) { + case IPFW_TABLE_FLOW: + fset = fclear = 0; + if (fill_flags(flowtypecmds, p, &e, &fset, &fclear) != 0) + errx(EX_USAGE, + "unable to parse flow option %s", e); + *tflags = fset; + break; + default: + return (EX_USAGE); + } + + return (0); +} + +void +table_print_type(char *tbuf, size_t size, uint8_t type, uint8_t tflags) +{ + const char *tname; + int l; + + if ((tname = match_value(tabletypes, type)) == NULL) + tname = "unknown"; + + l = snprintf(tbuf, size, "%s", tname); + tbuf += l; + size -= l; + + switch(type) { + case IPFW_TABLE_FLOW: + if (tflags != 0) { + *tbuf++ = ':'; + l--; + print_flags_buffer(tbuf, size, flowtypecmds, tflags); + } + break; + } +} + +/* + * Creates new table + * + * ipfw table NAME create [ type { addr | iface | number | flow } ] + * [ algo algoname ] + */ +static void +table_create(ipfw_obj_header *oh, int ac, char *av[]) +{ + ipfw_xtable_info xi; + int error, tcmd, val; + uint32_t fset, fclear; + char *e, *p; + char tbuf[128]; + + memset(&xi, 0, sizeof(xi)); + + while (ac > 0) { + tcmd = get_token(tablenewcmds, *av, "option"); + ac--; av++; + + switch (tcmd) { + case TOK_LIMIT: + NEED1("limit value required"); + xi.limit = strtol(*av, NULL, 10); + ac--; av++; + break; + case TOK_TYPE: + NEED1("table type required"); + /* Type may have suboptions after ':' */ + if ((p = strchr(*av, ':')) != NULL) + *p++ = '\0'; + val = match_token(tabletypes, *av); + if (val == -1) { + concat_tokens(tbuf, sizeof(tbuf), tabletypes, + ", "); + errx(EX_USAGE, + "Unknown tabletype: %s. Supported: %s", + *av, tbuf); + } + xi.type = val; + if (p != NULL) { + error = table_parse_type(val, p, &xi.tflags); + if (error != 0) + errx(EX_USAGE, + "Unsupported suboptions: %s", p); + } + ac--; av++; + break; + case TOK_VALTYPE: + NEED1("table value type required"); + fset = fclear = 0; + val = fill_flags(tablevaltypes, *av, &e, &fset, &fclear); + if (val != -1) { + xi.vmask = fset; + ac--; av++; + break; + } + concat_tokens(tbuf, sizeof(tbuf), tablevaltypes, ", "); + errx(EX_USAGE, "Unknown value type: %s. Supported: %s", + e, tbuf); + break; + case TOK_ALGO: + NEED1("table algorithm name required"); + if (strlen(*av) > sizeof(xi.algoname)) + errx(EX_USAGE, "algorithm name too long"); + strlcpy(xi.algoname, *av, sizeof(xi.algoname)); + ac--; av++; + break; + case TOK_LOCK: + xi.flags |= IPFW_TGFLAGS_LOCKED; + break; + } + } + + /* Set some defaults to preserve compatibility. */ + if (xi.algoname[0] == '\0' && xi.type == 0) + xi.type = IPFW_TABLE_ADDR; + if (xi.vmask == 0) + xi.vmask = IPFW_VTYPE_LEGACY; + + if ((error = table_do_create(oh, &xi)) != 0) + err(EX_OSERR, "Table creation failed"); +} + +/* + * Creates new table + * + * Request: [ ipfw_obj_header ipfw_xtable_info ] + * + * Returns 0 on success. + */ +static int +table_do_create(ipfw_obj_header *oh, ipfw_xtable_info *i) +{ + char tbuf[sizeof(ipfw_obj_header) + sizeof(ipfw_xtable_info)]; + int error; + + memcpy(tbuf, oh, sizeof(*oh)); + memcpy(tbuf + sizeof(*oh), i, sizeof(*i)); + oh = (ipfw_obj_header *)tbuf; + + error = do_set3(IP_FW_TABLE_XCREATE, &oh->opheader, sizeof(tbuf)); + + return (error); +} + +/* + * Modifies existing table + * + * ipfw table NAME modify [ limit number ] + */ +static void +table_modify(ipfw_obj_header *oh, int ac, char *av[]) +{ + ipfw_xtable_info xi; + int tcmd; + + memset(&xi, 0, sizeof(xi)); + + while (ac > 0) { + tcmd = get_token(tablenewcmds, *av, "option"); + ac--; av++; + + switch (tcmd) { + case TOK_LIMIT: + NEED1("limit value required"); + xi.limit = strtol(*av, NULL, 10); + xi.mflags |= IPFW_TMFLAGS_LIMIT; + ac--; av++; + break; + default: + errx(EX_USAGE, "cmd is not supported for modificatiob"); + } + } + + if (table_do_modify(oh, &xi) != 0) + err(EX_OSERR, "Table modification failed"); +} + +/* + * Modifies existing table. + * + * Request: [ ipfw_obj_header ipfw_xtable_info ] + * + * Returns 0 on success. + */ +static int +table_do_modify(ipfw_obj_header *oh, ipfw_xtable_info *i) +{ + char tbuf[sizeof(ipfw_obj_header) + sizeof(ipfw_xtable_info)]; + int error; + + memcpy(tbuf, oh, sizeof(*oh)); + memcpy(tbuf + sizeof(*oh), i, sizeof(*i)); + oh = (ipfw_obj_header *)tbuf; + + error = do_set3(IP_FW_TABLE_XMODIFY, &oh->opheader, sizeof(tbuf)); + + return (error); +} + +/* + * Locks or unlocks given table + */ +static void +table_lock(ipfw_obj_header *oh, int lock) +{ + ipfw_xtable_info xi; + + memset(&xi, 0, sizeof(xi)); + + xi.mflags |= IPFW_TMFLAGS_LOCK; + xi.flags |= (lock != 0) ? IPFW_TGFLAGS_LOCKED : 0; + + if (table_do_modify(oh, &xi) != 0) + err(EX_OSERR, "Table %s failed", lock != 0 ? "lock" : "unlock"); +} + +/* + * Destroys given table specified by @oh->ntlv. + * Returns 0 on success. + */ +static int +table_destroy(ipfw_obj_header *oh) +{ + + if (do_set3(IP_FW_TABLE_XDESTROY, &oh->opheader, sizeof(*oh)) != 0) + return (-1); + + return (0); +} + +/* + * Flushes given table specified by @oh->ntlv. + * Returns 0 on success. + */ +static int +table_flush(ipfw_obj_header *oh) +{ + + if (do_set3(IP_FW_TABLE_XFLUSH, &oh->opheader, sizeof(*oh)) != 0) + return (-1); + + return (0); +} + +static int +table_do_swap(ipfw_obj_header *oh, char *second) +{ + char tbuf[sizeof(ipfw_obj_header) + sizeof(ipfw_obj_ntlv)]; + int error; + + memset(tbuf, 0, sizeof(tbuf)); + memcpy(tbuf, oh, sizeof(*oh)); + oh = (ipfw_obj_header *)tbuf; + table_fill_ntlv((ipfw_obj_ntlv *)(oh + 1), second, oh->ntlv.set, 1); + + error = do_set3(IP_FW_TABLE_XSWAP, &oh->opheader, sizeof(tbuf)); + + return (error); +} + +/* + * Swaps given table with @second one. + */ +static int +table_swap(ipfw_obj_header *oh, char *second) +{ + + if (table_check_name(second) != 0) + errx(EX_USAGE, "table name %s is invalid", second); + + if (table_do_swap(oh, second) == 0) + return (0); + + switch (errno) { + case EINVAL: + errx(EX_USAGE, "Unable to swap table: check types"); + case EFBIG: + errx(EX_USAGE, "Unable to swap table: check limits"); + } + + return (0); +} + + +/* + * Retrieves table in given table specified by @oh->ntlv. + * it inside @i. + * Returns 0 on success. + */ +static int +table_get_info(ipfw_obj_header *oh, ipfw_xtable_info *i) +{ + char tbuf[sizeof(ipfw_obj_header) + sizeof(ipfw_xtable_info)]; + size_t sz; + + sz = sizeof(tbuf); + memset(tbuf, 0, sizeof(tbuf)); + memcpy(tbuf, oh, sizeof(*oh)); + oh = (ipfw_obj_header *)tbuf; + + if (do_get3(IP_FW_TABLE_XINFO, &oh->opheader, &sz) != 0) + return (errno); + + if (sz < sizeof(tbuf)) + return (EINVAL); + + *i = *(ipfw_xtable_info *)(oh + 1); + + return (0); +} + +static struct _s_x tablealgoclass[] = { + { "hash", IPFW_TACLASS_HASH }, + { "array", IPFW_TACLASS_ARRAY }, + { "radix", IPFW_TACLASS_RADIX }, + { NULL, 0 } +}; + +struct ta_cldata { + uint8_t taclass; + uint8_t spare4; + uint16_t itemsize; + uint16_t itemsize6; + uint32_t size; + uint32_t count; +}; + +/* + * Print global/per-AF table @i algorithm info. + */ +static void +table_show_tainfo(ipfw_xtable_info *i, struct ta_cldata *d, + const char *af, const char *taclass) +{ + + switch (d->taclass) { + case IPFW_TACLASS_HASH: + case IPFW_TACLASS_ARRAY: + printf(" %salgorithm %s info\n", af, taclass); + if (d->itemsize == d->itemsize6) + printf(" size: %u items: %u itemsize: %u\n", + d->size, d->count, d->itemsize); + else + printf(" size: %u items: %u " + "itemsize4: %u itemsize6: %u\n", + d->size, d->count, + d->itemsize, d->itemsize6); + break; + case IPFW_TACLASS_RADIX: + printf(" %salgorithm %s info\n", af, taclass); + if (d->itemsize == d->itemsize6) + printf(" items: %u itemsize: %u\n", + d->count, d->itemsize); + else + printf(" items: %u " + "itemsize4: %u itemsize6: %u\n", + d->count, d->itemsize, d->itemsize6); + break; + default: + printf(" algo class: %s\n", taclass); + } +} + +static void +table_print_valheader(char *buf, size_t bufsize, uint32_t vmask) +{ + + if (vmask == IPFW_VTYPE_LEGACY) { + snprintf(buf, bufsize, "legacy"); + return; + } + + memset(buf, 0, bufsize); + print_flags_buffer(buf, bufsize, tablevaltypes, vmask); +} + +/* + * Prints table info struct @i in human-readable form. + */ +static int +table_show_info(ipfw_xtable_info *i, void *arg) +{ + const char *vtype; + ipfw_ta_tinfo *tainfo; + int afdata, afitem; + struct ta_cldata d; + char ttype[64], tvtype[64]; + + table_print_type(ttype, sizeof(ttype), i->type, i->tflags); + table_print_valheader(tvtype, sizeof(tvtype), i->vmask); + + printf("--- table(%s), set(%u) ---\n", i->tablename, i->set); + if ((i->flags & IPFW_TGFLAGS_LOCKED) != 0) + printf(" kindex: %d, type: %s, locked\n", i->kidx, ttype); + else + printf(" kindex: %d, type: %s\n", i->kidx, ttype); + printf(" references: %u, valtype: %s\n", i->refcnt, tvtype); + printf(" algorithm: %s\n", i->algoname); + printf(" items: %u, size: %u\n", i->count, i->size); + if (i->limit > 0) + printf(" limit: %u\n", i->limit); + + /* Print algo-specific info if requested & set */ + if (arg == NULL) + return (0); + + if ((i->ta_info.flags & IPFW_TATFLAGS_DATA) == 0) + return (0); + tainfo = &i->ta_info; + + afdata = 0; + afitem = 0; + if (tainfo->flags & IPFW_TATFLAGS_AFDATA) + afdata = 1; + if (tainfo->flags & IPFW_TATFLAGS_AFITEM) + afitem = 1; + + memset(&d, 0, sizeof(d)); + d.taclass = tainfo->taclass4; + d.size = tainfo->size4; + d.count = tainfo->count4; + d.itemsize = tainfo->itemsize4; + if (afdata == 0 && afitem != 0) + d.itemsize6 = tainfo->itemsize6; + else + d.itemsize6 = d.itemsize; + if ((vtype = match_value(tablealgoclass, d.taclass)) == NULL) + vtype = "unknown"; + + if (afdata == 0) { + table_show_tainfo(i, &d, "", vtype); + } else { + table_show_tainfo(i, &d, "IPv4 ", vtype); + memset(&d, 0, sizeof(d)); + d.taclass = tainfo->taclass6; + if ((vtype = match_value(tablealgoclass, d.taclass)) == NULL) + vtype = "unknown"; + d.size = tainfo->size6; + d.count = tainfo->count6; + d.itemsize = tainfo->itemsize6; + d.itemsize6 = d.itemsize; + table_show_tainfo(i, &d, "IPv6 ", vtype); + } + + return (0); +} + + +/* + * Function wrappers which can be used either + * as is or as foreach function parameter. + */ + +static int +table_show_one(ipfw_xtable_info *i, void *arg) +{ + ipfw_obj_header *oh; + int error; + + if ((error = table_do_get_list(i, &oh)) != 0) { + err(EX_OSERR, "Error requesting table %s list", i->tablename); + return (error); + } + + table_show_list(oh, 1); + + free(oh); + return (0); +} + +static int +table_flush_one(ipfw_xtable_info *i, void *arg) +{ + ipfw_obj_header *oh; + + oh = (ipfw_obj_header *)arg; + + table_fill_ntlv(&oh->ntlv, i->tablename, i->set, 1); + + return (table_flush(oh)); +} + +static int +table_do_modify_record(int cmd, ipfw_obj_header *oh, + ipfw_obj_tentry *tent, int count, int atomic) +{ + ipfw_obj_ctlv *ctlv; + ipfw_obj_tentry *tent_base; + caddr_t pbuf; + char xbuf[sizeof(*oh) + sizeof(ipfw_obj_ctlv) + sizeof(*tent)]; + int error, i; + size_t sz; + + sz = sizeof(*ctlv) + sizeof(*tent) * count; + if (count == 1) { + memset(xbuf, 0, sizeof(xbuf)); + pbuf = xbuf; + } else { + if ((pbuf = calloc(1, sizeof(*oh) + sz)) == NULL) + return (ENOMEM); + } + + memcpy(pbuf, oh, sizeof(*oh)); + oh = (ipfw_obj_header *)pbuf; + oh->opheader.version = 1; + + ctlv = (ipfw_obj_ctlv *)(oh + 1); + ctlv->count = count; + ctlv->head.length = sz; + if (atomic != 0) + ctlv->flags |= IPFW_CTF_ATOMIC; + + tent_base = tent; + memcpy(ctlv + 1, tent, sizeof(*tent) * count); + tent = (ipfw_obj_tentry *)(ctlv + 1); + for (i = 0; i < count; i++, tent++) { + tent->head.length = sizeof(ipfw_obj_tentry); + tent->idx = oh->idx; + } + + sz += sizeof(*oh); + error = do_get3(cmd, &oh->opheader, &sz); + tent = (ipfw_obj_tentry *)(ctlv + 1); + /* Copy result back to provided buffer */ + memcpy(tent_base, ctlv + 1, sizeof(*tent) * count); + + if (pbuf != xbuf) + free(pbuf); + + return (error); +} + +static void +table_modify_record(ipfw_obj_header *oh, int ac, char *av[], int add, + int quiet, int update, int atomic) +{ + ipfw_obj_tentry *ptent, tent, *tent_buf; + ipfw_xtable_info xi; + uint8_t type; + uint32_t vmask; + int cmd, count, error, i, ignored; + char *texterr, *etxt, *px; + + if (ac == 0) + errx(EX_USAGE, "address required"); + + if (add != 0) { + cmd = IP_FW_TABLE_XADD; + texterr = "Adding record failed"; + } else { + cmd = IP_FW_TABLE_XDEL; + texterr = "Deleting record failed"; + } + + /* + * Calculate number of entries: + * Assume [key val] x N for add + * and + * key x N for delete + */ + count = (add != 0) ? ac / 2 + 1 : ac; + + if (count <= 1) { + /* Adding single entry with/without value */ + memset(&tent, 0, sizeof(tent)); + tent_buf = &tent; + } else { + + if ((tent_buf = calloc(count, sizeof(tent))) == NULL) + errx(EX_OSERR, + "Unable to allocate memory for all entries"); + } + ptent = tent_buf; + + memset(&xi, 0, sizeof(xi)); + count = 0; + while (ac > 0) { + tentry_fill_key(oh, ptent, *av, add, &type, &vmask, &xi); + + /* + * Compatibility layer: auto-create table if not exists. + */ + if (xi.tablename[0] == '\0') { + xi.type = type; + xi.vmask = vmask; + strlcpy(xi.tablename, oh->ntlv.name, + sizeof(xi.tablename)); + if (quiet == 0) + warnx("DEPRECATED: inserting data into " + "non-existent table %s. (auto-created)", + xi.tablename); + table_do_create(oh, &xi); + } + + oh->ntlv.type = type; + ac--; av++; + + if (add != 0 && ac > 0) { + tentry_fill_value(oh, ptent, *av, type, vmask); + ac--; av++; + } + + if (update != 0) + ptent->head.flags |= IPFW_TF_UPDATE; + + count++; + ptent++; + } + + error = table_do_modify_record(cmd, oh, tent_buf, count, atomic); + + /* + * Compatibility stuff: do not yell on duplicate keys or + * failed deletions. + */ + if (error == 0 || (error == EEXIST && add != 0) || + (error == ENOENT && add == 0)) { + if (quiet != 0) { + if (tent_buf != &tent) + free(tent_buf); + return; + } + } + + /* Report results back */ + ptent = tent_buf; + for (i = 0; i < count; ptent++, i++) { + ignored = 0; + switch (ptent->result) { + case IPFW_TR_ADDED: + px = "added"; + break; + case IPFW_TR_DELETED: + px = "deleted"; + break; + case IPFW_TR_UPDATED: + px = "updated"; + break; + case IPFW_TR_LIMIT: + px = "limit"; + ignored = 1; + break; + case IPFW_TR_ERROR: + px = "error"; + ignored = 1; + break; + case IPFW_TR_NOTFOUND: + px = "notfound"; + ignored = 1; + break; + case IPFW_TR_EXISTS: + px = "exists"; + ignored = 1; + break; + case IPFW_TR_IGNORED: + px = "ignored"; + ignored = 1; + break; + default: + px = "unknown"; + ignored = 1; + } + + if (error != 0 && atomic != 0 && ignored == 0) + printf("%s(reverted): ", px); + else + printf("%s: ", px); + + table_show_entry(&xi, ptent); + } + + if (tent_buf != &tent) + free(tent_buf); + + if (error == 0) + return; + /* Get real OS error */ + error = errno; + + /* Try to provide more human-readable error */ + switch (error) { + case EEXIST: + etxt = "record already exists"; + break; + case EFBIG: + etxt = "limit hit"; + break; + case ESRCH: + etxt = "table not found"; + break; + case ENOENT: + etxt = "record not found"; + break; + case EACCES: + etxt = "table is locked"; + break; + default: + etxt = strerror(error); + } + + errx(EX_OSERR, "%s: %s", texterr, etxt); +} + +static int +table_do_lookup(ipfw_obj_header *oh, char *key, ipfw_xtable_info *xi, + ipfw_obj_tentry *xtent) +{ + char xbuf[sizeof(ipfw_obj_header) + sizeof(ipfw_obj_tentry)]; + ipfw_obj_tentry *tent; + uint8_t type; + uint32_t vmask; + size_t sz; + + memcpy(xbuf, oh, sizeof(*oh)); + oh = (ipfw_obj_header *)xbuf; + tent = (ipfw_obj_tentry *)(oh + 1); + + memset(tent, 0, sizeof(*tent)); + tent->head.length = sizeof(*tent); + tent->idx = 1; + + tentry_fill_key(oh, tent, key, 0, &type, &vmask, xi); + oh->ntlv.type = type; + + sz = sizeof(xbuf); + if (do_get3(IP_FW_TABLE_XFIND, &oh->opheader, &sz) != 0) + return (errno); + + if (sz < sizeof(xbuf)) + return (EINVAL); + + *xtent = *tent; + + return (0); +} + +static void +table_lookup(ipfw_obj_header *oh, int ac, char *av[]) +{ + ipfw_obj_tentry xtent; + ipfw_xtable_info xi; + char key[64]; + int error; + + if (ac == 0) + errx(EX_USAGE, "address required"); + + strlcpy(key, *av, sizeof(key)); + + memset(&xi, 0, sizeof(xi)); + error = table_do_lookup(oh, key, &xi, &xtent); + + switch (error) { + case 0: + break; + case ESRCH: + errx(EX_UNAVAILABLE, "Table %s not found", oh->ntlv.name); + case ENOENT: + errx(EX_UNAVAILABLE, "Entry %s not found", *av); + case ENOTSUP: + errx(EX_UNAVAILABLE, "Table %s algo does not support " + "\"lookup\" method", oh->ntlv.name); + default: + err(EX_OSERR, "getsockopt(IP_FW_TABLE_XFIND)"); + } + + table_show_entry(&xi, &xtent); +} + +static void +tentry_fill_key_type(char *arg, ipfw_obj_tentry *tentry, uint8_t type, + uint8_t tflags) +{ + char *p, *pp; + int mask, af; + struct in6_addr *paddr, tmp; + struct tflow_entry *tfe; + uint32_t key, *pkey; + uint16_t port; + struct protoent *pent; + struct servent *sent; + int masklen; + + masklen = 0; + af = 0; + paddr = (struct in6_addr *)&tentry->k; + + switch (type) { + case IPFW_TABLE_ADDR: + /* Remove / if exists */ + if ((p = strchr(arg, '/')) != NULL) { + *p = '\0'; + mask = atoi(p + 1); + } + + if (inet_pton(AF_INET, arg, paddr) == 1) { + if (p != NULL && mask > 32) + errx(EX_DATAERR, "bad IPv4 mask width: %s", + p + 1); + + masklen = p ? mask : 32; + af = AF_INET; + } else if (inet_pton(AF_INET6, arg, paddr) == 1) { + if (IN6_IS_ADDR_V4COMPAT(paddr)) + errx(EX_DATAERR, + "Use IPv4 instead of v4-compatible"); + if (p != NULL && mask > 128) + errx(EX_DATAERR, "bad IPv6 mask width: %s", + p + 1); + + masklen = p ? mask : 128; + af = AF_INET6; + } else { + /* Assume FQDN */ + if (lookup_host(arg, (struct in_addr *)paddr) != 0) + errx(EX_NOHOST, "hostname ``%s'' unknown", arg); + + masklen = 32; + type = IPFW_TABLE_ADDR; + af = AF_INET; + } + break; + case IPFW_TABLE_INTERFACE: + /* Assume interface name. Copy significant data only */ + mask = MIN(strlen(arg), IF_NAMESIZE - 1); + memcpy(paddr, arg, mask); + /* Set mask to exact match */ + masklen = 8 * IF_NAMESIZE; + break; + case IPFW_TABLE_NUMBER: + /* Port or any other key */ + key = strtol(arg, &p, 10); + if (*p != '\0') + errx(EX_DATAERR, "Invalid number: %s", arg); + + pkey = (uint32_t *)paddr; + *pkey = key; + masklen = 32; + break; + case IPFW_TABLE_FLOW: + /* Assume [src-ip][,proto][,src-port][,dst-ip][,dst-port] */ + tfe = &tentry->k.flow; + af = 0; + + /* Handle */ + if ((tflags & IPFW_TFFLAG_SRCIP) != 0) { + if ((p = strchr(arg, ',')) != NULL) + *p++ = '\0'; + /* Determine family using temporary storage */ + if (inet_pton(AF_INET, arg, &tmp) == 1) { + if (af != 0 && af != AF_INET) + errx(EX_DATAERR, + "Inconsistent address family\n"); + af = AF_INET; + memcpy(&tfe->a.a4.sip, &tmp, 4); + } else if (inet_pton(AF_INET6, arg, &tmp) == 1) { + if (af != 0 && af != AF_INET6) + errx(EX_DATAERR, + "Inconsistent address family\n"); + af = AF_INET6; + memcpy(&tfe->a.a6.sip6, &tmp, 16); + } + + arg = p; + } + + /* Handle */ + if ((tflags & IPFW_TFFLAG_PROTO) != 0) { + if (arg == NULL) + errx(EX_DATAERR, "invalid key: proto missing"); + if ((p = strchr(arg, ',')) != NULL) + *p++ = '\0'; + + key = strtol(arg, &pp, 10); + if (*pp != '\0') { + if ((pent = getprotobyname(arg)) == NULL) + errx(EX_DATAERR, "Unknown proto: %s", + arg); + else + key = pent->p_proto; + } + + if (key > 255) + errx(EX_DATAERR, "Bad protocol number: %u",key); + + tfe->proto = key; + + arg = p; + } + + /* Handle */ + if ((tflags & IPFW_TFFLAG_SRCPORT) != 0) { + if (arg == NULL) + errx(EX_DATAERR, "invalid key: src port missing"); + if ((p = strchr(arg, ',')) != NULL) + *p++ = '\0'; + + if ((port = htons(strtol(arg, NULL, 10))) == 0) { + if ((sent = getservbyname(arg, NULL)) == NULL) + errx(EX_DATAERR, "Unknown service: %s", + arg); + else + key = sent->s_port; + } + + tfe->sport = port; + + arg = p; + } + + /* Handle */ + if ((tflags & IPFW_TFFLAG_DSTIP) != 0) { + if (arg == NULL) + errx(EX_DATAERR, "invalid key: dst ip missing"); + if ((p = strchr(arg, ',')) != NULL) + *p++ = '\0'; + /* Determine family using temporary storage */ + if (inet_pton(AF_INET, arg, &tmp) == 1) { + if (af != 0 && af != AF_INET) + errx(EX_DATAERR, + "Inconsistent address family"); + af = AF_INET; + memcpy(&tfe->a.a4.dip, &tmp, 4); + } else if (inet_pton(AF_INET6, arg, &tmp) == 1) { + if (af != 0 && af != AF_INET6) + errx(EX_DATAERR, + "Inconsistent address family"); + af = AF_INET6; + memcpy(&tfe->a.a6.dip6, &tmp, 16); + } + + arg = p; + } + + /* Handle */ + if ((tflags & IPFW_TFFLAG_DSTPORT) != 0) { + if (arg == NULL) + errx(EX_DATAERR, "invalid key: dst port missing"); + if ((p = strchr(arg, ',')) != NULL) + *p++ = '\0'; + + if ((port = htons(strtol(arg, NULL, 10))) == 0) { + if ((sent = getservbyname(arg, NULL)) == NULL) + errx(EX_DATAERR, "Unknown service: %s", + arg); + else + key = sent->s_port; + } + + tfe->dport = port; + + arg = p; + } + + tfe->af = af; + + break; + + default: + errx(EX_DATAERR, "Unsupported table type: %d", type); + } + + tentry->subtype = af; + tentry->masklen = masklen; +} + +/* + * Tries to guess table key type. + * This procedure is used in legacy table auto-create + * code AND in `ipfw -n` ruleset checking. + * + * Imported from old table_fill_xentry() parse code. + */ +static int +guess_key_type(char *key, uint8_t *ptype) +{ + char *p; + struct in6_addr addr; + uint32_t kv; + + if (ishexnumber(*key) != 0 || *key == ':') { + /* Remove / if exists */ + if ((p = strchr(key, '/')) != NULL) + *p = '\0'; + + if ((inet_pton(AF_INET, key, &addr) == 1) || + (inet_pton(AF_INET6, key, &addr) == 1)) { + *ptype = IPFW_TABLE_CIDR; + if (p != NULL) + *p = '/'; + return (0); + } else { + /* Port or any other key */ + /* Skip non-base 10 entries like 'fa1' */ + kv = strtol(key, &p, 10); + if (*p == '\0') { + *ptype = IPFW_TABLE_NUMBER; + return (0); + } else if ((p != key) && (*p == '.')) { + /* + * Warn on IPv4 address strings + * which are "valid" for inet_aton() but not + * in inet_pton(). + * + * Typical examples: '10.5' or '10.0.0.05' + */ + return (1); + } + } + } + + if (strchr(key, '.') == NULL) { + *ptype = IPFW_TABLE_INTERFACE; + return (0); + } + + if (lookup_host(key, (struct in_addr *)&addr) != 0) + return (1); + + *ptype = IPFW_TABLE_CIDR; + return (0); +} + +static void +tentry_fill_key(ipfw_obj_header *oh, ipfw_obj_tentry *tent, char *key, + int add, uint8_t *ptype, uint32_t *pvmask, ipfw_xtable_info *xi) +{ + uint8_t type, tflags; + uint32_t vmask; + int error; + + type = 0; + tflags = 0; + vmask = 0; + + if (xi->tablename[0] == '\0') + error = table_get_info(oh, xi); + else + error = 0; + + if (error == 0) { + if (co.test_only == 0) { + /* Table found */ + type = xi->type; + tflags = xi->tflags; + vmask = xi->vmask; + } else { + /* + * We're running `ipfw -n` + * Compatibility layer: try to guess key type + * before failing. + */ + if (guess_key_type(key, &type) != 0) { + /* Inknown key */ + errx(EX_USAGE, "Cannot guess " + "key '%s' type", key); + } + vmask = IPFW_VTYPE_LEGACY; + } + } else { + if (error != ESRCH) + errx(EX_OSERR, "Error requesting table %s info", + oh->ntlv.name); + if (add == 0) + errx(EX_DATAERR, "Table %s does not exist", + oh->ntlv.name); + /* + * Table does not exist + * Compatibility layer: try to guess key type before failing. + */ + if (guess_key_type(key, &type) != 0) { + /* Inknown key */ + errx(EX_USAGE, "Table %s does not exist, cannot guess " + "key '%s' type", oh->ntlv.name, key); + } + + vmask = IPFW_VTYPE_LEGACY; + } + + tentry_fill_key_type(key, tent, type, tflags); + + *ptype = type; + *pvmask = vmask; +} + +static void +set_legacy_value(uint32_t val, ipfw_table_value *v) +{ + v->tag = val; + v->pipe = val; + v->divert = val; + v->skipto = val; + v->netgraph = val; + v->fib = val; + v->nat = val; + v->nh4 = val; + v->dscp = (uint8_t)val; + v->limit = val; +} + +static void +tentry_fill_value(ipfw_obj_header *oh, ipfw_obj_tentry *tent, char *arg, + uint8_t type, uint32_t vmask) +{ + struct addrinfo hints, *res; + uint32_t a4, flag, val; + ipfw_table_value *v; + uint32_t i; + int dval; + char *comma, *e, *etype, *n, *p; + + v = &tent->v.value; + + /* Compat layer: keep old behavior for legacy value types */ + if (vmask == IPFW_VTYPE_LEGACY) { + /* Try to interpret as number first */ + val = strtoul(arg, &p, 0); + if (*p == '\0') { + set_legacy_value(val, v); + return; + } + if (inet_pton(AF_INET, arg, &val) == 1) { + set_legacy_value(ntohl(val), v); + return; + } + /* Try hostname */ + if (lookup_host(arg, (struct in_addr *)&val) == 0) { + set_legacy_value(val, v); + return; + } + errx(EX_OSERR, "Unable to parse value %s", arg); + } + + /* + * Shorthands: handle single value if vmask consists + * of numbers only. e.g.: + * vmask = "fib,skipto" -> treat input "1" as "1,1" + */ + + n = arg; + etype = NULL; + for (i = 1; i < (1 << 31); i *= 2) { + if ((flag = (vmask & i)) == 0) + continue; + vmask &= ~flag; + + if ((comma = strchr(n, ',')) != NULL) + *comma = '\0'; + + switch (flag) { + case IPFW_VTYPE_TAG: + v->tag = strtol(n, &e, 10); + if (*e != '\0') + etype = "tag"; + break; + case IPFW_VTYPE_PIPE: + v->pipe = strtol(n, &e, 10); + if (*e != '\0') + etype = "pipe"; + break; + case IPFW_VTYPE_DIVERT: + v->divert = strtol(n, &e, 10); + if (*e != '\0') + etype = "divert"; + break; + case IPFW_VTYPE_SKIPTO: + v->skipto = strtol(n, &e, 10); + if (*e != '\0') + etype = "skipto"; + break; + case IPFW_VTYPE_NETGRAPH: + v->netgraph = strtol(n, &e, 10); + if (*e != '\0') + etype = "netgraph"; + break; + case IPFW_VTYPE_FIB: + v->fib = strtol(n, &e, 10); + if (*e != '\0') + etype = "fib"; + break; + case IPFW_VTYPE_NAT: + v->nat = strtol(n, &e, 10); + if (*e != '\0') + etype = "nat"; + break; + case IPFW_VTYPE_LIMIT: + v->limit = strtol(n, &e, 10); + if (*e != '\0') + etype = "limit"; + break; + case IPFW_VTYPE_NH4: + if (strchr(n, '.') != NULL && + inet_pton(AF_INET, n, &a4) == 1) { + v->nh4 = ntohl(a4); + break; + } + if (lookup_host(n, (struct in_addr *)&v->nh4) == 0) + break; + etype = "ipv4"; + break; + case IPFW_VTYPE_DSCP: + if (isalpha(*n)) { + if ((dval = match_token(f_ipdscp, n)) != -1) { + v->dscp = dval; + break; + } else + etype = "DSCP code"; + } else { + v->dscp = strtol(n, &e, 10); + if (v->dscp > 63 || *e != '\0') + etype = "DSCP value"; + } + break; + case IPFW_VTYPE_NH6: + if (strchr(n, ':') != NULL) { + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(n, NULL, &hints, &res) == 0) { + v->nh6 = ((struct sockaddr_in6 *) + res->ai_addr)->sin6_addr; + v->zoneid = ((struct sockaddr_in6 *) + res->ai_addr)->sin6_scope_id; + freeaddrinfo(res); + break; + } + } + etype = "ipv6"; + break; + } + + if (etype != NULL) + errx(EX_USAGE, "Unable to parse %s as %s", n, etype); + + if (comma != NULL) + *comma++ = ','; + + if ((n = comma) != NULL) + continue; + + /* End of input. */ + if (vmask != 0) + errx(EX_USAGE, "Not enough fields inside value"); + } +} + +/* + * Compare table names. + * Honor number comparison. + */ +static int +tablename_cmp(const void *a, const void *b) +{ + ipfw_xtable_info *ia, *ib; + + ia = (ipfw_xtable_info *)a; + ib = (ipfw_xtable_info *)b; + + return (stringnum_cmp(ia->tablename, ib->tablename)); +} + +/* + * Retrieves table list from kernel, + * optionally sorts it and calls requested function for each table. + * Returns 0 on success. + */ +static int +tables_foreach(table_cb_t *f, void *arg, int sort) +{ + ipfw_obj_lheader *olh; + ipfw_xtable_info *info; + size_t sz; + int i, error; + + /* Start with reasonable default */ + sz = sizeof(*olh) + 16 * sizeof(ipfw_xtable_info); + + for (;;) { + if ((olh = calloc(1, sz)) == NULL) + return (ENOMEM); + + olh->size = sz; + if (do_get3(IP_FW_TABLES_XLIST, &olh->opheader, &sz) != 0) { + sz = olh->size; + free(olh); + if (errno != ENOMEM) + return (errno); + continue; + } + + if (sort != 0) + qsort(olh + 1, olh->count, olh->objsize, tablename_cmp); + + info = (ipfw_xtable_info *)(olh + 1); + for (i = 0; i < olh->count; i++) { + error = f(info, arg); /* Ignore errors for now */ + info = (ipfw_xtable_info *)((caddr_t)info + olh->objsize); + } + + free(olh); + break; + } + + return (0); +} + + +/* + * Retrieves all entries for given table @i in + * eXtended format. Allocate buffer large enough + * to store result. Called needs to free it later. + * + * Returns 0 on success. + */ +static int +table_do_get_list(ipfw_xtable_info *i, ipfw_obj_header **poh) +{ + ipfw_obj_header *oh; + size_t sz; + int c; + + sz = 0; + oh = NULL; + for (c = 0; c < 8; c++) { + if (sz < i->size) + sz = i->size + 44; + if (oh != NULL) + free(oh); + if ((oh = calloc(1, sz)) == NULL) + continue; + table_fill_objheader(oh, i); + oh->opheader.version = 1; /* Current version */ + if (do_get3(IP_FW_TABLE_XLIST, &oh->opheader, &sz) == 0) { + *poh = oh; + return (0); + } + + if (errno != ENOMEM) + break; + } + free(oh); + + return (errno); +} + +/* + * Shows all entries from @oh in human-readable format + */ +static void +table_show_list(ipfw_obj_header *oh, int need_header) +{ + ipfw_obj_tentry *tent; + uint32_t count; + ipfw_xtable_info *i; + + i = (ipfw_xtable_info *)(oh + 1); + tent = (ipfw_obj_tentry *)(i + 1); + + if (need_header) + printf("--- table(%s), set(%u) ---\n", i->tablename, i->set); + + count = i->count; + while (count > 0) { + table_show_entry(i, tent); + tent = (ipfw_obj_tentry *)((caddr_t)tent + tent->head.length); + count--; + } +} + +static void +table_show_value(char *buf, size_t bufsize, ipfw_table_value *v, + uint32_t vmask, int print_ip) +{ + char abuf[INET6_ADDRSTRLEN + IF_NAMESIZE + 2]; + struct sockaddr_in6 sa6; + uint32_t flag, i, l; + size_t sz; + struct in_addr a4; + + sz = bufsize; + + /* + * Some shorthands for printing values: + * legacy assumes all values are equal, so keep the first one. + */ + if (vmask == IPFW_VTYPE_LEGACY) { + if (print_ip != 0) { + flag = htonl(v->tag); + inet_ntop(AF_INET, &flag, buf, sz); + } else + snprintf(buf, sz, "%u", v->tag); + return; + } + + for (i = 1; i < (1 << 31); i *= 2) { + if ((flag = (vmask & i)) == 0) + continue; + l = 0; + + switch (flag) { + case IPFW_VTYPE_TAG: + l = snprintf(buf, sz, "%u,", v->tag); + break; + case IPFW_VTYPE_PIPE: + l = snprintf(buf, sz, "%u,", v->pipe); + break; + case IPFW_VTYPE_DIVERT: + l = snprintf(buf, sz, "%d,", v->divert); + break; + case IPFW_VTYPE_SKIPTO: + l = snprintf(buf, sz, "%d,", v->skipto); + break; + case IPFW_VTYPE_NETGRAPH: + l = snprintf(buf, sz, "%u,", v->netgraph); + break; + case IPFW_VTYPE_FIB: + l = snprintf(buf, sz, "%u,", v->fib); + break; + case IPFW_VTYPE_NAT: + l = snprintf(buf, sz, "%u,", v->nat); + break; + case IPFW_VTYPE_LIMIT: + l = snprintf(buf, sz, "%u,", v->limit); + break; + case IPFW_VTYPE_NH4: + a4.s_addr = htonl(v->nh4); + inet_ntop(AF_INET, &a4, abuf, sizeof(abuf)); + l = snprintf(buf, sz, "%s,", abuf); + break; + case IPFW_VTYPE_DSCP: + l = snprintf(buf, sz, "%d,", v->dscp); + break; + case IPFW_VTYPE_NH6: + sa6.sin6_family = AF_INET6; + sa6.sin6_len = sizeof(sa6); + sa6.sin6_addr = v->nh6; + sa6.sin6_port = 0; + sa6.sin6_scope_id = v->zoneid; + if (getnameinfo((const struct sockaddr *)&sa6, + sa6.sin6_len, abuf, sizeof(abuf), NULL, 0, + NI_NUMERICHOST) == 0) + l = snprintf(buf, sz, "%s,", abuf); + break; + } + + buf += l; + sz -= l; + } + + if (sz != bufsize) + *(buf - 1) = '\0'; +} + +static void +table_show_entry(ipfw_xtable_info *i, ipfw_obj_tentry *tent) +{ + char *comma, tbuf[128], pval[128]; + void *paddr; + struct tflow_entry *tfe; + + table_show_value(pval, sizeof(pval), &tent->v.value, i->vmask, + co.do_value_as_ip); + + switch (i->type) { + case IPFW_TABLE_ADDR: + /* IPv4 or IPv6 prefixes */ + inet_ntop(tent->subtype, &tent->k, tbuf, sizeof(tbuf)); + printf("%s/%u %s\n", tbuf, tent->masklen, pval); + break; + case IPFW_TABLE_INTERFACE: + /* Interface names */ + printf("%s %s\n", tent->k.iface, pval); + break; + case IPFW_TABLE_NUMBER: + /* numbers */ + printf("%u %s\n", tent->k.key, pval); + break; + case IPFW_TABLE_FLOW: + /* flows */ + tfe = &tent->k.flow; + comma = ""; + + if ((i->tflags & IPFW_TFFLAG_SRCIP) != 0) { + if (tfe->af == AF_INET) + paddr = &tfe->a.a4.sip; + else + paddr = &tfe->a.a6.sip6; + + inet_ntop(tfe->af, paddr, tbuf, sizeof(tbuf)); + printf("%s%s", comma, tbuf); + comma = ","; + } + + if ((i->tflags & IPFW_TFFLAG_PROTO) != 0) { + printf("%s%d", comma, tfe->proto); + comma = ","; + } + + if ((i->tflags & IPFW_TFFLAG_SRCPORT) != 0) { + printf("%s%d", comma, ntohs(tfe->sport)); + comma = ","; + } + if ((i->tflags & IPFW_TFFLAG_DSTIP) != 0) { + if (tfe->af == AF_INET) + paddr = &tfe->a.a4.dip; + else + paddr = &tfe->a.a6.dip6; + + inet_ntop(tfe->af, paddr, tbuf, sizeof(tbuf)); + printf("%s%s", comma, tbuf); + comma = ","; + } + + if ((i->tflags & IPFW_TFFLAG_DSTPORT) != 0) { + printf("%s%d", comma, ntohs(tfe->dport)); + comma = ","; + } + + printf(" %s\n", pval); + } +} + +static int +table_do_get_stdlist(uint16_t opcode, ipfw_obj_lheader **polh) +{ + ipfw_obj_lheader req, *olh; + size_t sz; + + memset(&req, 0, sizeof(req)); + sz = sizeof(req); + + if (do_get3(opcode, &req.opheader, &sz) != 0) + if (errno != ENOMEM) + return (errno); + + sz = req.size; + if ((olh = calloc(1, sz)) == NULL) + return (ENOMEM); + + olh->size = sz; + if (do_get3(opcode, &olh->opheader, &sz) != 0) { + free(olh); + return (errno); + } + + *polh = olh; + return (0); +} + +static int +table_do_get_algolist(ipfw_obj_lheader **polh) +{ + + return (table_do_get_stdlist(IP_FW_TABLES_ALIST, polh)); +} + +static int +table_do_get_vlist(ipfw_obj_lheader **polh) +{ + + return (table_do_get_stdlist(IP_FW_TABLE_VLIST, polh)); +} + +void +ipfw_list_ta(int ac, char *av[]) +{ + ipfw_obj_lheader *olh; + ipfw_ta_info *info; + int error, i; + const char *atype; + + error = table_do_get_algolist(&olh); + if (error != 0) + err(EX_OSERR, "Unable to request algorithm list"); + + info = (ipfw_ta_info *)(olh + 1); + for (i = 0; i < olh->count; i++) { + if ((atype = match_value(tabletypes, info->type)) == NULL) + atype = "unknown"; + printf("--- %s ---\n", info->algoname); + printf(" type: %s\n refcount: %u\n", atype, info->refcnt); + + info = (ipfw_ta_info *)((caddr_t)info + olh->objsize); + } + + free(olh); +} + + +/* Copy of current kernel table_value structure */ +struct _table_value { + uint32_t tag; /* O_TAG/O_TAGGED */ + uint32_t pipe; /* O_PIPE/O_QUEUE */ + uint16_t divert; /* O_DIVERT/O_TEE */ + uint16_t skipto; /* skipto, CALLRET */ + uint32_t netgraph; /* O_NETGRAPH/O_NGTEE */ + uint32_t fib; /* O_SETFIB */ + uint32_t nat; /* O_NAT */ + uint32_t nh4; + uint8_t dscp; + uint8_t spare0; + uint16_t spare1; + /* -- 32 bytes -- */ + struct in6_addr nh6; + uint32_t limit; /* O_LIMIT */ + uint32_t zoneid; + uint64_t refcnt; /* Number of references */ +}; + +int +compare_values(const void *_a, const void *_b) +{ + struct _table_value *a, *b; + + a = (struct _table_value *)_a; + b = (struct _table_value *)_b; + + if (a->spare1 < b->spare1) + return (-1); + else if (a->spare1 > b->spare1) + return (1); + + return (0); +} + +void +ipfw_list_values(int ac, char *av[]) +{ + ipfw_obj_lheader *olh; + struct _table_value *v; + int error, i; + uint32_t vmask; + char buf[128]; + + error = table_do_get_vlist(&olh); + if (error != 0) + err(EX_OSERR, "Unable to request value list"); + + vmask = 0x7FFFFFFF; /* Similar to IPFW_VTYPE_LEGACY */ + + table_print_valheader(buf, sizeof(buf), vmask); + printf("HEADER: %s\n", buf); + v = (struct _table_value *)(olh + 1); + qsort(v, olh->count, olh->objsize, compare_values); + for (i = 0; i < olh->count; i++) { + table_show_value(buf, sizeof(buf), (ipfw_table_value *)v, + vmask, 0); + printf("[%u] refs=%lu %s\n", v->spare1, (u_long)v->refcnt, buf); + v = (struct _table_value *)((caddr_t)v + olh->objsize); + } + + free(olh); +} + +int +table_check_name(const char *tablename) +{ + + if (ipfw_check_object_name(tablename) != 0) + return (EINVAL); + /* Restrict some 'special' names */ + if (strcmp(tablename, "all") == 0) + return (EINVAL); + return (0); +} + diff --git a/tools/opts.mk b/tools/opts.mk index e58e7c385..40e6e4206 100644 --- a/tools/opts.mk +++ b/tools/opts.mk @@ -10,3 +10,4 @@ MK_JAIL="no" MK_NETGRAPH_SUPPORT="no" MK_IPSEC_SUPPORT="no" MK_HAVE_LIBEDIT="no" +MK_DUMMYNET="no" diff --git a/tools/prog.mk b/tools/prog.mk index 6fff22cb8..78ad84473 100644 --- a/tools/prog.mk +++ b/tools/prog.mk @@ -55,6 +55,7 @@ FF_PROG_LIBS+= -Wl,--no-whole-archive -L${FF_DPDK}/lib FF_PROG_LIBS+= -Wl,--whole-archive -lrte_eal -lrte_mempool -lrte_ring FF_PROG_LIBS+= -Wl,--no-whole-archive -lrt -lm -ldl -lcrypto -pthread +CFLAGS+= -Wno-unused-but-set-variable -Wno-unused-variable CFLAGS+= ${FF_PROG_CFLAGS} CXXFLAGS+= ${FF_PROG_CFLAGS}