Implementing a firewall

6

Your program reads from standard input and prints to standard output.

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)
|
|  /> Source (incl. mask)
| |
| |               /> 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.

ChristopheD

Posted 2012-02-14T22:34:31.307

Reputation: 1 599

Answers

3

Haskell, 356 characters

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)`div`2^(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

hammar

Posted 2012-02-14T22:34:31.307

Reputation: 4 011

1

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}}}

resmon6

Posted 2012-02-14T22:34:31.307

Reputation: 111

2I'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. – mellamokb – 2012-02-15T17:38:17.463

2A 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). – Peter Taylor – 2012-02-15T20:23:15.050

You read my mind. I just changed that return statement. I'll work on the rest of the whitespace. I believe the return (VALUE) needs to stay so that perl doesn't try to interpret return(ARGS). – resmon6 – 2012-02-15T20:30:20.973

1I believe you can remove the whitespace around ==, ||, ? and :; and the whitespace in $q (, and before all "s. – Ry- – 2012-02-17T04:22:50.743

Thanks for all the help. I think I've gone about as far as I can short of writing a more efficient process. – resmon6 – 2012-02-17T15:16:05.683

1From 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 – 2012-02-21T22:14:08.143

@JB When I remove return from sub r{($i,$j)=split('/',shift);$j eq 0?return'*' perl complains about me calling it in void context. Useless use of a constant in void context at f3.pl line 5. I'm calling this subroutine as a scalar, is this just perl getting confused? – resmon6 – 2012-02-22T15:34:32.553

1r 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 – 2012-02-23T08:00:20.673

1

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.

jsvnm

Posted 2012-02-14T22:34:31.307

Reputation: 441