diff options
author | dm <dm@cvs.openbsd.org> | 1996-01-07 02:34:41 +0000 |
---|---|---|
committer | dm <dm@cvs.openbsd.org> | 1996-01-07 02:34:41 +0000 |
commit | 01b9b71d86a5edcc543a88b2d407927fa52c042d (patch) | |
tree | 878168b4effcec4e50c243cfd1095656af14f4db /sbin | |
parent | 2defc765aa92d65e239f5b4d36582850fd58b7da (diff) |
from beurton@fnet.fr: Darren Reed's IP filter
Diffstat (limited to 'sbin')
-rw-r--r-- | sbin/Makefile | 7 | ||||
-rw-r--r-- | sbin/ipf/Makefile | 7 | ||||
-rw-r--r-- | sbin/ipf/ipf.1 | 60 | ||||
-rw-r--r-- | sbin/ipf/ipf.4 | 154 | ||||
-rw-r--r-- | sbin/ipf/ipf.5 | 259 | ||||
-rw-r--r-- | sbin/ipf/ipf.c | 289 | ||||
-rw-r--r-- | sbin/ipf/ipf.h | 43 | ||||
-rw-r--r-- | sbin/ipf/opt.c | 133 | ||||
-rw-r--r-- | sbin/ipf/parse.c | 958 | ||||
-rw-r--r-- | sbin/ipfstat/Makefile | 9 | ||||
-rw-r--r-- | sbin/ipfstat/fils.c | 212 | ||||
-rw-r--r-- | sbin/ipfstat/ipfstat.8 | 48 | ||||
-rw-r--r-- | sbin/ipfstat/kmem.c | 66 | ||||
-rw-r--r-- | sbin/ipfstat/kmem.h | 11 |
14 files changed, 2253 insertions, 3 deletions
diff --git a/sbin/Makefile b/sbin/Makefile index 0679404a971..a4bf43886ce 100644 --- a/sbin/Makefile +++ b/sbin/Makefile @@ -4,9 +4,10 @@ # Not ported: XNSrouted enpload scsiformat startslip # Missing: icheck ncheck -SUBDIR= badsect disklabel ccdconfig dmesg fastboot ifconfig init mknod \ - modload modunload mount mountd nfsd nfsiod nologin ping quotacheck \ - reboot route routed savecore shutdown slattach swapon ttyflags umount +SUBDIR= badsect disklabel ccdconfig dmesg fastboot ifconfig init ipf \ + ipfstat mknod modload modunload mount mountd nfsd nfsiod nologin \ + ping quotacheck reboot route routed savecore shutdown slattach \ + swapon ttyflags umount # support for various file systems SUBDIR+= mount_ados diff --git a/sbin/ipf/Makefile b/sbin/ipf/Makefile new file mode 100644 index 00000000000..32e77eb18fa --- /dev/null +++ b/sbin/ipf/Makefile @@ -0,0 +1,7 @@ +PROG= ipf +MAN= ipf.1 ipf.4 ipf.5 +SRCS= ipf.c parse.c opt.c +CFLAGS+=-DIPL_NAME=\"/dev/ipl\" + + +.include <bsd.prog.mk> diff --git a/sbin/ipf/ipf.1 b/sbin/ipf/ipf.1 new file mode 100644 index 00000000000..1a4abc8bf44 --- /dev/null +++ b/sbin/ipf/ipf.1 @@ -0,0 +1,60 @@ +.TH IPF 1 +.SH NAME +ipf - alters packet filtering lists for IP packet input and ouput +.SH SYNOPSIS +ipf [-AEDIsnovdr] [-F <i|o|a>] -f <\fIfilename\fP> +[ -f <\fIfilename\fP> [...]] +.SH DESCRIPTION +.PP +\fBipf\fP opens the filenames listed (treating "-" as stdin) and parses the +file for a set of rules which are to be added or removed from the packet +filter rule set. +.PP +Each rule processed by \fBipf\fP +is added to the kernels internal lists if there are no parsing problems. +Rules are added to the end of the internal lists, matching the order in +which they appear when given to \fBipf\fP. +.SH OPTIONS +.IP -A +set the list to make changes to the active list (default). +.IP -E +Enable the filter (if disabled). Not effective for loadable kernel versions. +.IP -D +Disable the filter (if enabled). Not effective for loadable kernel versions. +.IP -F +this option specifies which filter list to flush. The parameter should +either be "i" (input), "o" (output) or "a" (remove all filter rules). +Either a single letter or an entire word starting with the appropriate +letter maybe used. This option maybe before, or after, any other with +the order on the command line being that used to execute options. +.IP -d +turn debug mode on. Causes a hexdump of filter rules to be generated as +it processes each one. +.IP -f +this option specifies which files +\fBipf\fP should use to get input from for modifying the pack filter rule +lists. +.IP -I +set the list to make changes to the inactive list. +.IP -n +This flag (no-change) prevents \fBipf\fP from actually making any ioctl +calls or doing anything which would alter the currently running kernel. +.IP -o +Force rules by default to be added/deleted to/from the output list, rather +than the (default) input list. +.IP -s +swap the active filter list in use to be the "other" one. +.IP -r +remove matching filter rules rather than add them to the internal lists +.IP -v +turn verbose mode on. Displays information relating to rule processing. +.DT +.SH SEE ALSO +ipfstat(1), ipftest(1), ipf(5) +.SH DIAGNOSTICS +.PP +Needs to be run as root for the packet filtering lists to actually +be affected inside the kernel. +.SH BUGS +.PP +If you find any, please send email to me at darrenr@arbld.unimelb.edu.au diff --git a/sbin/ipf/ipf.4 b/sbin/ipf/ipf.4 new file mode 100644 index 00000000000..f9c65e77c32 --- /dev/null +++ b/sbin/ipf/ipf.4 @@ -0,0 +1,154 @@ +.TH IPF 4 +.SH NAME +ipf - packet filtering kernel interface +.SH SYNOPSIS +#include <sys/ip_fil.h> +.SH IOCTLS +.PP +To add and delete rules to the filter list, three 'basic' ioctls are provided +for use. The ioctl's are called as: +.LP +.nf + ioctl(fd, SIOCADDFR, struct frentry *) + ioctl(fd, SIOCDELFR, struct frentry *) + ioctl(fd, SIOCIPFFL, int *) +.fi +.PP +However, the full complement is as follows: +.LP +.nf + ioctl(fd, SIOCADAFR, struct frentry *) (same as SUICADDFR) + ioctl(fd, SIOCRMAFR, struct frentry *) (same as SUICDELFR) + ioctl(fd, SIOCADIFR, struct frentry *) + ioctl(fd, SIOCRMIFR, struct frentry *) + ioctl(fd, SIOCINAFR, struct frentry *) + ioctl(fd, SIOCINIFR, struct frentry *) + ioctl(fd, SIOCIPFFL, int *) +.fi +.PP +The variations, SIOCADAFR vs SIOCADIFR, allow operation on the two lists, +active and inactive, respectively. All of these ioctl's are implemented +as being routing ioctls and thus the same rules for the various routing +ioctls and the file descriptor are employed, mainly being that the fd must +be that of the device associated with the module (ie /dev/ipl). In addition +to this, these ioctl's will only succeed if made as root. +.LP +.PP +The three groups of ioctls above perform adding rules to the end of the +list (SIOCAD*), deletion of rules from any place in the list (SIOCRM*) +and insertion of a rule into the list (SIOCIN*). The rule place into +which it is inserted is stored in the "fr_hits" field, below. +.LP +.nf + +typedef struct frentry { + struct frentry *fr_next; + struct ifnet *fr_ifa; + u_int fr_hits; + + /* + * Fields after this may not change whilst in the kernel. + */ + struct ip fr_ip; + struct ip fr_mip; + + u_short fr_icmpm; /* data for ICMP packets (mask) */ + u_short fr_icmp; + + char fr_tcpfm; /* tcp flags mask */ + char fr_tcpf; /* tcp flags */ + + u_char fr_scmp; /* data for port comparisons */ + u_char fr_dcmp; + u_short fr_dport; + u_short fr_sport; + u_short fr_stop; /* top port for <> and >< */ + u_short fr_dtop; /* top port for <> and >< */ + u_short fr_flags; /* per-rule flags && options */ + char fr_ifname[IFNAMSIZ]; +} frentry_t; +.fi +.PP +Flags which are recognised in fr_pass: +.nf + + FR_BLOCK 0x0001 /* do not allow packet to pass */ + FR_PASS 0x0002 /* allow packet to pass */ + FR_OUTQUE 0x0004 /* outgoing packets */ + FR_QUICK 0x0008 /* quick-match and return */ + FR_LOGP 0x0010 /* Log-pass */ + FR_INQUE 0x0020 /* ingoing packets */ + FR_LOGB 0x0040 /* Log-fail */ + FR_LOG 0x0080 /* Log */ + FR_RETRST 0x0100 /* return a TCP RST packet if blocked */ + FR_OPTFRAG 0x0200 /* filter packets which are fragments */ + FR_OPTSHORT 0x0400 /* filter short TCP packets */ + FR_RETICMP 0x0800 /* return an ICMP packet if blocked */ + FR_TCPUDP 0x1000 /* TCP/UCP implied comparison involved */ +.fi +.PP +Values for fr_scomp and fr_dcomp (source and destination port value +comparisons) : +.LP +.nf + FR_NONE 0 + FR_EQUAL 1 + FR_NEQUAL 2 + FR_LESST 3 + FR_GREATERT 4 + FR_LESSTE 5 + FR_GREATERTE 6 + FR_OUTRANGE 7 + FR_INRANGE 8 +.fi +.PP +The third ioctl, SIOCIPFFL, flushes either the input filter list, the +output filter list or both and it returns the number of filters removed +from the list(s). The values which it will take and recognise are FR_INQUE +and FR_OUTQUE (see above). + +\fBGeneral Logging Flags\fP +There are two flags which can be set to log packets independantly of the +rules used. These allow for packets which are either passed or blocked +to be logged. To set (and clear)/get these flags, two ioctls are +provided: +.IP SIOCSETFF 16 +Takes an unsigned integer as the parameter. The flags are then set to +those provided (clearing/setting all in one). +.nf + + FF_LOGPASS 1 + FF_LOGBLOCK 2 +.fi +.IP SIOCGETFF 16 +Takes a pointer to an unsigned integer as the parameter. A copy of the +fags currently in used is copied to user space. +.LP +\fBFilter statistics\fP +Statistics on the various operations performed by this package on packets +is kept inside the kernel. These statistics apply to packets traversing +through the kernel. To retrieve this structure, use this ioctl: +.nf + + ioctl(fd, SIOCGETFS, struct friostat *) + +struct friostat { + struct filterstats f_st[2]; + struct frentry *f_fin; + struct frentry *f_fout; +}; + +struct filterstats { + u_long fr_pass; /* packets allowed */ + u_long fr_block; /* packets denied */ + u_long fr_ppkl; /* packets allowed and logged */ + u_long fr_bpkl; /* packets denied and logged */ + u_long fr_pkl; /* packets logged */ + u_long fr_skip; /* packets to be logged but buffer full */ +}; +.fi +.SH BUGS +It would be nice if there were more flexibility when adding and deleting +filter rules. +.SH SEE ALSO +ipfstat(1), ipf(1), ipf(5) diff --git a/sbin/ipf/ipf.5 b/sbin/ipf/ipf.5 new file mode 100644 index 00000000000..2e70be16e92 --- /dev/null +++ b/sbin/ipf/ipf.5 @@ -0,0 +1,259 @@ +.LP +.TH IPF 5 +.SH NAME +ipf - IP packet filtering format. +.SH DESCRIPTION +.PP +A rule file for \fBipf\fP may have any name or even be stdin. As +\fBipfstat\fP produces parseable rules as output when displaying the internal +kernel filter lists, it is quite plausible to use its output to feed back +into \fBipf\fP. Thus, to remove all filters on input packets, the following +could be done: +.nf + +\fC# ipfstat -i | ipf -rf -\fP +.fi +.PP +The format used by \fBipf\fP for construction of filtering rules can be +described using the following grammar in BNF: +\fC +.nf +filter-rule = [ insert ] action in-out [ options ] [ tos ] [ ttl ] + [ proto ] [ ip ] . + +insert = "@" decnumber +action = block | "pass" | log . +in-out = "in" | "out" . +options = [ log ] [ "quick" ] [ "on" interface-name] . +tos = "tos" decnumber | "tos" hexnumber . +ttl = "ttl" decnumber . +proto = "proto" protocol . +ip = srcdst [ flags ] [ with withopt ] [ icmp ] . + +block = "block" [ "return-icmp" ] [ "return-rst" ]. +log = "log" [ "body" ] . +protocol = "tcp/udp" | "udp" | "tcp" | "icmp" | decnumber . +srcdst = "all" | fromto . +fromto = "from" object "to" object . + +object = addr [ port-comp | port-range ] . +addr = "any" | nummask | host-name [ "mask" ipaddr | hexnumber ] . +port-comp = "port" compare port-num . +port-range = "port" port-num range port-num . +flags = "flags" flag { flag } [ "/" flag { flag } ] . +with = "with" | "and" . +icmp = "icmp-type" icmp-type . + +nummask = host-name [ "/" decnumber ] . +host-name = ipaddr | hostname | "any" . +ipaddr = host-num "." host-num "." host-num "." host-num . +host-num = digit [ digit [ digit ] ] . +port-num = service-name | decnumber . + +withopt = [ "not" | "no" ] opttype [ withopt ] . +opttype = "ipopts" | "short" | "frag" | "opt" ipopts . +ipopts = "nop" | "rr" | "ts" | "security" | "sec-class" [ "=" seclvl ] | + "lsrr" | "satid" | "rsrr" . +seclvl = "unclass" | "confid" | "reserv-1" | "reserv-2" | "reserv-3" | + "reserv-4" | "secret" | "topsecret" . +icmp-type = "unreach" | "echo" | "echorep" | "squench" | "redir" | + "timex" | "paramprob" | "timest" | "timestrep" | "inforeq" | + "inforep" | "maskreq" | "maskrep" | decnumber . + +hexnumber = "0" "x" hexstring . +hexstring = hexdigit [ hexstring ] . +decnumber = digit [ decnumber ] . + +compare = "=" | "!=" | "<" | ">" | "<=" | ">=" | "eq" | "ne" | "lt" | "gt" | + "le" | "ge" . +range = "<>" | "><" . +hexdigit = digit | "a" | "b" | "c" | "d" | "e" | "f" . +digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" . +flag = "F" | "S" | "R" | "P" | "A" | "U" . +.fi +.PP +The "briefest" valid rule is of the form: +.nf + + block in + pass in + log in +.fi +.PP +These can also be written like: +.nf + block in all pass in from any to any +.fi +.PP +The action, one of either block, log or pass, indicates what to do with +the packet if it matches the rest of the filter rule. Block indicates that +the packet should be dropped here and not let through, log write the packet +header to the \fBipl\fP packet logging psuedo-device (and has no further +effect on validity of packet to be allowed through the filter) and pass which +will allow the packet through. Each rule MUST have one of these three +keywords. +.PP +In response to blocking a packet, the filter may be instructed to send a +reply packet, either an ICMP unreachable (\fBreturn-icmp\fP)or a TCP +"reset" (\fBreturn-rst\fP). An ICMP packet may be generated in response +to any IP packet but a TCP reset may only be used with a rule which is +being applied to TCP packets. +.PP +When a packet header is logged with the \fBlog\fP keyword, the optional +\fBbody\fP keyword indicates that the first 128 bytes of the packet contents +will be logged to the \fBipl\fP packet logging psuedo-device after the +headers. +.PP +The next word must be either \fBin\fP or \fBout\fP. As each packet moving +through the kernel is either an inbound packet or outbound, there is a +requirement that each filter rule be explicitly stated as to which side of +the IO it is to be used on. +.PP +The list of options is brief, and indeed all are optional. The presence +of the \fBlog\fP option indicates, that should this be the last matching +rule, the packet header will be written to the \fBipl\fP log. The \fBquick\fP +option allows "short-cut" rules in order to speed up the filter. If a +packet header matches a filter rule which is marked as \fBquick\fP, it will +result in a quick-match and stop processing at this point. This is good for +rules such as "block in quick from any to any with ipopts" which will match +any packet with a non-standard header length (IP options present) and abort +further processing, recording a match and also that the packet should be +blocked. If this command is missing, the rule is taken to be a +"fall-through" rule, meaning that the result of the match is used +(block/pass) and that it will continue processing to see if there are any +more matches. This allows for effects such as this: +.LP +.nf + block in from any to any port < 6000 + pass in from any to any port >= 6000 + block in from any to port > 6003 +.fi +.PP +which sets up the range 6000-6003 as being permitted and all others being +denied. Another (easier) way to do the same is: +.LP +.nf + block in from any to any port 6000 <> 6003 + pass in from any to any port 5999 >< 6004 +.fi +.PP +Note that both the "block" and "pass" are needed here to affect a result +as a failed "block" does not imply a pass, only that the rule hasn't taken +effect. To then allow ports < 1024, a rule such as: +.LP +.nf + pass in quick from any to any port < 1024 +.fi +.PP +would be needed before the first block. Expect to see a "between" operator +as soon as I can work out how to fit in in. +.PP +The \fBon\fP command allows an interface name to be incorporated into the +matching procedure. That it is a match and not actually associated with +the interface itself is a result of the way this was implemented. Indeed, +there is nothing to stop you using this with every rule if you so wish. +If it is absent, the rule is taken to be applied to a packet regardless of +the interface it is present on. +.PP +The \fBall\fP command is essentially an alias for "from any to any" with +no other commands. +.PP +Using \fBtos\fP, packets with different service capabilities can be filtered +upon. Individual service levels or combinations can be filtered upon. The +value for the TOS mask can either be represented as a hex number or a +decimal integer value. +.PP +Packets may also be selected by their \fBttl\fP value. The value given in +the filter rule must exactly match that in the packet for a match to occur. +This value can only be given as a decimal integer value. +.PP +The \fBproto\fP command allows a specific protocol to be matched against. +All protocol names found in \fB/etc/protocols\fP are recognised and maybe +used. However, the protocol may also be given as a DECIMAL number, allowing +for rules to match your own protocols, or new ones which would out-date any +attempted listing. +.PP +To match against BOTH source and destination addresses, the \fBfrom\fP and +\fBto\fP commands are used. They both support a large variety of valid +syntaxes, including the "x/y" format. There is a special case for the +hostname \fBany\fP which is taken to be 0.0.0.0/0 and matches all IP numbers. +If a \fBport\fP match is included, then it is only applied to TCP/UDP +packets. If the \fBproto\fP command is left out, packets from both protocols +are compared. The hostname may either be a valid hostname, from either the +hosts file or DNS (depending on your configuration and library) or of the +dotted numeric form. There is no special designation for networks but +network names are recognised. +.PP +"x/y" indicates that a mask of y consecutive bits set is generated, starting +with the MSB, so a value of 16 would give 0xffff0000. +.PP +"x mask y" indicates that the mask y is in dotted IP notation or a hexadecimal +number of the form 0x12345678. +.PP +Only the presence of "any" has an implied mask, in all other situations, +a hostname MUST be accompanied by a mask. It is possible to give "any" a +hostmask, but in the context of this language, it is non-sensical. +.PP +When composing +\fBport\fP comparisons, either the service name may be used or an integer +port number. +.PP +The \fBwith\fP command is used to nominate irregular attributes that some +packets ma have associated with them. Alternatively, the keyword \fBand\fP +maybe used in place of \fBwith\fP. This is provided to make the rules more +readable and serves no other purpose. To filter IP options, in general, +use \fBipopts\fP. For more specific filtering on IP options, individual +options can be listed. When listed, all those listed must be found in a +packet to cause a match. +.PP +Before any option used after the \fBwith\fP keyword, the word \fBnot\fp +maybe inserted to cause the filter rule to only match if the option(s) is +not present. +.PP +The \fBflags\fP command is only effective for TCP filtering. Each of the +letters possible represents one of the possible flags that can be set in the +TCP header. The association is as follows: +.LP +.nf + F - FIN + S - SYN + R - RST + P - PUSH + A - ACK + U - URG +.fi +.PP +The various flag symbols maybe used in combination, so that "SA" would +represent a SYN-ACK combination present in a packet. There is nothing +preventing combinations, such as "SFR". However, to guard against weird +abberations, it is necessary to state which flags you are filtering against. +To allow this, it is possible to set a mask indicating which TCP flags you +wich to compare (ie those you deem significant). This is done by appending +"/<flags>" to the set of TCP flags you wish to match against. eg: +.LP +.nf + ... flags S + # becomes "flags S/AUPRFS" and will match a + # packet with ONLY the SYN flag set. + + ... flags SA + # becomes "flags SA/AUPRFS" and will match any + # packet with only the SYN and ACK flags set. + + ... flags S/SA + # will match any packet with just the SYN flag set + # out of the SYN-ACK pair; the common "establish" + # keyword action. "S/SA" will NOT match a packet + # with BOTH SYN and ACK set, but WILL match "SFP". +.fi +.PP +The last parameter set for the filter rule is the optional \fBicmp-type\fP. +It is only effective when used with \fB"proto icmp"\fP and must NOT be used +in conjuction with \fBflags\fP. There are a number of types which can be +refered to by an abbreviation recognised by this language or the numbers +with which they are associated can be used. +.SH FILES +/etc/services +/etc/hosts +.SH SEE ALSO +ipf(1), ipftest(1) diff --git a/sbin/ipf/ipf.c b/sbin/ipf/ipf.c new file mode 100644 index 00000000000..12e8ff2caef --- /dev/null +++ b/sbin/ipf/ipf.c @@ -0,0 +1,289 @@ +/* + * (C)opyright 1993,1994,1995 by Darren Reed. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and due credit is given + * to the original author and the contributors. + */ +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#if !defined(__SVR4) && !defined(__GNUC__) +#include <strings.h> +#endif +#if !defined(__SVR4) && defined(__GNUC__) +extern char *index(); +#endif +#include <sys/types.h> +#include <sys/param.h> +#include <sys/file.h> +#include <stdlib.h> +#include <unistd.h> +#include <stddef.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <net/if.h> +#include <netinet/ip.h> +#include <netinet/ip_fil.h> +#include <netdb.h> +#include <arpa/nameser.h> +#include <resolv.h> +#include "ipf.h" + +#ifndef lint +static char sccsid[] = "@(#)ipf.c 1.18 11/11/95 (C) 1993-1995 Darren Reed"; +#endif + +extern char *optarg; + +int opts = 0; + +static int fd = -1; +static void procfile(), flushfilter(), set_state(); +static void packetlogon(), swapactive(); + +int main(argc,argv) +int argc; +char *argv[]; +{ + char c; + + if ((fd = open(IPL_NAME, O_RDONLY)) == -1) + perror("open device"); + + while ((c = getopt(argc, argv, "AsInovdryf:F:l:EDZ")) != -1) + switch (c) + { + case 'E' : + set_state(1); + break; + case 'D' : + set_state(0); + break; + case 'A' : + opts &= ~OPT_INACTIVE; + break; + case 'd' : + opts |= OPT_DEBUG; + break; + case 'f' : + procfile(optarg); + break; + case 'F' : + flushfilter(optarg); + break; + case 'I' : + opts |= OPT_INACTIVE; + break; + case 'l' : + packetlogon(optarg); + break; + case 'n' : + opts |= OPT_DONOTHING; + break; + case 'o' : + opts |= OPT_OUTQUE; + break; + case 'r' : + opts |= OPT_REMOVE; + break; + case 's' : + swapactive(); + break; + case 'v' : + opts |= OPT_VERBOSE; + break; +#if defined(sun) && (defined(__SVR4) || defined(__svr4__)) + case 'y' : + frsync(); + break; +#endif + case 'Z' : + zerostats(); + break; + } + + if (fd != -1) + (void) close(fd); + return 0; +} + +static void set_state(enable) +u_int enable; +{ + if (ioctl(fd, SIOCFRENB, &enable) == -1) + perror("SIOCFRENB"); + return; +} + +static void procfile(file) +char *file; +{ + FILE *fp; + char line[513], *s; + struct frentry *fr; + u_int add = SIOCADAFR, del = SIOCRMAFR; + + if (opts & OPT_INACTIVE) { + add = SIOCADIFR; + del = SIOCRMIFR; + } + if (opts & OPT_DEBUG) + printf("add %x del %x\n", add, del); + + if (!strcmp(file, "-")) + fp = stdin; + else if (!(fp = fopen(file, "r"))) + return; + + while (fgets(line, sizeof(line)-1, fp)) { + /* + * treat both CR and LF as EOL + */ + if ((s = index(line, '\n'))) + *s = '\0'; + if ((s = index(line, '\r'))) + *s = '\0'; + /* + * # is comment marker, everything after is a ignored + */ + if ((s = index(line, '#'))) + *s = '\0'; + + if (!*line) + continue; + + if (opts & OPT_VERBOSE) + (void)fprintf(stderr, "[%s]\n",line); + + fr = parse(line); + (void)fflush(stdout); + + if (fr) { + if (opts & OPT_INACTIVE) + add = fr->fr_hits ? SIOCINIFR : SIOCADIFR; + else + add = fr->fr_hits ? SIOCINAFR : SIOCADAFR; + if (fr->fr_hits) + fr->fr_hits--; + if (fr && (opts & OPT_VERBOSE)) + printfr(fr); + if (fr && (opts & OPT_OUTQUE)) + fr->fr_flags |= FR_OUTQUE; + + if (opts & OPT_DEBUG) + binprint(fr); + + if ((opts & OPT_REMOVE) && !(opts & OPT_DONOTHING)) { + if (ioctl(fd, del, fr) == -1) + perror("ioctl(SIOCDELFR)"); + } else if (!(opts & OPT_DONOTHING)) { + if (ioctl(fd, add, fr) == -1) + perror("ioctl(SIOCADDFR)"); + } + } + } + (void)fclose(fp); +} + + +static void packetlogon(opt) +char *opt; +{ + int err, flag; + + if (opts & OPT_VERBOSE) { + if ((err = ioctl(fd, SIOCGETFF, &flag))) + perror("ioctl(SIOCGETFF)"); + + printf("log flag is currently %#x\n", flag); + } + + flag = 0; + + if (index(opt, 'p')) { + flag |= FF_LOGPASS; + if (opts & OPT_VERBOSE) + printf("set log flag: pass\n"); + } + if (index(opt, 'b') || index(opt, 'd')) { + flag |= FF_LOGBLOCK; + if (opts & OPT_VERBOSE) + printf("set log flag: block\n"); + } + + if (!(opts & OPT_DONOTHING) && + (err = ioctl(fd, SIOCSETFF, &flag))) + perror("ioctl(SIOCSETFF)"); + + if (opts & OPT_VERBOSE) { + if ((err = ioctl(fd, SIOCGETFF, &flag))) + perror("ioctl(SIOCGETFF)"); + + printf("log flag is now %#x\n", flag); + } +} + + +static void flushfilter(arg) +char *arg; +{ + int fl = 0, rem; + + if (!arg || !*arg) + return; + if (*arg == 'i' || *arg == 'I') + fl = FR_INQUE; + else if (*arg == 'o' || *arg == 'O') + fl = FR_OUTQUE; + else if (*arg == 'a' || *arg == 'A') + fl = FR_OUTQUE|FR_INQUE; + fl |= (opts & FR_INACTIVE); + rem = fl; + + if (!(opts & OPT_DONOTHING) && ioctl(fd, SIOCIPFFL, &fl) == -1) + perror("ioctl(SIOCIPFFL)"); + if (opts & OPT_VERBOSE){ + printf("remove flags %s%s (%d)\n", (rem & FR_INQUE) ? "I" : "", + (rem & FR_OUTQUE) ? "O" : "", rem); + printf("removed %d filter rules\n", fl); + } + return; +} + + +static void swapactive() +{ + int in = 2; + + if (ioctl(fd, SIOCSWAPA, &in) == -1) + perror("ioctl(SIOCSWAPA)"); + else + printf("Set %d now inactive\n", in); +} + + +#if defined(sun) && (defined(__SVR4) || defined(__svr4__)) +frsync() +{ + if (ioctl(fd, SIOCFRSYN, 0) == -1) + perror("SIOCFRSYN"); + else + printf("filter sync'd\n"); +} +#endif + + +zerostats() +{ + struct friostat fio; + + if (ioctl(fd, SIOCFRZST, &fio) == -1) { + perror("ioctl(SIOCFRZST)"); + exit(-1); + } + +} diff --git a/sbin/ipf/ipf.h b/sbin/ipf/ipf.h new file mode 100644 index 00000000000..537c15b901d --- /dev/null +++ b/sbin/ipf/ipf.h @@ -0,0 +1,43 @@ +/* + * (C)opyright 1993,1994,1995 by Darren Reed. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and due credit is given + * to the original author and the contributors. + * + * @(#)ipf.h 1.7 10/15/95 + */ + +#define OPT_INQUE FR_INQUE /* 0x0001 */ +#define OPT_REMOVE 0x0002 +#define OPT_OUTQUE FR_OUTQUE /* 0x0004 */ +#define OPT_DEBUG 0x0008 +#define OPT_SHOWLIST 0x0010 +#define OPT_VERBOSE 0x0020 +#define OPT_LOG FR_LOG /* 0x0040 */ +#define OPT_DONOTHING 0x0080 +#define OPT_INACTIVE FR_INACTIVE /* 0x0800 */ +#define OPT_HITS 0x10000 +#define OPT_BRIEF 0x20000 + +extern struct frentry *parse(); + +extern void printfr(), binprint(); + +#if defined(__SVR4) || defined(__svr4__) +#define index strchr +#define bzero(a,b) memset(a, 0, b) +#define bcopy(a,b,c) memmove(b,a,c) +#endif + +struct ipopt_names { + int on_value; + int on_bit; + int on_siz; + char *on_name; +}; + + +extern u_long hostnum(), optname(); +extern void printpacket(); + diff --git a/sbin/ipf/opt.c b/sbin/ipf/opt.c new file mode 100644 index 00000000000..5b7debfb2b4 --- /dev/null +++ b/sbin/ipf/opt.c @@ -0,0 +1,133 @@ +/* + * (C)opyright 1993,1994,1995 by Darren Reed. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and due credit is given + * to the original author and the contributors. + */ +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_var.h> +#include <netinet/tcp.h> +#include <netinet/tcpip.h> +#include <net/if.h> +#include <netinet/ip_fil.h> +#include "ipf.h" + +#ifndef lint +static char sccsid[] = "@(#)opt.c 1.6 11/11/95 (C) 1993-1995 Darren Reed"; +#endif + +extern int opts; + +struct ipopt_names ionames[] ={ + { IPOPT_NOP, 0x000001, 1, "nop" }, + { IPOPT_RR, 0x000002, 7, "rr" }, /* 1 route */ + { IPOPT_ZSU, 0x000004, 3, "zsu" }, + { IPOPT_MTUP, 0x000008, 3, "mtup" }, + { IPOPT_MTUR, 0x000010, 3, "mtur" }, + { IPOPT_ENCODE, 0x000020, 3, "encode" }, + { IPOPT_TS, 0x000040, 8, "ts" }, /* 1 TS */ + { IPOPT_TR, 0x000080, 3, "tr" }, + { IPOPT_SECURITY,0x000100, 11, "sec" }, + { IPOPT_SECURITY,0x000100, 11, "sec-class" }, + { IPOPT_LSRR, 0x000200, 7, "lsrr" }, /* 1 route */ + { IPOPT_E_SEC, 0x000400, 3, "e-sec" }, + { IPOPT_CIPSO, 0x000800, 3, "cipso" }, + { IPOPT_SATID, 0x001000, 4, "satid" }, + { IPOPT_SSRR, 0x002000, 7, "ssrr" }, /* 1 route */ + { IPOPT_ADDEXT, 0x004000, 3, "addext" }, + { IPOPT_VISA, 0x008000, 3, "visa" }, + { IPOPT_IMITD, 0x010000, 3, "imitd" }, + { IPOPT_EIP, 0x020000, 3, "eip" }, + { IPOPT_FINN, 0x040000, 3, "finn" }, + { 0, 0, 0, (char *)NULL } /* must be last */ +}; + +struct ipopt_names secclass[] = { + { IPSO_CLASS_RES4, 0x01, 0, "reserv-4" }, + { IPSO_CLASS_TOPS, 0x02, 0, "topsecret" }, + { IPSO_CLASS_SECR, 0x04, 0, "secret" }, + { IPSO_CLASS_RES3, 0x08, 0, "reserv-3" }, + { IPSO_CLASS_CONF, 0x10, 0, "confid" }, + { IPSO_CLASS_UNCL, 0x20, 0, "unclass" }, + { IPSO_CLASS_RES2, 0x40, 0, "reserv-2" }, + { IPSO_CLASS_RES1, 0x40, 0, "reserv-1" }, + { 0, 0, 0, NULL } /* must be last */ +}; + + +static u_char seclevel(slevel) +char *slevel; +{ + struct ipopt_names *so; + + for (so = secclass; so->on_name; so++) + if (!strcasecmp(slevel, so->on_name)) + break; + + if (!so->on_name) { + fprintf(stderr, "no such security level: %s\n", slevel); + return 0; + } + return (u_char)so->on_value; +} + + +u_long buildopts(cp, op) +char *cp, *op; +{ + struct ipopt_names *io; + u_char lvl; + u_long msk = 0; + char *s, *t; + int len = 0; + + for (s = strtok(cp, ","); s; s = strtok(NULL, ",")) { + if ((t = strchr(s, '='))) + *t++ = '\0'; + for (io = ionames; io->on_name; io++) { + if (strcasecmp(s, io->on_name) || (msk & io->on_bit)) + continue; + if ((len + io->on_siz) > 48) { + fprintf(stderr, "options too long\n"); + return 0; + } + len += io->on_siz; + *op++ = io->on_value; + if (io->on_siz > 1) { + *op++ = io->on_siz; + *op++ = IPOPT_MINOFF; + + if (t && !strcasecmp(s, "sec-class")) { + lvl = seclevel(t); + *op = lvl; + } + op += io->on_siz - 3; + if (len & 3) { + *op++ = IPOPT_NOP; + len++; + } + } + if (opts & OPT_DEBUG) + fprintf(stderr, "bo: %s %d %#x: %d\n", + io->on_name, io->on_value, + io->on_bit, len); + msk |= io->on_bit; + break; + } + if (!io->on_name) { + fprintf(stderr, "unknown IP option name %s\n", s); + return 0; + } + } + *op++ = IPOPT_EOL; + len++; + return len; +} diff --git a/sbin/ipf/parse.c b/sbin/ipf/parse.c new file mode 100644 index 00000000000..56cf14d0afd --- /dev/null +++ b/sbin/ipf/parse.c @@ -0,0 +1,958 @@ +/* + * (C)opyright 1993,1994,1995 by Darren Reed. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and due credit is given + * to the original author and the contributors. + */ +#include <stdio.h> +#include <string.h> +#if !defined(__SVR4) && !defined(__svr4__) +#include <strings.h> +#else +#include <sys/byteorder.h> +#endif +#include <sys/types.h> +#include <sys/param.h> +#include <stdlib.h> +#include <unistd.h> +#include <stddef.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/tcp.h> +#include <net/if.h> +#include <netinet/ip_fil.h> +#include <netdb.h> +#include <arpa/nameser.h> +#include <arpa/inet.h> +#include <resolv.h> +#include "ipf.h" +#include <ctype.h> + +#ifndef lint +static char sccsid[] ="@(#)parse.c 1.25 11/11/95 (C) 1993 Darren Reed"; +#endif + +extern struct ipopt_names ionames[], secclass[]; +extern int opts; + +u_long hostnum(), optname(); +u_short portnum(); +u_char tcp_flags(); +struct frentry *parse(); +void binprint(), printfr(); +int addicmp(), extras(), hostmask(), ports(); + +char *proto = NULL; +char flagset[] = "FSRPAU"; +u_char flags[] = { TH_FIN, TH_SYN, TH_RST, TH_PUSH, TH_ACK, TH_URG }; + +/* parse() + * + * parse a line read from the input filter rule file + */ +struct frentry *parse(line) +char *line; +{ + static struct frentry fil; + struct protoent *p = NULL; + char *cps[31], **cpp; + u_char ch; + int i, cnt = 1; + + bzero((char *)&fil, sizeof(fil)); + fil.fr_mip.fi_v = 0xf; + fil.fr_ip.fi_v = 4; + /* + * break line up into max of 20 segments + */ + if (opts & OPT_DEBUG) + fprintf(stderr, "parse [%s]\n", line); + for (i = 0, *cps = strtok(line, " \b\t\r\n"); cps[i] && i < 30; cnt++) + cps[++i] = strtok(NULL, " \b\t\r\n"); + cps[i] = NULL; + + if (cnt < 3) { + (void)fprintf(stderr,"not enough segments in line\n"); + return NULL; + } + + cpp = cps; + if (**cpp == '@') + fil.fr_hits = atoi(*cpp++ + 1) + 1; + /* + * does it start with one of the two possible first words ? + */ + if (strcasecmp("block",*cpp) && strcasecmp("pass",*cpp) && + strcasecmp("log",*cpp)) { + (void)fprintf(stderr, "unknown keyword (%s)\n", *cpp); + return NULL; + } + if (**cpp == 'b') { + fil.fr_flags = FR_BLOCK; + if (!strncasecmp(*(cpp+1), "return-icmp", 11)) { + fil.fr_flags |= FR_RETICMP; + cpp++; + } else if (!strncasecmp(*(cpp+1), "return-rst", 10)) { + fil.fr_flags |= FR_RETRST; + cpp++; + } + } else if (**cpp == 'p') + fil.fr_flags = FR_PASS; + else if (**cpp == 'l') { + fil.fr_flags = FR_LOG; + if (!strcasecmp(*(cpp+1), "body")) { + fil.fr_flags |= FR_LOGBODY; + cpp++; + } + } + cpp++; + + if (!strcasecmp("in", *cpp)) + fil.fr_flags |= FR_INQUE; + else if (!strcasecmp("out", *cpp)) + fil.fr_flags |= FR_OUTQUE; + else { + (void)fprintf(stderr, "missing 'in'/'out' keyword (%s)\n", + *cpp); + return NULL; + } + + if (!*++cpp) + return NULL; + if (!strcasecmp("log", *cpp)) { + cpp++; + if (fil.fr_flags & FR_PASS) + fil.fr_flags |= FR_LOGP; + else if (fil.fr_flags & FR_BLOCK) + fil.fr_flags |= FR_LOGB; + if (!strcasecmp(*cpp, "body")) { + fil.fr_flags |= FR_LOGBODY; + cpp++; + } + } + + if (!strcasecmp("quick", *cpp)) { + cpp++; + fil.fr_flags |= FR_QUICK; + } + + *fil.fr_ifname = '\0'; + if (!strcasecmp(*cpp, "on")) { + if (!*++cpp) { + (void)fprintf(stderr, "interface name missing\n"); + return NULL; + } + (void)strncpy(fil.fr_ifname, *cpp, IFNAMSIZ-1); + fil.fr_ifname[IFNAMSIZ-1] = '\0'; + cpp++; + if (!*cpp) { + if (fil.fr_flags & FR_RETRST) { + (void)fprintf(stderr, + "%s can only be used with TCP\n", + "return-rst"); + return NULL; + } + return &fil; + } + } + + if (!strcasecmp(*cpp, "tos")) { + if (!*++cpp) { + (void)fprintf(stderr, "tos missing value\n"); + return NULL; + } + fil.fr_tos = strtol(*cpp, NULL, 0); + fil.fr_mip.fi_tos = 0xff; + cpp++; + } + + if (!strcasecmp(*cpp, "ttl")) { + if (!*++cpp) { + (void)fprintf(stderr, "ttl missing hopcount value\n"); + return NULL; + } + fil.fr_ttl = atoi(*cpp); + fil.fr_mip.fi_ttl = 0xff; + cpp++; + } + + /* + * check for "proto <protoname>" only decode udp/tcp/icmp as protoname + */ + proto = NULL; + if (!strcasecmp(*cpp, "proto")) { + if (!*++cpp) { + (void)fprintf(stderr, "protocol name missing\n"); + return NULL; + } + if (!strcasecmp(*cpp, "tcp/udp")) { + fil.fr_ip.fi_fl |= FI_TCPUDP; + fil.fr_mip.fi_fl |= FI_TCPUDP; + } else { + if (!(p = getprotobyname(*cpp)) && !isdigit(**cpp)) { + (void)fprintf(stderr, + "unknown protocol (%s)\n", *cpp); + return NULL; + } + if (p) + fil.fr_proto = p->p_proto; + else if (isdigit(**cpp)) + fil.fr_proto = atoi(*cpp); + fil.fr_mip.fi_p = 0xff; + } + proto = *cpp; + if (fil.fr_proto != IPPROTO_TCP && fil.fr_flags & FR_RETRST) { + (void)fprintf(stderr, + "%s can only be used with TCP\n", + "return-rst"); + return NULL; + } + if (!*++cpp) + return &fil; + } + if (fil.fr_proto != IPPROTO_TCP && fil.fr_flags & FR_RETRST) { + (void)fprintf(stderr, "%s can only be used with TCP\n", + "return-rst"); + return NULL; + } + + /* + * get the from host and bit mask to use against packets + */ + + if (!strcasecmp(*cpp, "all")) { + cpp++; + if (!*cpp) + return &fil; + } else { + if (strcasecmp(*cpp, "from")) { + (void)fprintf(stderr, + "unexpected keyword (%s) - from\n", *cpp); + return NULL; + } + if (!*++cpp) { + (void)fprintf(stderr, "missing host after from\n"); + return NULL; + } + ch = 0; + if (hostmask(&cpp, &fil.fr_src, &fil.fr_smsk, + &fil.fr_sport, &ch, &fil.fr_stop)) { + (void)fprintf(stderr, "bad host (%s)\n", *cpp); + return NULL; + } + fil.fr_scmp = ch; + if (!*cpp) { + (void)fprintf(stderr, "missing to fields\n"); + return NULL; + } + + /* + * do the same for the to field (destination host) + */ + if (strcasecmp(*cpp, "to")) { + (void)fprintf(stderr, + "unexpected keyword (%s) - to\n", *cpp); + return NULL; + } + if (!*++cpp) { + (void)fprintf(stderr, "missing host after to\n"); + return NULL; + } + ch = 0; + if (hostmask(&cpp, &fil.fr_dst, &fil.fr_dmsk, + &fil.fr_dport, &ch, &fil.fr_dtop)) { + (void)fprintf(stderr, "bad host (%s)\n", *cpp); + return NULL; + } + fil.fr_dcmp = ch; + } + + /* + * check some sanity, make sure we don't have icmp checks with tcp + * or udp or visa versa. + */ + if (fil.fr_proto && (fil.fr_dcmp || fil.fr_scmp) && + fil.fr_proto != IPPROTO_TCP && fil.fr_proto != IPPROTO_UDP) { + (void)fprintf(stderr, "port operation on non tcp/udp\n"); + return NULL; + } + if (fil.fr_icmp && fil.fr_proto != IPPROTO_ICMP) { + (void)fprintf(stderr, "icmp comparisons on wrong protocol\n"); + return NULL; + } + + if (!*cpp) + return &fil; + + if (*cpp && !strcasecmp(*cpp, "flags")) { + if (!*++cpp) { + (void)fprintf(stderr, "no flags present\n"); + return NULL; + } + fil.fr_tcpf = tcp_flags(*cpp, &fil.fr_tcpfm); + cpp++; + } + + /* + * extras... + */ + if (*cpp && (!strcasecmp(*cpp, "with") || !strcasecmp(*cpp, "and"))) + if (extras(&cpp, &fil)) + return NULL; + + /* + * icmp types for use with the icmp protocol + */ + if (*cpp && !strcasecmp(*cpp, "icmp-type")) { + if (fil.fr_proto != IPPROTO_ICMP) { + (void)fprintf(stderr, + "icmp with wrong protocol (%d)\n", + fil.fr_proto); + return NULL; + } + if (addicmp(&cpp, &fil)) + return NULL; + fil.fr_icmp = htons(fil.fr_icmp); + fil.fr_icmpm = htons(fil.fr_icmpm); + } + + /* + * leftovers...yuck + */ + if (*cpp && **cpp) { + fprintf(stderr, "unknown words at end: ["); + for (; *cpp; cpp++) + (void)fprintf(stderr, "%s ", *cpp); + (void)fprintf(stderr, "]\n"); + return NULL; + } + + /* + * lazy users... + */ + if (!fil.fr_proto && (fil.fr_dcmp || fil.fr_scmp || fil.fr_tcpf)) { + (void)fprintf(stderr, + "no protocol given for TCP/UDP comparisons\n"); + return NULL; + } + + return &fil; +} + +/* + * returns false if neither "hostmask/num" or "hostmask mask addr" are + * found in the line segments + */ +int hostmask(seg, sa, msk, pp, cp, tp) +char ***seg; +u_long *sa, *msk; +u_short *pp, *tp; +u_char *cp; +{ + char *s; + int bits = -1; + + /* + * is it possibly hostname/num ? + */ + if ((s = index(**seg, '/'))) { + *s++ = '\0'; + if (!isdigit(*s)) + return -1; + if (index(s, '.')) + *msk = inet_addr(s); + else { + /* + * set x most significant bits + */ + for (bits = atoi(s); bits; bits--) { + *msk /= 2; + *msk |= ntohl(inet_addr("128.0.0.0")); + } + *msk = htonl(*msk); + } + *sa = hostnum(**seg) & *msk; + (*seg)++; + return ports(seg, pp, cp, tp); + } + + /* + * look for extra segments if "mask" found in right spot + */ + if (*(*seg+1) && *(*seg+2) && !strcasecmp(*(*seg+1), "mask")) { + *sa = hostnum(**seg); + (*seg)++; + (*seg)++; + *msk = inet_addr(**seg); + (*seg)++; + *sa &= *msk; + return ports(seg, pp, cp, tp); + } + + if (**seg) { + *sa = hostnum(**seg); + (*seg)++; + *msk = (*sa ? inet_addr("255.255.255.255") : 0L); + *sa &= *msk; + return ports(seg, pp, cp, tp); + } + return -1; +} + +/* + * returns an ip address as a long var as a result of either a DNS lookup or + * straight inet_addr() call + */ +u_long hostnum(host) +char *host; +{ + struct hostent *hp; + struct netent *np; + + if (!strcasecmp("any",host)) + return 0L; + if (isdigit(*host)) + return inet_addr(host); + + if (!(hp = gethostbyname(host))) { + if (!(np = getnetbyname(host))) + return 0; + return np->n_net; + } + return *(u_long *)hp->h_addr; +} + +/* + * check for possible presence of the port fields in the line + */ +int ports(seg, pp, cp, tp) +char ***seg; +u_short *pp, *tp; +u_char *cp; +{ + int comp = -1; + + if (!*seg || !**seg || !***seg) + return 0; + if (!strcasecmp(**seg, "port") && *(*seg + 1) && *(seg + 2)) { + (*seg)++; + if (isdigit(***seg) && *(seg + 2)) { + *pp = portnum(**seg); + (*seg)++; + if (!strcmp(**seg, "<>")) + comp = FR_OUTRANGE; + else if (!strcmp(**seg, "><")) + comp = FR_INRANGE; + (*seg)++; + *tp = portnum(**seg); + } else if (!strcmp(**seg, "=") || !strcasecmp(**seg, "eq")) + comp = FR_EQUAL; + else if (!strcmp(**seg, "!=") || !strcasecmp(**seg, "ne")) + comp = FR_NEQUAL; + else if (!strcmp(**seg, "<") || !strcasecmp(**seg, "lt")) + comp = FR_LESST; + else if (!strcmp(**seg, ">") || !strcasecmp(**seg, "gt")) + comp = FR_GREATERT; + else if (!strcmp(**seg, "<=") || !strcasecmp(**seg, "le")) + comp = FR_LESSTE; + else if (!strcmp(**seg, ">=") || !strcasecmp(**seg, "ge")) + comp = FR_GREATERTE; + else { + (void)fprintf(stderr,"unknown comparator (%s)\n", + **seg); + return -1; + } + if (comp != FR_OUTRANGE && comp != FR_INRANGE) { + (*seg)++; + *pp = portnum(**seg); + } + *cp = comp; + (*seg)++; + } + return 0; +} + +/* + * find the port number given by the name, either from getservbyname() or + * straight atoi() + */ +u_short portnum(name) +char *name; +{ + struct servent *sp, *sp2; + u_short p1 = 0; + + if (isdigit(*name) || !proto) + return htons((u_short)atoi(name)); + if (strcasecmp(proto, "tcp/udp")) { + sp = getservbyname(name, proto); + if (sp) + return sp->s_port; + (void) fprintf(stderr, "unknown service \"%s\".\n", name); + return 0; + } + sp = getservbyname(name, "tcp"); + if (sp) + p1 = sp->s_port; + sp2 = getservbyname(name, "udp"); + if (!sp || !sp2) { + (void) fprintf(stderr, "unknown tcp/udp service \"%s\".\n", + name); + return 0; + } + if (p1 != sp2->s_port) { + (void) fprintf(stderr, "%s %d/tcp is a different port to ", + name, p1); + (void) fprintf(stderr, "%s %d/udp\n", name, sp->s_port); + return 0; + } + return p1; +} + + +u_char tcp_flags(flgs, mask) +char *flgs; +u_char *mask; +{ + u_char tcpf = 0, tcpfm = 0, *fp = &tcpf; + char *s, *t; + + for (s = flgs; *s; s++) { + if (*s == '/' && fp == &tcpf) { + fp = &tcpfm; + continue; + } + if (!(t = index(flagset, *s))) { + (void)fprintf(stderr, "unknown flag (%c)\n", *s); + return 0; + } + *fp |= flags[t - flagset]; + } + if (!tcpfm) + tcpfm = 0xff; + *mask = tcpfm; + return tcpf; +} + + +/* + * deal with extra bits on end of the line + */ +int extras(cp, fr) +char ***cp; +struct frentry *fr; +{ + u_short secmsk; + u_long opts; + int notopt; + char oflags; + + opts = 0; + secmsk = 0; + notopt = 0; + (*cp)++; + if (!**cp) + return -1; + + while (**cp && (!strncasecmp(**cp, "ipopt", 5) || + !strncasecmp(**cp, "not", 3) || !strncasecmp(**cp, "opt", 4) || + !strncasecmp(**cp, "frag", 3) || !strncasecmp(**cp, "no", 2) || + !strncasecmp(**cp, "short", 5))) { + if (***cp == 'n') + notopt = 1; + else if (***cp == 'i') + oflags = FI_OPTIONS; + else if (***cp == 'f') + oflags = FI_FRAG; + else if (***cp == 'o') { + if (!*(*cp + 1)) { + (void)fprintf(stderr, + "opt missing arguements\n"); + return -1; + } + (*cp)++; + if (!(opts = optname(cp, &secmsk))) + return -1; + oflags = FI_OPTIONS; + } else if (***cp == 's') { + if (fr->fr_tcpf) { + (void) fprintf(stderr, + "short cannot be used with TCP flags\n"); + return -1; + } + oflags = FI_SHORT; + } else + return -1; + + fr->fr_mip.fi_fl |= oflags; + fr->fr_mip.fi_optmsk |= opts; + fr->fr_mip.fi_secmsk |= secmsk; + + if (notopt) { + fr->fr_ip.fi_fl &= (~oflags & 0xf); + fr->fr_ip.fi_optmsk &= ~opts; + fr->fr_ip.fi_secmsk &= ~secmsk; + } else { + fr->fr_ip.fi_fl |= oflags; + fr->fr_ip.fi_optmsk |= opts; + fr->fr_ip.fi_secmsk |= secmsk; + } + opts = 0; + oflags = 0; + secmsk = 0; + (*cp)++; + } + return 0; +} + + +u_long optname(cp, sp) +char ***cp; +u_short *sp; +{ + struct ipopt_names *io, *so; + u_long msk = 0; + u_short smsk = 0; + char *s; + int sec = 0; + + for (s = strtok(**cp, ","); s; s = strtok(NULL, ",")) { + for (io = ionames; io->on_name; io++) + if (!strcasecmp(s, io->on_name)) { + msk |= io->on_bit; + break; + } + if (!io->on_name) { + fprintf(stderr, "unknown IP option name %s\n", s); + return 0; + } + if (!strcasecmp(s, "sec-class")) + sec = 1; + } + + if (sec && !*(*cp + 1)) { + fprintf(stderr, "missing security level after sec-class\n"); + return 0; + } + + if (sec) { + (*cp)++; + for (s = strtok(**cp, ","); s; s = strtok(NULL, ",")) { + for (so = secclass; so->on_name; so++) + if (!strcasecmp(s, so->on_name)) { + smsk |= so->on_bit; + break; + } + if (!so->on_name) { + fprintf(stderr, "no such security level: %s\n", + s); + return 0; + } + } + if (smsk) + *sp = smsk; + } + return msk; +} + + +void optprint(optmsk, secmsk) +u_char optmsk, secmsk; +{ + struct ipopt_names *io, *so; + char *s = "", *t = ""; + + printf("opt "); + for (io = ionames; io->on_name; io++) + if (io->on_bit & optmsk) { + printf("%s%s", s, io->on_name); + s = ","; + } + if (secmsk) { + putchar(' '); + for (so = secclass; so->on_name; so++) + if (secmsk & so->on_bit) { + printf("%s%s", t, so->on_name); + t = ","; + } + } +} + +char *icmptypes[] = { + "echorep", (char *)NULL, (char *)NULL, "unreach", "squench", + "redir", (char *)NULL, (char *)NULL, "echo", (char *)NULL, + (char *)NULL, "timex", "paramprob", "timest", "timestrep", + "inforeq", "inforep", "maskreq", "maskrep", "END" +}; + +/* + * set the icmp field to the correct type if "icmp" word is found + */ +int addicmp(cp, fp) +char ***cp; +struct frentry *fp; +{ + char **t; + int i; + + (*cp)++; + if (!**cp) + return -1; + if (!fp->fr_proto) /* to catch lusers */ + fp->fr_proto = IPPROTO_ICMP; + if (isdigit(***cp)) { + i = atoi(**cp); + (*cp)++; + } else { + for (t = icmptypes, i = 0; ; t++, i++) { + if (!*t) + continue; + if (!strcasecmp("END", *t)) { + i = -1; + break; + } + if (!strcasecmp(*t, **cp)) + break; + } + if (i == -1) + return -1; + } + fp->fr_icmp = (u_short)(i << 8); + fp->fr_icmpm = (u_short)0xff00; + (*cp)++; + if (!**cp) + return 0; + + if (**cp && strcasecmp("code", **cp)) + return 0; + (*cp)++; + if (isdigit(***cp)) { + i = atoi(**cp); + fp->fr_icmp |= (u_short)i; + fp->fr_icmpm = (u_short)0xffff; + (*cp)++; + return 0; + } + return -1; +} + + +/* + * count consecutive 1's in bit mask. If the mask generated by counting + * consecutive 1's is different to that passed, return -1, else return # + * of bits. + */ +int countbits(ip) +u_long ip; +{ + u_long ipn; + int cnt = 0, i, j; + + ip = ipn = ntohl(ip); + for (i = 32; i; i--, ipn *= 2) + if (ipn & 0x80000000) + cnt++; + else + break; + ipn = 0; + for (i = 32, j = cnt; i; i--, j--) { + ipn *= 2; + if (j > 0) + ipn++; + } + if (ipn == ip) + return cnt; + return -1; +} + + +char *portname(pr, port) +int pr, port; +{ + static char buf[32]; + struct protoent *p = NULL; + struct servent *sv = NULL, *sv1 = NULL; + + if (pr == -1) { + if ((sv = getservbyport(htons(port), "tcp"))) { + strncpy(buf, sv->s_name, sizeof(buf)-1); + buf[sizeof(buf)-1] = '\0'; + sv1 = getservbyport(htons(port), "udp"); + if (sv1 && !strcasecmp(buf, sv->s_name)) + return buf; + } + } else if (pr && (p = getprotobynumber(pr))) { + if ((sv = getservbyport(htons(port), p->p_name))) { + strncpy(buf, sv->s_name, sizeof(buf)-1); + buf[sizeof(buf)-1] = '\0'; + return buf; + } + } + + (void) sprintf(buf, "%d", port); + return buf; +} + + +/* + * print the filter structure in a useful way + */ +void printfr(fp) +struct frentry *fp; +{ + static char *pcmp1[] = { "*", "=", "!=", "<", ">", "<=", ">=", + "<>", "><"}; + struct protoent *p; + int ones = 0, pr; + char *s; + u_char *t; + + if (fp->fr_flags & FR_PASS) + (void)printf("pass"); + else if (fp->fr_flags & FR_LOG) { + (void)printf("log"); + if (fp->fr_flags & FR_LOGBODY) + (void)printf(" body"); + } else if (fp->fr_flags & FR_BLOCK) { + (void)printf("block"); + if (fp->fr_flags & FR_RETICMP) + (void)printf(" return-icmp"); + if (fp->fr_flags & FR_RETRST) + (void)printf(" return-rst"); + } + + if (fp->fr_flags & FR_OUTQUE) + (void)printf(" out "); + else + (void)printf(" in "); + + if (fp->fr_flags & (FR_LOGB|FR_LOGP)) { + (void)printf("log "); + if (fp->fr_flags & FR_LOGBODY) + (void)printf(" body"); + } + if (fp->fr_flags & FR_QUICK) + (void)printf("quick "); + + if (*fp->fr_ifname) + (void)printf("on %s%s ", fp->fr_ifname, + (fp->fr_ifa || (int)fp->fr_ifa == -1) ? "" : "(!)"); + if (fp->fr_mip.fi_tos) + (void)printf("tos %#x ", fp->fr_tos); + if (fp->fr_mip.fi_ttl) + (void)printf("ttl %d ", fp->fr_ttl); + if (fp->fr_ip.fi_fl & FI_TCPUDP) { + (void)printf("proto tcp/udp "); + pr = -1; + } else if ((pr = fp->fr_proto)) { + if ((p = getprotobynumber(fp->fr_proto))) + (void)printf("proto %s ", p->p_name); + else + (void)printf("proto %d ", fp->fr_proto); + } + + if (!fp->fr_src.s_addr & !fp->fr_smsk.s_addr) + (void)printf("from any "); + else { + (void)printf("from %s", inet_ntoa(fp->fr_src)); + if ((ones = countbits(fp->fr_smsk.s_addr)) == -1) + (void)printf("/%s ", inet_ntoa(fp->fr_smsk)); + else + (void)printf("/%d ", ones); + } + if (fp->fr_sport) + if (fp->fr_scmp == FR_INRANGE || fp->fr_scmp == FR_OUTRANGE) + (void)printf("port %d %s %d ", ntohs(fp->fr_sport), + pcmp1[fp->fr_scmp], ntohs(fp->fr_stop)); + else + (void)printf("port %s %s ", pcmp1[fp->fr_scmp], + portname(pr, ntohs(fp->fr_sport))); + if (!fp->fr_dst.s_addr & !fp->fr_dmsk.s_addr) + (void)printf("to any "); + else { + (void)printf("to %s", inet_ntoa(fp->fr_dst)); + if ((ones = countbits(fp->fr_dmsk.s_addr)) == -1) + (void)printf("/%s ", inet_ntoa(fp->fr_dmsk)); + else + (void)printf("/%d ", ones); + } + if (fp->fr_dport) { + if (fp->fr_dcmp == FR_INRANGE || fp->fr_dcmp == FR_OUTRANGE) + (void)printf("port %d %s %d ", ntohs(fp->fr_dport), + pcmp1[fp->fr_dcmp], ntohs(fp->fr_dtop)); + else + (void)printf("port %s %s ", pcmp1[fp->fr_dcmp], + portname(pr, ntohs(fp->fr_dport))); + } + if (fp->fr_mip.fi_fl & (FI_SHORT|FI_OPTIONS|FI_FRAG)) { + (void)printf("with "); + if (fp->fr_mip.fi_fl & FI_OPTIONS) { + if (fp->fr_ip.fi_optmsk) + optprint(fp->fr_ip.fi_optmsk, + fp->fr_ip.fi_secmsk); + else { + if (!(fp->fr_ip.fi_fl & FI_OPTIONS)) + (void)printf("not "); + (void)printf("ipopt "); + } + } + if (fp->fr_mip.fi_fl & FI_SHORT) { + if (!(fp->fr_ip.fi_fl & FI_SHORT)) + (void)printf("not "); + (void)printf("short "); + } + if (fp->fr_mip.fi_fl & FI_FRAG) { + if (!(fp->fr_ip.fi_fl & FI_FRAG)) + (void)printf("not "); + (void)printf("frag "); + } + } + if (fp->fr_proto == IPPROTO_ICMP && fp->fr_icmpm) { + int type = fp->fr_icmp, code; + + type = ntohs(fp->fr_icmp); + code = type & 0xff; + type /= 256; + if (type < (sizeof(icmptypes) / sizeof(char *)) && + icmptypes[type]) + (void)printf("icmp-type %s ", icmptypes[type]); + else + (void)printf("icmp-type %d ", type); + if (code) + (void)printf("code %d ", code); + } + if (fp->fr_proto == IPPROTO_TCP && fp->fr_tcpf) { + (void)printf("flags "); + for (s = flagset, t = flags; *s; s++, t++) + if (fp->fr_tcpf & *t) + (void)putchar(*s); + if (fp->fr_tcpfm) { + (void)putchar('/'); + for (s = flagset, t = flags; *s; s++, t++) + if (fp->fr_tcpfm & *t) + (void)putchar(*s); + } + } + (void)putchar('\n'); +} + +void binprint(fp) +struct frentry *fp; +{ + int i = sizeof(*fp), j = 0; + u_char *s; + + for (s = (u_char *)fp; i; i--, s++) { + j++; + (void)printf("%02x ",*s); + if (j == 16) { + (void)printf("\n"); + j = 0; + } + } + putchar('\n'); + (void)fflush(stdout); +} diff --git a/sbin/ipfstat/Makefile b/sbin/ipfstat/Makefile new file mode 100644 index 00000000000..bafd458b225 --- /dev/null +++ b/sbin/ipfstat/Makefile @@ -0,0 +1,9 @@ +PROG= ipfstat +MAN= ipfstat.8 +SRCS= fils.c parse.c opt.c kmem.c +.PATH: ${.CURDIR}/../../sbin/ipf +CFLAGS+=-DIPL_NAME=\"/dev/ipl\" -I${.CURDIR}/../../sbin/ipf + + + +.include <bsd.prog.mk> diff --git a/sbin/ipfstat/fils.c b/sbin/ipfstat/fils.c new file mode 100644 index 00000000000..7f440f6da39 --- /dev/null +++ b/sbin/ipfstat/fils.c @@ -0,0 +1,212 @@ +/* + * (C)opyright 1993,1994,1995 by Darren Reed. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and due credit is given + * to the original author and the contributors. + */ +#include <stdio.h> +#include <string.h> +#if !defined(__SVR4) && !defined(__svr4__) +#include <strings.h> +#endif +#include <sys/types.h> +#include <sys/param.h> +#include <sys/file.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <stddef.h> +#include <nlist.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <net/if.h> +#include <netinet/ip_fil.h> +#include <netdb.h> +#include <arpa/nameser.h> +#include <resolv.h> +#include "ipf.h" +#include "kmem.h" +#ifdef __NetBSD__ +#include <paths.h> +#endif + +#ifndef lint +static char sccsid[] = "@(#)fils.c 1.15 11/11/95 (C) 1993 Darren Reed"; +#endif +#ifdef _PATH_UNIX +#define VMUNIX _PATH_UNIX +#else +#define VMUNIX "/vmunix" +#endif + +extern char *optarg; +#define F_ST 0 +#define F_IN 1 +#define F_OUT 2 +#define F_FL 3 + +int opts = 0; + +static void showstats(); +static void showlist(); + +int main(argc,argv) +int argc; +char *argv[]; +{ + struct friostat fio; + char c, *name = NULL, *device = IPL_NAME; + int fd; + + if (openkmem() == -1) + exit(-1); + + (void)setuid(getuid()); + (void)setgid(getgid()); + + while ((c = getopt(argc, argv, "hIiovd:")) != -1) + { + switch (c) + { + case 'd' : + device = optarg; + break; + case 'h' : + opts |= OPT_HITS; + break; + case 'i' : + opts |= OPT_INQUE|OPT_SHOWLIST; + break; + case 'I' : + opts |= OPT_INACTIVE; + break; + case 'o' : + opts |= OPT_OUTQUE|OPT_SHOWLIST; + break; + case 'v' : + opts |= OPT_VERBOSE; + break; + } + } + + if ((fd = open(device, O_RDONLY)) < 0) { + perror("open"); + exit(-1); + } + bzero((char *)&fio, sizeof(fio)); + if (ioctl(fd, SIOCGETFS, &fio) == -1) { + perror("ioctl(SIOCGETFS)"); + exit(-1); + } + + if (opts & OPT_VERBOSE) + printf("opts %#x name %s\n", opts, name ? name : "<>"); + if (opts & OPT_SHOWLIST){ + showlist(&fio); + if((opts & OPT_OUTQUE) && (opts & OPT_INQUE)){ + opts &= ~OPT_OUTQUE; + showlist(&fio); + } + } + else + showstats(fd, &fio); + return 0; +} + + +/* + * read the kernel stats for packets blocked and passed + */ +static void showstats(fd, fp) +int fd; +struct friostat *fp; +{ + int frf = 0; + + if (ioctl(fd, SIOCGETFF, &frf) == -1) + perror("ioctl(SIOCGETFF)"); + +#if SOLARIS + (void)printf("dropped packets:\tin %ld\tout %ld\n", + fp->f_st[0].fr_drop, fp->f_st[1].fr_drop); + (void)printf("non-ip packets:\t\tin %ld\tout %ld\n", + fp->f_st[0].fr_notip, fp->f_st[1].fr_notip); + (void)printf(" bad packets:\t\tin %ld\tout %ld\n", + fp->f_st[0].fr_bad, fp->f_st[1].fr_bad); +#endif + (void)printf(" input packets:\t\tblocked %ld passed %ld nomatch %ld\n", + fp->f_st[0].fr_block, fp->f_st[0].fr_pass, + fp->f_st[0].fr_nom); + (void)printf("output packets:\t\tblocked %ld passed %ld nomatch %ld\n", + fp->f_st[1].fr_block, fp->f_st[1].fr_pass, + fp->f_st[1].fr_nom); + (void)printf(" input packets logged:\tblocked %ld passed %ld\n", + fp->f_st[0].fr_bpkl, fp->f_st[0].fr_ppkl); + (void)printf("output packets logged:\tblocked %ld passed %ld\n", + fp->f_st[1].fr_bpkl, fp->f_st[1].fr_ppkl); + (void)printf(" packets logged:\tinput %ld-%ld output %ld-%ld\n", + fp->f_st[0].fr_pkl, fp->f_st[0].fr_skip, + fp->f_st[1].fr_pkl, fp->f_st[1].fr_skip); + (void)printf("ICMP replies:\t%ld\tTCP RSTs sent:\t%ld\n", + fp->f_st[0].fr_ret, fp->f_st[1].fr_ret); + + (void)printf("Packet log flags set: (%#x)\n", frf); + if (frf & FF_LOGPASS) + printf("\tpackets passed through filter\n"); + if (frf & FF_LOGBLOCK) + printf("\tpackets blocked by filter\n"); + if (!frf) + printf("\tnone\n"); +} + +/* + * print out filter rule list + */ +static void showlist(fiop) +struct friostat *fiop; +{ + struct frentry fb; + struct frentry *fp = NULL; + int i, set; + + if (opts & OPT_OUTQUE) + i = F_OUT; + else if (opts & OPT_INQUE) + i = F_IN; + else + return; + set = fiop->f_active; + if (opts & OPT_INACTIVE) + set = 1 - set; + fp = (i == F_IN) ? (struct frentry *)fiop->f_fin[set] : + (struct frentry *)fiop->f_fout[set]; + if (opts & OPT_VERBOSE) + (void)fprintf(stderr, "showlist:opts %#x i %d\n", opts, i); + + if (opts & OPT_VERBOSE) + printf("fp %#x set %d\n", (u_int)fp, set); + if (!fp) { + (void)fprintf(stderr, "empty list for filter%s\n", + (i == F_IN) ? "in" : "out"); + return; + } + while (fp) { + if (kmemcpy((char *)&fb, (u_long)fp, sizeof(fb)) == -1) { + perror("kmemcpy"); + return; + } + fp = &fb; + if (opts & OPT_OUTQUE) + fp->fr_flags |= FR_OUTQUE; + if (opts & (OPT_HITS|OPT_VERBOSE)) + printf("%d ", fp->fr_hits); + printfr(fp); + if (opts & OPT_VERBOSE) + binprint(fp); + fp = fp->fr_next; + } +} diff --git a/sbin/ipfstat/ipfstat.8 b/sbin/ipfstat/ipfstat.8 new file mode 100644 index 00000000000..bfc8b2ae262 --- /dev/null +++ b/sbin/ipfstat/ipfstat.8 @@ -0,0 +1,48 @@ +.LP +.TH ipfstat 8 +.SH NAME +ipfstat - reports on packet filter statistics and filter list +.SH SYNOPSIS +ipfstat [-hIiovd:] +.SH DESCRIPTION +.LP +.PP +\fBipfstat examines /dev/kmem using the symbols \fB_fr_flags\fP, +\fB_frstats\fP, \fB_filterin\fP, and \fB_filterout\fP. +To run and work, it needs to be able to read both /dev/kmem and the +kernel itself. The kernel name defaults to \fB/vmunix\fP. +.PP +The default behaviour of \fBipfstat\fP +is to retrieve and display the accumulated statistics which have been +accumulated over time as the kernel has put packets through the filter. +.SH OPTIONS +.IP -o +display the filter list used for the output side of the kernel IP processing. +.IP -i +display the filter list used for the input side of the kernel IP processing. +.IP -v +turn verbose mode on. Displays more debugging information. +.IP -d <device> +use a device other than \fB/dev/ipl\fP for interfacing with the kernel. +.IP -I +swap between retrieving "inactive"/"active" filter list details. For use +in combination with \fB-i\fP. +.IP -h +show per-rule the number of times each one scores a "hit". For use in +combination with \fB-i\fP. +.SH SYNOPSIS +The role of \fBipfstat\fP is to display current kernel statistics gathered +as a result of applying the filters in place (if any) to packets going in and +out of the kernel. This is the default operation when no command line +parameters are present. +.PP +When supplied with either \fB-i\fP or \fB-o\fP, it will retrieve and display +the appropriate list of filter rules currently installed and in use by the +kernel. +.SH FILES +/dev/kmem +/vmunix +.SH SEE ALSO +ipf(1), ipfstat(1) +.SH BUGS +none known. diff --git a/sbin/ipfstat/kmem.c b/sbin/ipfstat/kmem.c new file mode 100644 index 00000000000..4c0dbe3b520 --- /dev/null +++ b/sbin/ipfstat/kmem.c @@ -0,0 +1,66 @@ +/* + * (C)opyright 1993,1994,1995 by Darren Reed. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and due credit is given + * to the original author and the contributors. + */ +/* + * kmemcpy() - copies n bytes from kernel memory into user buffer. + * returns 0 on success, -1 on error. + */ + +#include <sys/types.h> +#include <sys/uio.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/file.h> + +#define KMEM "/dev/kmem" + +#ifndef lint +static char sccsid[] = "@(#)kmem.c 1.3 10/15/95 (C) 1992 Darren Reed"; +#endif + +static int kmemfd = -1; + +int openkmem() +{ + if ((kmemfd = open(KMEM,O_RDONLY)) == -1) + { + perror("kmeminit:open"); + return -1; + } + return kmemfd; +} + +int kmemcpy(buf, pos, n) +register char *buf; +long pos; +register int n; +{ + register int r; + + if (!n) + return 0; + if (kmemfd == -1) + if (openkmem() == -1) + return -1; + if (lseek(kmemfd, pos, 0) == -1) + { + perror("kmemcpy:lseek"); + return -1; + } + while ((r = read(kmemfd, buf, n)) < n) + if (r <= 0) + { + perror("kmemcpy:read"); + return -1; + } + else + { + buf += r; + n -= r; + } + return 0; +} diff --git a/sbin/ipfstat/kmem.h b/sbin/ipfstat/kmem.h new file mode 100644 index 00000000000..dd6af9db495 --- /dev/null +++ b/sbin/ipfstat/kmem.h @@ -0,0 +1,11 @@ +/* + * (C)opyright 1993,1994,1995 by Darren Reed. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and due credit is given + * to the original author and the contributors. + */ + +extern int openkmem(); +extern int kmemcpy(); + |