.TH FW 8 .SH NAME fw \- firewall .SH SYNOPSIS .B fw .RB [ -dSW ] .RB [ -m .IR ctlmtpt ] .RB [ -n .IR net ] .RB [ -s .IR srv ] .br .RB [ -e .I ether .RB [ -a .IR addr / mask ] .RB [ -g .IR gateway ]] .br .I rules .RI [ outside .IR inside ] .SH DESCRIPTION .I Fw is a network firewall. It can filter a network card, the traffic crossing between two networks, or the traffic of a single namespace. Rules are written in an .IR ndb (6) file; see .B RULES below. .PP The three ways of running it follow. The first two filter .IR packets , as they cross a wire; the third filters .IR requests , before any packet exists. .SS Filtering a network card .IP .EX fw -e /net/ether0 -a 10.0.2.15/24 -g 10.0.2.2 /lib/fw/host.ndb .EE .PP Everything sent or received through that card is filtered, whoever sent it. One card to one .IR fw , so a machine with two cards to filter runs two of them. .PP The card cannot stay attached to the IP stack, or packets would reach it whatever the rules said. So .I fw takes the card and hands the stack a .I pkt interface instead - a card with .I fw on the other end of it: .IP .EX network ---- ether0 [ fw ] stack ---- your programs .EE .PP The stack keeps the address it had, given as .BI -a " addr/mask"\f1, and .B -g names the gateway. Nothing is left doing ARP for an address whose card has been taken away, so .I fw answers for it and resolves the next hop itself. .SS Gateway, between two networks .IP .EX fw /lib/fw/gate.ndb /net.wan!203.0.113.2!/24 /net.lan!10.0.0.1!/24 .EE .PP .I Fw sits between two networks and decides, packet by packet, what may cross: .IP .EX internet ---- ether0 [ fw ] ether1 ---- your machines outside inside .EE .PP The side facing the untrusted network is the .IR outside , the side facing the machines you are protecting is the .IR inside . Rules are written in those terms, so which side is which matters. .PP Every packet between the two networks goes through .IR fw , so one the rules forbid is thrown away before the far side ever sees it. Nothing is opened, nothing replies, and the sender is told nothing. A side is written .IB net ! addr ! mask\f1, naming the mountpoint of an IP stack and the address to give the interface .I fw creates on it. .SS Filtering one namespace .IP .EX fw.rc /lib/fw/web.ndb mothra .EE .PP Given neither of those, .I fw serves a synthetic .B /net in place of .IR ip (3), and every program in that namespace reaches the network through it. A write of .B connect or .B announce to a protocol ctl file is matched against the rules before it reaches the kernel. A refused write fails, and the diagnostic is what .IR dial (2) reports to the program that tried it. .PP The .B /net it serves is named rather than filtered: protocol directories and their conversations, plus .BR cs , .BR dns , .BR arp , .BR bootp , .BR iproute , .B ipselftab and .BR ndb , of which only .B cs and .B dns may be written. Nothing else is there at all - not a card, not .BR ipmux , not .BR log , not a protocol's .B trans file, and not an interface's .B data or .B snoop file, each of which is a wire. A protocol .I fw has not been told about is one nobody in the namespace can reach, which is the safe way for this list to be out of date. .PP Because the policy lives in a namespace, each program can have its own. Filtering a program's requests is not a boundary by itself - see .B CONTAINMENT below - so to sandbox one, run .B /lib/fw/fw.rc rather than .I fw directly: it builds the namespace, mounts the filter, drops the devices that would go around it, and only then runs the program. .PP The difference is one of altitude. Packet filtering sees everything, including traffic no local program asked for, but cannot tell one program from another. Request filtering knows exactly who asked, but only ever sees intentions, so it cannot stop an inbound connection before the handshake and cannot see traffic that is merely passing through. .PP The options are: .TP .B -W require wire (packet) filtering; exit rather than fall back to filtering requests. .TP .B -S do not track connections. Every packet is then matched against the rules, and a rule permitting traffic one way does not permit the replies. .TP .B -d report every packet and every decision on standard error. .TP .BI -m " ctlmtpt" mount the control files here, default .BR /mnt/fw . Packet filtering only. .TP .BI -e " ether" take this card and filter the packets of this machine, eg .BR /net/ether0 . .TP .BI -a " addr/mask" the address the stack should keep once its card has been taken. Without it .I fw reads the address and mask off the card it is taking, which is almost always what you want; give it only to use something other than the address the card already has. .TP .BI -g " gateway" the next hop for anything off this network. Without it .I fw takes the default route the stack was already using; if there was none, only the local network is reachable. .TP .BI -n " net" filter this stack rather than .BR /net . .TP .BI -s " srv" post the served filesystem on .BI /srv/ srv as well as mounting it. Which filesystem that is depends on the mode, and so does the risk. Filtering packets it is the control files, and anyone who can open them can rewrite the rules; filtering requests it is the synthetic .BR /net , and anyone who can mount it has the network the rules were meant to ration. Either way, do not post it anywhere the traffic being filtered can reach .BR /srv . .SH RULES Rules live in an .IR ndb (6) file, matched from the top, first match deciding, and traffic matching nothing denied. Connections are tracked, so permitting traffic one way permits the replies. The file format, the attributes, and worked rule sets are in .IR fwrules (6). .SH CONTROL When filtering packets, .I fw serves four files, by default under .BR /mnt/fw . There are none when filtering requests: that policy is chosen when the namespace is built and lasts as long as it does, so to change it, build the namespace again. .TP .B ctl Read it for the commands it takes. Writing a rule installs it at once. A rule that will not parse fails the write and leaves the running rules alone. .TP .B rules The current rules, as ndb, one rule per line and numbered from one in the order shown - so rule .I n in a diagnostic is line .I n here, which is what .B delete counts. What it prints can be written back unchanged. .IP Writing it replaces the whole set. Writes accumulate and are applied when the file is closed, so a set arriving in several messages is still installed in one step and no packet is matched against half of it. A clunk cannot report an error, so a set that will not parse is noted in .B /sys/log/fw and the running rules are kept; .B ctl is the way in if you want to be told. .TP .B flows The connections being tracked. .TP .B stats Packets passed and dropped, then each rule with the number of decisions it has made. A rule at zero is either dead or waiting for something that has not happened, and it is worth knowing which. .PP The commands accepted by .B ctl are .BR prepend , .BR append , .B delete .IR n , .BR flush , .B reload .RI [ file ], .B save .RI [ file ], and .BR flushflows . .B Reload and .B save with no argument use the file .I fw was started with. .PP There are three things called the rules: the file, what is running, and what has been typed at .BR ctl . .B Reload makes the running rules match the file, discarding anything typed; .B save makes the file match the running rules, keeping it. Neither happens on its own. .SH CONTAINMENT Filtering .B /net achieves nothing on its own, because .IP .EX bind -a '#I' /net .EE .PP puts the real stack back. A filtered program must also be denied the devices that reach a network directly, through .B /dev/drivers (see .IR cons (3)): .IP .EX echo chdev '&~' 'Iluσ' >/dev/drivers .EE .PP That mask can only ever have bits added, and every new process group inherits it, so nothing below can undo it. Note that the .B & form permits .I only the devices named, so using it to try to regain one silently drops everything else instead. .PP .B /lib/fw/fw.rc does this in the right order: private namespace, mount, drop, exec. .SH BOOT .B /lib/fw/fwstart starts a firewall for each card named in .BR /lib/ndb/fw , which is an .IR ndb (6) file of one entry per card: .IP .EX fw=ether0 rules=/lib/fw/wan.ndb fw=ether1 rules=/lib/fw/lan.ndb .EE .PP No address appears there. .I Fw reads each card's address, mask and gateway from the card it is taking over, so this file cannot drift out of step with .IR ipconfig (8). Control files land under .BI /mnt/fw/ ether0 and so on, one directory per card. .PP Run it after the network is configured and before anything dials. Too early and there is no address to read; too late and something has already connected unfiltered. A card named here that does not exist, or a rule file that is missing, is reported and skipped rather than stopping the rest. .SH EXAMPLES Filter this machine's card, keeping the address it already has: .IP .EX fw -e /net/ether0 -a 10.0.2.15/24 -g 10.0.2.2 /lib/fw/host.ndb .EE .PP Filter two cards, one .I fw each: .IP .EX fw -m /mnt/fw/ether0 -e /net/ether0 -a 198.51.100.7/24 -g 198.51.100.1 \ /lib/fw/wan.ndb fw -m /mnt/fw/ether1 -e /net/ether1 -a 10.0.0.1/24 /lib/fw/lan.ndb .EE .PP Sit between two networks: .IP .EX fw /lib/fw/gate.ndb /net.wan!203.0.113.2!/24 /net.lan!10.0.0.1!/24 .EE .PP Sandbox one program, which may resolve names and speak https: .IP .EX fw.rc /lib/fw/web.ndb mothra .EE .PP Give a program no network whatever. An empty rule file permits nothing: .IP .EX fw.rc /lib/fw/none.ndb troff -ms doc .EE .PP Block a port on a running firewall, at once, including anything already connected: .IP .EX echo prepend deny=in proto=tcp lport=80 >/mnt/fw/ctl .EE .PP Edit the rule file and apply it, or keep what was typed at .BR ctl : .IP .EX echo reload >/mnt/fw/ctl echo save >/mnt/fw/ctl .EE .PP See what a rule has actually done: .IP .EX cat /mnt/fw/stats .EE .SH FILES .TP .B /lib/fw rule sets .TP .B /lib/fw/fwstart starts one firewall per card at boot .TP .B /lib/ndb/fw which cards are filtered, and with what .TP .B /mnt/fw control files .TP .B /sys/log/fw where rules marked .B log=yes are recorded .SH SOURCE .B /sys/src/cmd/fw .SH "SEE ALSO" .IR ip (3), .IR cons (3), .IR fwrules (6), .IR ndb (6), .IR dial (2), .IR syslog (2), .IR fork (2), .IR ipconfig (8) .SH BUGS There is no address translation. A machine behind a .I fw gateway needs a routable address, or must import .B /net rather than route through it. .PP Packet filtering drops silently, so a refused connection is discovered by timing out. Request filtering fails .IR dial (2) immediately with a reason, which is much easier to diagnose. .PP IPv6 extension headers are not walked; such packets are matched on their addresses and next-header protocol alone. ICMP has no type or code matching. .PP A fragmented datagram does not cross. Only the first fragment carries the transport header, so the rest match no port, and a rule set written in ports - which is every rule set worth writing - denies them. The first fragment arrives and the receiver waits for the others until it gives up. Reading ports out of a later fragment, which is what .I fw did before, is worse: a fragment whose payload bytes happen to match an open connection is then let through. .PP Taking a card is destructive and is not undone. The interface that replaces it is unbound when .I fw stops, and the address goes with it, so a .I fw that is killed leaves the card bound to nothing and the machine with no network. It cannot be restarted unaided either: the address it would have read off the card is the address that has just been lost, so it must be told one with .BR -a , or the card configured again with .IR ipconfig (8) first. Nothing puts the card back. Whatever restarts .I fw must configure the card first, with the address it had, which .I fw no longer knows. .PP The first packet to an unresolved next hop is dropped while .I fw asks for its ethernet address, exactly as any other stack would, so a run of .I fw begins with one drop that no rule caused. .PP .I Fw does not implement .BR Tflush , so a request it is blocked on cannot be abandoned. While filtering requests this affects waiting for an inbound connection, which is the one operation that blocks indefinitely: giving up on it does not reach .IR fw , which stays waiting until a connection arrives.