# Implementing a firewall

The first thing you'll be receiving on stdinput will be the ruleset for your firewall (until you encounter a double newline).

We'll start explaining the ruleset itself with some sample input:

/* header abbreviations added for demonstration purposes only */

/> Allow (A) or Block (B)
|
| |
| |               /> Destination (incl. mask)
| |              |
| |              |          /> Source port(s)
| |              |         |
| |              |         |           /> Destination port(s)
| |              |         |          |

A 192.168.1.0/24 0.0.0.0/0 1024:65536 80
A 0.0.0.0/0 192.168.1.0/24 80 1024:65536
A 192.168.1.1/32 0.0.0.0/0 80 1024:65536
A 0.0.0.0/0 192.168.1.1/32 1024:65536 80
B 0.0.0.0/0 0.0.0.0/0 * *

• Rules are processed in cascading order. Once you find a matching rule, determine the outcome and stop processing.
• If a packet does not match any given rule, block it (sane defaults).
• Elements of a rule are single-space delimited.
• Source and destination adresses will provide a netmask. Be sure to filter accordingly.
• The specification of the source and destinations ports must support the following syntax:
1. Single ports: e.g. "80"
2. Inclusive port ranges: e.g. "1024:65536"
3. All ports: e.g. "*"
• You may assume input to be well-formed.

After the double newline, you'll receive (one or more) newline seperated input packets to test according to the ruleset you've now acquired.

Some sample input for the packets to clarify:

/* header abbreviations added for demonstration purposes only */

/> Source
|
|             /> Source port
|            |
|            |     /> Destination
|            |    |
|            |    |              /> Destination port
|            |    |             |

192.168.1.18 1036 157.136.122.5 22
157.136.108.8 2400 192.168.1.1 80


You'll examine these input packets and determine the outcome of filtering according to the rules.

The above sample input (ruleset + packets) should thus yield the following output:

BLOCK 192.168.1.18 1036 157.136.122.5 22
ALLOW 157.136.108.8 2400 192.168.1.1 80


The output is basically no more than 'BLOCK' or 'ALLOW' followed by a space and the verbatim input line.

Some varia:

• This is code golf, the shortest (source code, by byte count) solution wins. Have fun!
• You are not allowed to use specific IP address handling features of your language.
• Specialized languages for routing or firewalling will be excluded from contending for first place (if they exist).
• If any part of the question is ambiguous or in need of further clarification, feel free to comment.

main=interact$f.break null.lines;f(r,_:p)=unlines$map(foldr(g.w)(j"B"++)r)p
g[r,s,d,a,b]n x|and$zipWith3($)(cycle[k.('/'#),q.(':'#)])[s,a,d,b]$w x=j r++x|t=n x k[y,z]x=y%z==x%z;y%z=foldl((.r).(+).(*256))0('.'#y)div2^(32-r z) q["*"]x=t;q[y]x=x==y;q[a,b]x=r x>=r a&&r x<=r b d#x=w$map(d?)x;d?x|d==x=' '|t=x;j"A"="ALLOW ";j"B"="BLOCK ";t=1<3;r=read;w=words


# Perl 561

First try. Any tips?

[gary@phoenix ~]$cat test | perl f2.pl BLOCK 192.168.1.18 1036 157.136.122.5 22 ALLOW 157.136.108.8 2400 192.168.1.1 80 sub r{($i,$j)=split'/',shift;$j eq 0?'*':map$h.=sprintf("%.8b",$_),split/\./,$i; substr$h,0,$j}sub s{$_[0]=~/(\d+):(\d+)/?$1<$_[1]&& $_[1]<$2:$_[0]==$_[1]}sub
t{chomp;($a,$b,$c,$d,$e)=split/ /}sub o{$_[0]eq'*'or$_[0] eq substr&r($_[1].'/32'),0,length$_[0]}sub p{$_[0]eq'*'or&s($_[0],$_[1])}
map((/^[AB]/?(t,push@f,join' ',($a,&r($b),&r($c),$d,$e)):push@g,$_),<STDIN>);
push@f,"B * * * *\n";for $q(@g){if($q=~/[0-9]/){($k,$l,$m,$n)=split/ /,$q; for(@f){&t;$a=$a=~/^A/?'ALLOW':'BLOCK';&o($b,$k)&& &o($c,$m)&& &p($d,$l)&& &p($e,$n)?print"$a $q":next;last}}}  • I'm not familiar with Perl, but at first glance, I see lots of duplicate code, like ($a,$b,$c,$d,$e)=split(/ /); and (($b eq '*') ||$b eq substr(&r($k.'/32'),0,length($b))). Try factoring those into loops or functions. Feb 15, 2012 at 17:38
• A lot of the whitespace can be removed. ...?return 1:return 0 seems to cry out for return ...?1:0 or even return ... if that works. (My knowledge of Perl is quite superficial). Feb 15, 2012 at 20:23
• I believe you can remove the whitespace around ==, ||, ? and :; and the whitespace in $q (, and before all "s. – Ry- Feb 17, 2012 at 4:22 • From a quick glance: 1) most ;} sequences could be rewritten as just } 2) don't call functions as &o, go for straight o 3) a lot of function-call-parentheses can be dropped (for user-defined functions, move the definition somewhere before the call) 4) a lot (all?) of returns can be dropped. – J B Feb 21, 2012 at 22:14 • r might be a case where you can't do it, since return was being used to short-circuit the rest of the function body. – J B Feb 23, 2012 at 8:00 # Ruby (384) Not sure it's 100% correct, but works at least for the given example inputs. Propably could be made a bit shorter too. $ wc gc4907-firewall.rb
9      15     384 gc4907-firewall.rb

$cat fwinput| ruby gc4907-firewall.rb BLOCK 192.168.1.18 1036 157.136.122.5 22 ALLOW 157.136.108.8 2400 192.168.1.1 80  q=->x{x.gsub(/(\d+)\.?/){'%02x'%$1}.hex}
w=->x{u=x.scan(/[\d.]+\/\d+/)
p=$'.split.map{|z|z=="*"?->y{1}:(z=eval z.sub(?:,'..');->y{z===y.to_i})} f=u.map{|z|a,m=z.split ?/;a=q[a]>>(m=32-m.to_i);->y{a==q[y]>>m}}.zip(p).flatten ->y{f.zip(y.split).all?{|z,c|z[c]}&&x[0]}} s=[] while gets=~/./;s<<w[$_];end
s<<->x{0}
$<.map{|p|s.map{|f|(f=f[p])&&(puts (f=="A"?"ALLOW ":"BLOCK ")+p;break)}} $' seems to confuse both brace matching in emacs, and syntax highlight here.