From 99e5e6450432b70cd817fb4f9b957f5bcdbcb5f3 Mon Sep 17 00:00:00 2001 From: Luigi Rizzo Date: Fri, 16 Aug 2002 10:31:47 +0000 Subject: [PATCH] sys/netinet/ip_fw2.c: Implement the M_SKIP_FIREWALL bit in m_flags to avoid loops for firewall-generated packets (the constant has to go in sys/mbuf.h). Better comments on keepalive generation, and enforce dyn_rst_lifetime and dyn_fin_lifetime to be less than dyn_keepalive_period. Enforce limits (up to 64k) on the number of dynamic buckets, and retry allocation with smaller sizes. Raise default number of dynamic rules to 4096. Improved handling of set of rules -- now you can atomically enable/disable multiple sets, move rules from one set to another, and swap sets. sbin/ipfw/ipfw2.c: userland support for "noerror" pipe attribute. userland support for sets of rules. minor improvements on rule parsing and printing. sbin/ipfw/ipfw.8: more documentation on ipfw2 extensions, differences from ipfw1 (so we can use the same manpage for both), stateful rules, and some additional examples. Feedback and more examples needed here. --- sbin/ipfw/ipfw.8 | 441 ++++++++++++++++++++++++++++++++++++------- sbin/ipfw/ipfw2.c | 205 +++++++++++++++----- sys/netinet/ip_fw2.c | 163 +++++++++++----- 3 files changed, 644 insertions(+), 165 deletions(-) diff --git a/sbin/ipfw/ipfw.8 b/sbin/ipfw/ipfw.8 index 1b3832b9da43..91502c16cba8 100644 --- a/sbin/ipfw/ipfw.8 +++ b/sbin/ipfw/ipfw.8 @@ -1,7 +1,12 @@ .\" .\" $FreeBSD$ .\" -.Dd May 31, 2001 +.de NOIPFW +.br +(\\$1 NOT IN IPFW) +.br +.. +.Dd August 13, 2002 .Dt IPFW 8 .Os .Sh NAME @@ -13,11 +18,6 @@ .Cm add .Ar rule .Nm -.Op Fl q -.Cm delete -.Op Cm set -.Op Ar number ... -.Nm .Op Fl adeftNS .Brq Cm list | show .Op Ar number ... @@ -26,16 +26,20 @@ .Cm flush .Nm .Op Fl q -.Brq Cm zero | resetlog +.Brq Cm delete | zero | resetlog .Op Cm set .Op Ar number ... +.Pp .Nm -.Op Fl q -.Brq Cm disable | enable -.Cm set -.Op Ar number ... +.Cm set Oo Cm disable Ar number ... Oc Op Cm enable Ar number ... .Nm -.Cm show sets +.Cm set move +.Op Cm rule +.Ar number Cm to Ar number +.Nm +.Cm set swap Ar number number +.Nm +.Cm set show .Pp .Nm .Brq Cm pipe | queue @@ -68,6 +72,22 @@ firewall and the traffic shaper in .Fx . .Pp +.Em NOTE: +this manual page refers to the newer version of +.Nm +introduced in July 2002, also known as +.Nm ipfw2 . +The commands listed here are a superset of the old +firewall, which we will call +.Nm ipfw1 +when it is necessary to distinguish between the two. +See the +.Sx IPFW2 ENHANCEMENTS +Section for a list of features which are not present in +.Nm ipfw1 . +This list can also be useful to revise your ruleset and +write them more efficiently. +.Pp An .Nm configuration, or @@ -126,10 +146,10 @@ or rule, and are typically used to open the firewall on-demand to legitimate traffic only. See the -.Sx RULE FORMAT +.Sx STATEFUL FIREWALL and .Sx EXAMPLES -sections below for more information on the stateful behaviour of +Sections below for more information on the stateful behaviour of .Nm . .Pp All rules (including dynamic ones) have a few associated counters: @@ -157,6 +177,19 @@ and .Cm resetlog commands. .Pp +Also, 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 The following options are available: .Bl -tag -width indent .It Fl a @@ -174,8 +207,7 @@ option was specified, also show expired dynamic rules. Don't ask for confirmation for commands that can cause problems if misused, .No i.e. Cm flush . -.Em Note , -if there is no tty associated with the process, this is implied. +If there is no tty associated with the process, this is implied. .It Fl N Try to resolve addresses and service names in output. .It Fl q @@ -206,7 +238,9 @@ to the login session, causing the remote login session to be closed and the remainder of the ruleset is not processed. Access to the console would then be required to recover. .It Fl S -While listing rules, show the set each rule belongs to. +While 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 @@ -265,7 +299,7 @@ and .Cm queue commands are used to configure the traffic shaper, as shown in the .Sx TRAFFIC SHAPER CONFIGURATION -section below. +Section below. .Sh PACKET FLOW .Nm can be invoked from multiple places in the protocol stack, @@ -404,7 +438,7 @@ which defaults to 100. If this is not possible (e.g. because we would go beyond the maximum allowed rule number), the same number of the last non-default value is used instead. -.It Ar set_number +.It Cm set Ar set_number Each rule is associated to a .Ar set_number in the range 0..31, with the latter reserved for the @@ -535,7 +569,7 @@ Pass packet to a (for bandwidth limitation, delay, etc.). See the .Sx TRAFFIC SHAPER CONFIGURATION -section for further information. +Section for further information. The search terminates; however, on exit from the pipe and if the .Xr sysctl 8 @@ -568,7 +602,7 @@ Send a copy of packets matching this rule to the socket bound to port .Ar port . The search terminates and the original packet is accepted -(but see section +(but see Section .Sx BUGS below). .It Cm unreach Ar code @@ -630,13 +664,17 @@ The general rule body format is one of the following: .Op Ar options .br .Cm MAC Ar dst-mac src-mac mac-type +.Op Cm from Ar src Cm to Ar dst .Op Ar options .Ed .Pp -where fields have the following meaning: +where the second format allows you to specify MAC header fields +instead (or in addition) of the IPv4 header fields. +.Pp +Rule fields have the following meaning: .Bl -tag -width indent .It Ar proto -An IP protocol specified by number or name (for a complete +An IPv4 protocol specified by number or name (for a complete list see .Pa /etc/protocols ) . The @@ -652,7 +690,6 @@ A single containing one or more of them, optionally followed by .Em port numbers. -followed by a set of port numbers. .It Ar ip address : An address (or set of addresses) specified in one of the following ways, optionally preceded by a @@ -699,7 +736,7 @@ within a single rule. Because the matching occurs using a bitmask, it takes constant time and dramatically reduces the complexity of rulesets. .El -.It Cm port numbers +.It port numbers With 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 @@ -741,6 +778,28 @@ specifications. See the .Cm frag option for details on matching fragmented packets. +.It dst-mac, src-mac +Destination and source MAC addresses, specified as +groups of hex digits separated by commas, and optionally +followed by a mask indicating how many bits are significant: +.Pp +.Dl "ipfw add allow MAC 10:20:30:40:50:60/30 any any +.Pp +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 mac-type +The value of the Ethernet Type field, 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 . +The values can be enter as decimal or hexadecimal, but they +are always printed as hexadecimal (unless the +.Cm -N +option is used, in which case symbolic resolution will be +attempted). .El .Ss RULE OPTIONS Additional match patterns can be used within @@ -1016,12 +1075,127 @@ A .Ar user may be matched by name or identification number. .El -.Sh STATEFUL FIREWALL -To be completed. -.Sh TRAFFIC SHAPER CONFIGURATION -The +.Sh SETS OF RULES +Each rule belongs to one of 32 different +.Em sets +, numbered 0 to 31. +Set 31 is reserved for the default rule. +.Pp +By default, rules are put in set 0, unless you use the +.Cm set N +attribute when entering a new rule. +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. +The command to enable/disable sets is +.Pp .Nm -utility is also the user interface for the +.Cm set disable Ar number ... Op Cm enable Ar number ... +.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 were not existing +in the firewall configuration, with only one exception: +.Bl -bullet +.It +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; +.El +The set number of rules can be changed with the command +.Pp +.Nm +.Cm set move +.Brq Cm rule Ar rule-number | old-set +.Cm to Ar new-set +.Pp +Also, you can atomically swap two rulesets with the command +.Pp +.Nm +.Cm set swap Ar first-set second-set +.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" +.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" +.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 CONFIGURATION +.Nm +is also the user interface for the .Xr dummynet 4 traffic shaper. The shaper operates by dividing packets into @@ -1124,22 +1298,6 @@ variable .Em net.inet.ip.dummynet.hash_size , allowed range is 16 to 1024. .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 effect can result if you get packets from an -interface with a much larger MTU, e.g. the loopback interface -with its 16KB packets. -.Pp .It Cm mask Ar mask-specifier The .Xr dummynet 4 @@ -1167,6 +1325,14 @@ configuration, each flow is assigned a weight equal to the weight of the queue, and all flows insisting on the same pipe share bandwidth proportionally to their weight. .Pp +.It Cm noerror +When a packet is dropped by a 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 @@ -1175,6 +1341,22 @@ 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 effect can result if you get packets from an +interface with a much larger MTU, e.g. the loopback interface +with its 16KB packets. +.Pp .It Cm red | gred Ar w_q Ns / Ns Ar min_th Ns / Ns Ar max_th Ns / Ns Ar max_p Make use of the RED (Random Early Detection) queue management algorithm. .Ar w_q @@ -1290,36 +1472,32 @@ These are shown below together with their default value .Xr sysctl 8 command what value is actually in use) and meaning: .Bl -tag -width indent +.It Em net.inet.ip.fw.autoinc_step : No 100 +Delta beween rule numbers when auto-generating them. +The value must be in the range 1..1000. +.It Em net.inet.ip.fw.curr_dyn_buckets : Em net.inet.ip.fw.dyn_buckets +The current number of buckets in the hash table for dynamic rules +(readonly). .It Em net.inet.ip.fw.debug : No 1 Controls debugging messages produced by .Nm . -.It Em net.inet.ip.fw.one_pass : No 1 -When set, the packet exiting from the -.Xr dummynet 4 -pipe is not passed though the firewall again. -Otherwise, after a pipe action, the packet is -reinjected into the firewall at the next rule. -.It Em net.inet.ip.fw.verbose : No 1 -Enables verbose messages. -.It Em 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 Em net.inet.ip.fw.verbose_limit : No 0 -Limits the number of messages produced by a verbose firewall. .It Em net.inet.ip.fw.dyn_buckets : No 256 -.It Em net.inet.ip.fw.curr_dyn_buckets : No 256 -The configured and current size of the hash table used to -hold dynamic rules. -This must be a power of 2. -The table can only be resized when empty, so in order to -resize it on the fly you will probably have to +The number of buckets in the hash table for dynamic rules. +Must be a power of 2, up to 1^^20. +It only takes effect when all dynamic rules have expired, so you +are advised to use a .Cm flush -and reload the ruleset. +command to make sure that the hash table is resized. .It Em net.inet.ip.fw.dyn_count : No 3 Current number of dynamic rules (read-only). -.It Em net.inet.ip.fw.dyn_max : No 1000 +.It Em 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 Em 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. @@ -1333,7 +1511,31 @@ 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 +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 Em 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 Em net.inet.ip.fw.one_pass : No 1 +When set, the packet exiting from the +.Xr dummynet 4 +pipe is not passed though the firewall again. +Otherwise, after a pipe action, the packet is +reinjected into the firewall at the next rule. +.Pp +Note: bridged and layer 2 packets coming out of a pipe +are never reinjected in the firewall irrespective of the +value of this variable. +.It Em net.inet.ip.fw.verbose : No 1 +Enables verbose messages. +.It Em net.inet.ip.fw.verbose_limit : No 0 +Limits the number of messages produced by a verbose firewall. .It Em net.link.ether.ipfw : No 0 Controls whether layer-2 packets are passed to .Nm . @@ -1343,7 +1545,68 @@ Controls whether bridged packets are passed to .Nm . Default is no. .El +.Sh IPFW2 ENHANCEMENTS +This Section lists the features that have been introduced in +.Nm ipfw2 +and were not present in +.Nm ipfw1 . +We list them in order of the potential impact that they can +have in writing your rulesets. +You might want to consider using these features in order to +write your rulesets in a more efficient way. +.Bl -tag -width indent +.It Address sets +.Nm ipfw1 +does not supports address sets (those in the form +.Ar addr/masklen{num,num,...} +) +.It Port specifications +.Nm ipfw1 +only allows one port range when specifying TCP and UDP ports, and +is limited to 10 entries instead of the 15 allowed by +.Nm ipfw2 . +Also, in +.Nm ipfw1 +you can only specify ports when the rule is requesting +.Cm tcp +or +.Cm udp +packets. With +.Nm ipfw2 +you can put port specifications in rules matching all packets, +and the match will be attempted only on those packets carrying +protocols which include port identifiers. +.It Or-blocks +.Nm ipfw1 +does not support Or-blocks. All match operators are implicitly +connected by +.Cm and +operators. +.It keepalives +.Nm ipfw1 +does not generate keepalives for stateful sessions. +As a consequence, it might cause idle sessions to drop because +the lifetime of the dynamic rules expires. +.It Sets of rules +.Nm ipfw1 +does not implement sets of rules. +.It MAC header filtering and Layer-2 firewalling. +.Nm ipfw1 +does not implement filtering on MAC header fields, nor it is +invoked on packets from +.Cm ether_demux() +and +.Cm ether_output_frame(). +The sysctl variable +.Em net.link.ether.ipfw +has no effect there. +.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 @@ -1375,6 +1638,24 @@ 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 +.Nm ipfw2 +syntax to specify 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 +.Nm ipfw1 +syntax would require a separate rule for each IP in the above +example. +.Ss DYNAMIC RULES In order to protect a site from flood attacks involving fake TCP packets, it is safer to use dynamic rules: .Pp @@ -1434,6 +1715,7 @@ to divert port 5000: .Pp .Dl ipfw divert 5000 ip from 192.168.2.0/24 to any in .Pp +.Ss TRAFFIC SHAPING The following rules show some of the applications of .Nm and @@ -1525,6 +1807,27 @@ on a net with per-host limits, rather than per-network limits: .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 SETS OF RULES +To add a set of rules atomically, e.g. set 18: +.Pp +.Dl "ipfw disable set 18" +.Dl "ipfw add NN set 18 ... # repeat as needed" +.Dl "ipfw enable set 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 disable set 18" +.Dl "ipfw add NN set 18 ... # repeat as needed" +.Dl "ipfw enable set 18 ; echo done; sleep 30 && ipfw disable set 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. .Sh SEE ALSO .Xr cpp 1 , .Xr m4 1 , diff --git a/sbin/ipfw/ipfw2.c b/sbin/ipfw/ipfw2.c index 0f577e583945..3aac954a5d20 100644 --- a/sbin/ipfw/ipfw2.c +++ b/sbin/ipfw/ipfw2.c @@ -223,6 +223,7 @@ enum tokens { TOK_ICMPTYPES, TOK_PLR, + TOK_NOERROR, TOK_BUCKETS, TOK_DSTIP, TOK_SRCIP, @@ -241,6 +242,7 @@ enum tokens { struct _s_x dummynet_params[] = { { "plr", TOK_PLR }, + { "noerror", TOK_NOERROR }, { "buckets", TOK_BUCKETS }, { "dst-ip", TOK_DSTIP }, { "src-ip", TOK_SRCIP }, @@ -502,8 +504,10 @@ fill_newports(ipfw_insn_u16 *cmd, char *av, int proto) p[1] = b; } else if (*s == ',' || *s == '\0' ) { p[0] = p[1] = a; - } else /* invalid separator */ - break; + } else { /* invalid separator */ + errx(EX_DATAERR, "invalid separator <%c> in <%s>\n", + *s, av); + } av = s+1; } if (i > 0) { @@ -737,17 +741,29 @@ print_icmptypes(ipfw_insn_u32 *cmd) * 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. + * + * Special case: if we have provided a MAC header, and no IP specs, + * just leave it alone. + * Also, if we have providea a MAC header and no IP protocol, print it + * as "all" instead of "ip". */ -#define HAVE_PROTO 1 -#define HAVE_SRCIP 2 -#define HAVE_DSTIP 4 -#define HAVE_MAC 8 +#define HAVE_PROTO 0x0001 +#define HAVE_SRCIP 0x0002 +#define HAVE_DSTIP 0x0004 +#define HAVE_MAC 0x0008 +#define HAVE_MACTYPE 0x0010 +#define HAVE_IP (HAVE_PROTO | HAVE_SRCIP | HAVE_DSTIP) static void show_prerequisites(int *flags, int want) { + if ( (*flags & (HAVE_MAC | HAVE_MACTYPE)) == HAVE_MAC) { + printf(" any"); /* MAC type */ + *flags |= HAVE_MACTYPE; + } + if ( !(*flags & HAVE_PROTO) && (want & HAVE_PROTO)) - printf(" ip"); + printf( (*flags & HAVE_MAC) ? " all" : " ip"); if ( !(*flags & HAVE_SRCIP) && (want & HAVE_SRCIP)) printf(" from any"); if ( !(*flags & HAVE_DSTIP) && (want & HAVE_DSTIP)) @@ -907,6 +923,9 @@ show_ipfw(struct ip_fw *rule) break; case O_MAC_TYPE: + if ( (flags & HAVE_MAC) == 0) + printf(" MAC"); + flags |= (HAVE_MAC | HAVE_MACTYPE); print_newports((ipfw_insn_u16 *)cmd, IPPROTO_ETHERTYPE); break; @@ -1340,6 +1359,115 @@ list_pipes(void *data, int nbytes, int ac, char *av[]) } } +/* + * 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 + */ +static void +sets_handler(int ac, char *av[]) +{ + u_int32_t set_disable, masks[2]; + int i, nbytes; + u_int16_t rulenum; + u_int8_t cmd, new_set; + + ac--; + av++; + + if (!ac) + errx(EX_USAGE, "set needs command"); + if (!strncmp(*av, "show", strlen(*av)) ) { + void *data; + char *msg; + + nbytes = sizeof(struct ip_fw); + if ((data = malloc(nbytes)) == NULL) + err(EX_OSERR, "malloc"); + if (getsockopt(s, IPPROTO_IP, IP_FW_GET, data, &nbytes) < 0) + err(EX_OSERR, "getsockopt(IP_FW_GET)"); + set_disable = (u_int32_t)(((struct ip_fw *)data)->next_rule); + + for (i = 0, msg = "disable" ; i < 31; i++) + if ( (set_disable & (1< 30) + errx(EX_DATAERR, "invalid set number %s\n", av[0]); + if (!isdigit(*(av[1])) || new_set > 30) + errx(EX_DATAERR, "invalid set number %s\n", av[1]); + masks[0] = (4 << 24) | (new_set << 16) | (rulenum); + i = setsockopt(s, IPPROTO_IP, IP_FW_DEL, + masks, sizeof(u_int32_t)); + } else if (!strncmp(*av, "move", strlen(*av))) { + ac--; av++; + if (ac && !strncmp(*av, "rule", strlen(*av))) { + cmd = 2; + ac--; av++; + } else + cmd = 3; + if (ac != 3 || strncmp(av[1], "to", strlen(*av))) + errx(EX_USAGE, "syntax: set move [rule] X to Y\n"); + rulenum = atoi(av[0]); + new_set = atoi(av[2]); + if (!isdigit(*(av[0])) || (cmd == 3 && rulenum > 30) || + (cmd == 2 && rulenum == 65535) ) + errx(EX_DATAERR, "invalid source number %s\n", av[0]); + if (!isdigit(*(av[2])) || new_set > 30) + errx(EX_DATAERR, "invalid dest. set %s\n", av[1]); + masks[0] = (cmd << 24) | (new_set << 16) | (rulenum); + i = setsockopt(s, IPPROTO_IP, IP_FW_DEL, + masks, sizeof(u_int32_t)); + } else if (!strncmp(*av, "disable", strlen(*av)) || + !strncmp(*av, "enable", strlen(*av)) ) { + int which = !strncmp(*av, "enable", strlen(*av)) ? 1 : 0; + + ac--; av++; + masks[0] = masks[1] = 0; + + while (ac) { + if (isdigit(**av)) { + i = atoi(*av); + if (i < 0 || i > 30) + errx(EX_DATAERR, + "invalid set number %d\n", i); + masks[which] |= (1<next_rule); - - for (i = 0; i < 31; i++) - printf("%s set %d\n", - set_disable & (1< 0 && !strncmp(*av, "set", strlen(*av))) { - if (do_set == 0) - do_set = 1; /* delete set */ + do_set = 1; /* delete set */ ac--; av++; - } else if (do_set != 0) - errx(EX_DATAERR, "missing 'set' keyword"); + } /* Rule number */ while (ac && isdigit(**av)) { @@ -1728,7 +1833,7 @@ delete(int ac, char *av[]) pipe.fs.fs_nr); } } else { - rulenum = (i & 0xffff) | (do_set << 16); + rulenum = (i & 0xffff) | (do_set << 24); i = setsockopt(s, IPPROTO_IP, IP_FW_DEL, &rulenum, sizeof rulenum); if (i) { @@ -1804,6 +1909,10 @@ config_pipe(int ac, char **av) ac--; av++; switch(tok) { + case TOK_NOERROR: + pipe.fs.flags_fs |= DN_NOERROR; + break; + case TOK_PLR: NEED1("plr needs argument 0..1\n"); d = strtod(av[0], NULL); @@ -2173,21 +2282,20 @@ add_mac(ipfw_insn *cmd, int ac, char *av[]) { ipfw_insn_mac *mac; /* also *src */ - if (ac <3) - errx(EX_DATAERR, "MAC dst src type"); + if (ac <2) + errx(EX_DATAERR, "MAC dst src [type]"); cmd->opcode = O_MACADDR2; cmd->len = (cmd->len & (F_NOT | F_OR)) | F_INSN_SIZE(ipfw_insn_mac); mac = (ipfw_insn_mac *)cmd; - get_mac_addr_mask(av[0], mac->addr, mac->mask); /* dst */ + get_mac_addr_mask(av[0], mac->addr, mac->mask); /* dst */ get_mac_addr_mask(av[1], &(mac->addr[6]), &(mac->mask[6])); /* src */ - av += 2; - - if (strcmp(av[0], "any") != 0) { /* we have a non-null port */ + + if (ac>2 && strcmp(av[2], "any") != 0) { /* we have a non-null type */ cmd += F_LEN(cmd); - fill_newports((ipfw_insn_u16 *)cmd, av[0], IPPROTO_ETHERTYPE); + fill_newports((ipfw_insn_u16 *)cmd, av[2], IPPROTO_ETHERTYPE); cmd->opcode = O_MAC_TYPE; } @@ -2467,10 +2575,9 @@ add(int ac, char *av[]) if (!strncmp(*av, "all", strlen(*av))) ; /* same as "ip" */ else if (!strncmp(*av, "MAC", strlen(*av))) { - /* need exactly 3 fields */ - cmd = add_mac(cmd, ac-1, av+1); /* exits in case of errors */ - ac -= 3; + cmd = add_mac(cmd, ac-1, av+1); /* exits in case of errors */ av += 3; + ac -= 3; have_mac = 1; } else if ((proto = atoi(*av)) > 0) ; /* all done! */ @@ -2916,7 +3023,7 @@ add(int ac, char *av[]) } static void -zero (int ac, char *av[]) +zero(int ac, char *av[]) { int rulenum; int failed = EX_OK; @@ -2955,7 +3062,7 @@ zero (int ac, char *av[]) } static void -resetlog (int ac, char *av[]) +resetlog(int ac, char *av[]) { int rulenum; int failed = EX_OK; @@ -3105,9 +3212,7 @@ ipfw_main(int ac, char **av) add(ac, av); else if (do_pipe && !strncmp(*av, "config", strlen(*av))) config_pipe(ac, av); - else if (!strncmp(*av, "delete", strlen(*av)) || - !strncmp(*av, "disable", strlen(*av)) || - !strncmp(*av, "enable", strlen(*av))) + else if (!strncmp(*av, "delete", strlen(*av))) delete(ac, av); else if (!strncmp(*av, "flush", strlen(*av))) flush(); @@ -3118,6 +3223,8 @@ ipfw_main(int ac, char **av) else if (!strncmp(*av, "print", strlen(*av)) || !strncmp(*av, "list", strlen(*av))) list(ac, av); + else if (!strncmp(*av, "set", strlen(*av))) + sets_handler(ac, av); else if (!strncmp(*av, "show", strlen(*av))) { do_acct++; list(ac, av); diff --git a/sys/netinet/ip_fw2.c b/sys/netinet/ip_fw2.c index 5f86a7c4fe80..f5753e6a74f5 100644 --- a/sys/netinet/ip_fw2.c +++ b/sys/netinet/ip_fw2.c @@ -77,6 +77,14 @@ #include /* XXX for in_cksum */ +/* + * XXX This one should go in sys/mbuf.h. It is used to avoid that + * a firewall-generated packet loops forever through the firewall. + */ +#ifndef M_SKIP_FIREWALL +#define M_SKIP_FIREWALL 0x4000 +#endif + /* * set_disable contains one bit per set value (0..31). * If the bit is set, all rules with the corresponding set @@ -168,12 +176,22 @@ static u_int32_t dyn_rst_lifetime = 1; static u_int32_t dyn_udp_lifetime = 10; static u_int32_t dyn_short_lifetime = 5; +/* + * Keepalives are sent if dyn_keepalive is set. They are sent every + * dyn_keepalive_period seconds, in the last dyn_keepalive_interval + * seconds of lifetime of a rule. + * dyn_rst_lifetime and dyn_fin_lifetime should be strictly lower + * than dyn_keepalive_period. + */ + +static u_int32_t dyn_keepalive_interval = 20; +static u_int32_t dyn_keepalive_period = 5; static u_int32_t dyn_keepalive = 1; /* do send keepalives */ static u_int32_t static_count; /* # of static rules */ static u_int32_t static_len; /* size in bytes of static rules */ static u_int32_t dyn_count; /* # of dynamic rules */ -static u_int32_t dyn_max = 1000; /* max # of dynamic rules */ +static u_int32_t dyn_max = 4096; /* max # of dynamic rules */ SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_buckets, CTLFLAG_RW, &dyn_buckets, 0, "Number of dyn. buckets"); @@ -754,6 +772,7 @@ lookup_dyn_rule(struct ipfw_flow_id *pkt, int *match_direction, case TH_SYN: /* opening */ q->expire = time_second + dyn_syn_lifetime; break; + case BOTH_SYN: /* move to established */ case BOTH_SYN | TH_FIN : /* one side tries to close */ case BOTH_SYN | (TH_FIN << 8) : @@ -776,9 +795,13 @@ lookup_dyn_rule(struct ipfw_flow_id *pkt, int *match_direction, } q->expire = time_second + dyn_ack_lifetime; break; + case BOTH_SYN | BOTH_FIN: /* both sides closed */ + if (dyn_fin_lifetime >= dyn_keepalive_period) + dyn_fin_lifetime = dyn_keepalive_period - 1; q->expire = time_second + dyn_fin_lifetime; break; + default: #if 0 /* @@ -788,6 +811,8 @@ lookup_dyn_rule(struct ipfw_flow_id *pkt, int *match_direction, if ( (q->state & ((TH_RST << 8)|TH_RST)) == 0) printf("invalid state: 0x%x\n", q->state); #endif + if (dyn_rst_lifetime >= dyn_keepalive_period) + dyn_rst_lifetime = dyn_keepalive_period - 1; q->expire = time_second + dyn_rst_lifetime; break; } @@ -806,8 +831,14 @@ lookup_dyn_rule(struct ipfw_flow_id *pkt, int *match_direction, static void realloc_dynamic_table(void) { - /* try reallocation, make sure we have a power of 2 */ + /* + * Try reallocation, make sure we have a power of 2 and do + * not allow more than 64k entries. In case of overflow, + * default to 1024. + */ + if (dyn_buckets > 65536) + dyn_buckets = 1024; if ((dyn_buckets & (dyn_buckets-1)) != 0) { /* not a power of 2 */ dyn_buckets = curr_dyn_buckets; /* reset */ return; @@ -815,8 +846,13 @@ realloc_dynamic_table(void) curr_dyn_buckets = dyn_buckets; if (ipfw_dyn_v != NULL) free(ipfw_dyn_v, M_IPFW); - ipfw_dyn_v = malloc(curr_dyn_buckets * sizeof(ipfw_dyn_rule *), + for (;;) { + ipfw_dyn_v = malloc(curr_dyn_buckets * sizeof(ipfw_dyn_rule *), M_IPFW, M_DONTWAIT | M_ZERO); + if (ipfw_dyn_v != NULL || curr_dyn_buckets <= 2) + break; + curr_dyn_buckets /= 2; + } } /** @@ -1084,6 +1120,7 @@ send_pkt(struct ipfw_flow_id *id, u_int32_t seq, u_int32_t ack, int flags) ip->ip_len = m->m_pkthdr.len; bzero (&sro, sizeof (sro)); ip_rtaddr(ip->ip_dst, &sro); + m->m_flags |= M_SKIP_FIREWALL; ip_output(m, NULL, &sro, 0, NULL); if (sro.ro_rt) RTFREE(sro.ro_rt); @@ -1191,8 +1228,7 @@ ipfw_chk(struct ip_fw_args *args) * are documented here. Should you change them, please check * the implementation of the various instructions to make sure * that they still work. - */ - /* + * * args->eh The MAC header. It is non-null for a layer2 * packet, it is NULL for a layer-3 packet. * @@ -1254,6 +1290,8 @@ ipfw_chk(struct ip_fw_args *args) int dyn_dir = MATCH_UNKNOWN; ipfw_dyn_rule *q = NULL; + if (m->m_flags & M_SKIP_FIREWALL) + return 0; /* accept */ /* * dyn_dir = MATCH_UNKNOWN when rules unchecked, * MATCH_NONE when checked and not matched (q = NULL), @@ -2057,39 +2095,41 @@ free_chain(struct ip_fw **chain, int kill_default) /** * Remove all rules with given number, and also do set manipulation. * - * The argument is an int. The low 16 bit are the - * rule or set number, the upper 16 bits are the - * function, namely: + * The argument is an u_int32_t. The low 16 bit are the rule or set number, + * the next 8 bits are the new set, the top 8 bits are the command: * - * 0 DEL_SINGLE_RULE - * 1 DELETE_RULESET - * 2 DISABLE_SET - * 3 ENABLE_SET + * 0 delete rules with given number + * 1 delete rules with given set number + * 2 move rules with given number to new set + * 3 move rules with given set number to new set + * 4 swap sets with given numbers */ static int del_entry(struct ip_fw **chain, u_int32_t arg) { struct ip_fw *prev, *rule; int s; - - u_int16_t rulenum, cmd; + u_int16_t rulenum; + u_int8_t cmd, new_set; rulenum = arg & 0xffff; - cmd = (arg >> 16) & 0xffff; + cmd = (arg >> 24) & 0xff; + new_set = (arg >> 16) & 0xff; - if (cmd > 3) + if (cmd > 4) return EINVAL; - if (cmd == 0 && rulenum == IPFW_DEFAULT_RULE) - return EINVAL; - - if (cmd != 0 && rulenum > 30) { - printf("ipfw: del_entry: invalid set number %d\n", - rulenum); + if (new_set > 30) return EINVAL; + if (cmd == 0 || cmd == 2) { + if (rulenum == IPFW_DEFAULT_RULE) + return EINVAL; + } else { + if (rulenum > 30) + return EINVAL; } - + switch (cmd) { - case 0: /* DEL_SINGLE_RULE */ + case 0: /* delete rules with given number */ /* * locate first rule to delete */ @@ -2101,18 +2141,19 @@ del_entry(struct ip_fw **chain, u_int32_t arg) return EINVAL; s = splimp(); /* no access to rules while removing */ - flush_rule_ptrs(); /* more efficient to do outside the loop */ /* - * prev remains the same throughout the cycle + * flush pointers outside the loop, then delete all matching + * rules. prev remains the same throughout the cycle. */ + flush_rule_ptrs(); while (rule && rule->rulenum == rulenum) rule = delete_rule(chain, prev, rule); splx(s); break; - case 1: /* DELETE_RULESET */ - s = splimp(); /* no access to rules while removing */ - flush_rule_ptrs(); /* more efficient to do outside the loop */ + case 1: /* delete all rules with given set number */ + s = splimp(); + flush_rule_ptrs(); for (prev = NULL, rule = *chain; rule ; ) if (rule->set == rulenum) rule = delete_rule(chain, prev, rule); @@ -2123,15 +2164,29 @@ del_entry(struct ip_fw **chain, u_int32_t arg) splx(s); break; - case 2: /* DISABLE SET */ + case 2: /* move rules with given number to new set */ s = splimp(); - set_disable |= 1 << rulenum; + for (rule = *chain; rule ; rule = rule->next) + if (rule->rulenum == rulenum) + rule->set = new_set; splx(s); break; - case 3: /* ENABLE SET */ + case 3: /* move rules with given set number to new set */ s = splimp(); - set_disable &= ~(1 << rulenum); + for (rule = *chain; rule ; rule = rule->next) + if (rule->set == rulenum) + rule->set = new_set; + splx(s); + break; + + case 4: /* swap two sets */ + s = splimp(); + for (rule = *chain; rule ; rule = rule->next) + if (rule->set == rulenum) + rule->set = new_set; + else if (rule->set == new_set) + rule->set = rulenum; splx(s); break; } @@ -2515,23 +2570,32 @@ ipfw_ctl(struct sockopt *sopt) error = sooptcopyout(sopt, rule, size); break; - case IP_FW_DEL: /* argument is an int, the rule number */ + case IP_FW_DEL: /* - * IP_FW_DEL is used for deleting single rules, - * set of rules, and manipulating set_disable. - * - * Everything is managed in del_entry(); + * IP_FW_DEL is used for deleting single rules or sets, + * and (ab)used to atomically manipulate sets. Argument size + * is used to distinguish between the two: + * sizeof(u_int32_t) + * delete single rule or set of rules, + * or reassign rules (or sets) to a different set. + * 2*sizeof(u_int32_t) + * atomic disable/enable sets. + * first u_int32_t contains sets to be disabled, + * second u_int32_t contains sets to be enabled. */ - error = sooptcopyin(sopt, &rulenum, sizeof(int), sizeof(int)); + error = sooptcopyin(sopt, rule_buf, + 2*sizeof(u_int32_t), sizeof(u_int32_t)); if (error) break; - if (rulenum == IPFW_DEFAULT_RULE) { - if (fw_debug) - printf("ipfw: can't delete rule %u\n", - (unsigned)IPFW_DEFAULT_RULE); + size = sopt->sopt_valsize; + if (size == sizeof(u_int32_t)) /* delete or reassign */ + error = del_entry(&layer3_chain, rule_buf[0]); + else if (size == 2*sizeof(u_int32_t)) /* set enable/disable */ + set_disable = + (set_disable | rule_buf[0]) & ~rule_buf[1] & + ~(1<<31); /* set 31 always enabled */ + else error = EINVAL; - } else - error = del_entry(&layer3_chain, rulenum); break; case IP_FW_ZERO: @@ -2564,6 +2628,10 @@ ipfw_ctl(struct sockopt *sopt) */ struct ip_fw *ip_fw_default_rule; +/* + * This procedure is only used to handle keepalives. It is invoked + * every dyn_keepalive_period + */ static void ipfw_tick(void * __unused unused) { @@ -2583,7 +2651,8 @@ ipfw_tick(void * __unused unused) continue; if ( (q->state & BOTH_SYN) != BOTH_SYN) continue; - if (TIME_LEQ( time_second+20, q->expire)) + if (TIME_LEQ( time_second+dyn_keepalive_interval, + q->expire)) continue; /* too early */ if (TIME_LEQ(q->expire, time_second)) continue; /* too late, rule expired */ @@ -2594,7 +2663,7 @@ ipfw_tick(void * __unused unused) } splx(s); done: - ipfw_timeout_h = timeout(ipfw_tick, NULL, 5*hz); + ipfw_timeout_h = timeout(ipfw_tick, NULL, dyn_keepalive_period*hz); } static void