From: pdcawley@ftech.net (Piers Cawley)
Newsgroups: comp.lang.perl.misc
Subject: Raw Sockets in Perl
Date: 22 Aug 1996 12:57:00 GMT
Organization: Frontier Internet Services
Lines: 142
Message-ID: <4vhles$ppl@alpha.ftech.net>
NNTP-Posting-Host: mercury.ftech.net
Mime-Version: 1.0
Content-Type: text/plain; charset=iso-8859-1
Content-Transfer-Encoding: 8bit
X-Newsreader: knews 0.9.6

It's like this: I'm trying to implement IP address based access
control lists for my linux based quake server. The simple way to do
this is just to use the Linux firewalling code to deny packets from
any host in the ban list.

However, in a fit of elegance (and bolstered by the fact that ID have
released partial specs to Quake's network protocol) I thought it would
be neat to have the server send back a proper deny message for hosts
outside the domain. Now, sitting something on the quake port and
sending back deny messages is an absolute doddle, the sample udp
client in the perlipc manpage can be adopted very easily. But of
course this won't work 'cos if you start the denier up then quake
can't bind on the port.

So, what I did was to use the linux firewalling's transparent proxy
code, so that any udp packets that arrive from disallowed hosts are
automatically passed to a different port. The responder then needs to
send back spoofed denial packets (they need to appear to come from the
port they were sent to) and there's the rub.

I've tried doing some (vaguely voodoo) raw socket coding to spoof the
packets, but as far as tcpdump can see no packets are getting out to
the net...

Here's the code. As far as the code can see it's sending the packets
out (I'm seeing no errors). As far as the network can see, those
packets are nowhere to be seen. I'm sure that it all boils down to me
doing something seriously dumb, but I can't for the life of me see
what it is.

--- *SNIP* ---
#!/usr/bin/perl -w

require 5.002;
use Socket;
use Sys::Hostname;

###
### Subroutines
###

## Build a Quake Deny packet.
## We're using raw sockets here, so we have to build the IP and UDP
## headers as well.

sub makePacket {
    my $message = shift;
    my $hispaddr = shift;
    my ($hisport, $hisiaddr) = sockaddr_in($hispaddr);
    my $ouriaddr = gethostbyname(hostname());

    my $qlength = 7 + length $message; # Length of Quake packet
    my $dlength = 28 + $qlength;       # Length of ip packet
    my $ulength = 13 + $qlength;       # Length of udp packet

    my $packstring = 'CCnn2CCna4a4 n2n2 nnCa' . (length($message) + 1);
    my $packet = pack $packstring,
    0x45, 0, $dlength,
    0x1234, 0x0000,
    0xff, 0x11, 0x0000, # Don't bother with checksums
    $hisiaddr, $ouriaddr,
    $hisport, 26000,    # Standard Quake UDP port, should be a variable...
    $ulength, 0x0000,   # Again, no checksum
    0x8000, $qlength,   # Quake packet header
    0x82,               # CONNECTION_DENY
    $message;           # Reason (null terminated)
    return $packet;
}

## Check to see if packet is a connection request.
sub isaConnect {
    my $packet = shift;

    my ($first ,$length, $type, $string) = (unpack('nnCa5C', $packet))[0 .. 3];

    $type == 0x1 and
        $length == 12 and
            $string eq 'QUAKE' and
                $first == 0x8000;
}

### The Game's afoot!

# Name those variables
my ($iaddr, $paddr, $port, $proto, $rin, $rout,
    $hisiaddr, $hispaddr, $message, $packet);

# Initialize those sockets dude!

$iaddr  = gethostbyname(hostname());
$proto  = getprotobyname('udp');
$outproto = getprotobyname('raw');
$port   = 26001;        # Here's where it's redirected to.
$outport = 26000;
$paddr  = sockaddr_in($port, $iaddr);

socket(SOCKET, PF_INET, SOCK_DGRAM, $proto) || die "socket: $!";
bind(SOCKET, $paddr)                        || die "bind: $!";

## Are we brave? Do we like playing with RAW Sockets?
## Can we do it any other way?

socket(OUTSOCKET, PF_INET, SOCK_RAW, $outproto) || die "socket: $!";
setsockopt(OUTSOCKET,getprotobyname('ip'),3,1)   || die "setsockopt: $!";

$| = 1;
$packet = '';

$rin = '';

vec($rin, fileno(SOCKET), 1) = 1;

while (select($rout = $rin, undef, undef, undef)) {
    unless ($hispaddr = recv(SOCKET, $packet, 11, 0)) {
        warn "recv: $!";
        next;
    }
    if(isaConnect($packet)) {
        warn "It's a connect packet!";
        unless (send(OUTSOCKET,
                     makePacket("Sorry, you can't come here.",
                                $hispaddr),
                     0,$hispaddr)) {
            warn "send: $!";
        }
    }
}

--
   Piers Cawley - Systems Sheriff on the Frontier Internet Service
            Purveyors of fine connections to the Internet
          0D 02 A0 20 54 E0 60 02   2B 77 F8 D1 8B EB 3F 36
     finger pdcawley@mercury.ftech.net for PGP key and Geek Code

