Browse Source

initial

master
root 5 years ago
commit
bb649fc0f3
3 changed files with 504 additions and 0 deletions
  1. 0
    0
      README.md
  2. 20
    0
      dhcpgen.conf
  3. 484
    0
      dhcpgen.pl

+ 0
- 0
README.md View File


+ 20
- 0
dhcpgen.conf View File

@@ -0,0 +1,20 @@
{
"pcap": {
"interface": "eth1",
"_filter": "( arp ) or ( ip and port 67 ) or ( ip6 and port 547 )",
"filter": "icmp6 or port 67 or port 68 or port 546 or port 547 or arp",
"__filter": "port 546 or port 547",
"snaplen": 2000
},
"sleep": 1000000,
"maxpkt": 1,
"clients": {
"count": 10,
"baseoui": "5254FF",
"_rampup": "number of clients per second to start, CANNOT BE 0 !!!",
"rampup": 1,
"ipv4": 1,
"ipv6": 0,
"backoff": [ 2, 5, 10, 20, 20, 20, 20 ]
}
}

+ 484
- 0
dhcpgen.pl View File

@@ -0,0 +1,484 @@
#!/usr/bin/perl

use strict;
use JSON;
use Data::Dumper;
use Net::Pcap;
use Time::HiRes qw(usleep);
use Socket qw(AF_INET6 inet_ntop inet_ntoa);
use Switch;
use warnings;

$Data::Dumper::Sortkeys = 1;

my $c = loadconf(defined $ARGV[0] ? $ARGV[0] : "dhcpgen.conf");

my $recv = recv_setup();
my $send = send_setup();
my $cli = gen_clients();
my $ip2mac = { };

# print Dumper($cli); exit;

while(1) {
my $recv_pkt = [ ];
recv_work($recv, $recv_pkt);
print Dumper($recv_pkt);

# process received packets
foreach my $pkt ( @{$recv_pkt} ) {
pkt_work($pkt);
}

# check what clients have to send
# foreach my $mac ( keys %{$cli} ) {
# cli_work($mac);
# }

usleep $c->{sleep};
}

sub gen_clients {
my %cl;
my $toff = 2;
for(my $i=0; $i<$c->{clients}{count}; $i++) {
my $mac = sprintf("%s%06X", $c->{clients}{baseoui}, $i);

$toff++ if !($i % $c->{clients}{rampup});

$cl{$mac}{ipv4} = {
dhcp => {
state => 0,
timer => time() + $toff,
retry => 0,
},
} if $c->{clients}{ipv4};

$cl{$mac}{ipv6} = {
dhcp => {
state => 0,
timer => time() + $toff,
retry => 0,
},
} if $c->{clients}{ipv6};

$cl{$mac}{queue} = [ ];
}
return \%cl;
}

sub loadconf {
my($file) = @_;

my $x='';
open(F,$file) || die "No such file '$file'";
while(<F>) { $x.=$_; }
close(F);

return from_json($x);
}

sub recv_setup {
my $err = '';

my $pcap = pcap_open_live($c->{pcap}{interface}, $c->{pcap}{snaplen}, 100, 100, \$err);

my $filter;

if ( pcap_compile($pcap, \$filter, $c->{pcap}{filter}, 1, 0) ne 0 ) {
die "pcap_compile failed -> filter -> '$c->{pcap}{filter}'";
}

pcap_setfilter($pcap, $filter);

return $pcap;
}

sub send_setup {
my $x = { };
return $x;
}

sub recv_work {
my($ctx, $ret) = @_;

pcap_loop($ctx, $c->{maxpkt}, \&recv_work_pkt, $ret);
}

sub pkt_work {
my ($pkt) = @_;

print Dumper("received packet", $pkt);

if ( exists $pkt->{arp} ) {
my $dstmac = $pkt->{arp}{dst}{eth};
my $dstip = $pkt->{arp}{dst}{addr};
if ( exists $cli->{$dstmac} ) {
push @{$cli->{$dstmac}{queue}}, make_arp_reply($pkt);
}
elsif ( exists $ip2mac->{$dstip} ) {
push @{$cli->{$ip2mac->{$dstip}}{queue}}, make_arp_reply($pkt);
}
}
elsif ( exists $pkt->{dhcp} ) {
my $mac = $pkt->{eth}{dst};
if ( $pkt->{ip}{version} eq 4 ) {
push @{$cli->{$mac}{queue}}, make_dhcp_reply($pkt) if exists $cli->{$mac};
}
elsif ( $pkt->{ip}{version} eq 6 ) {
push @{$cli->{$mac}{queue}}, make_dhcp6_reply($pkt) if exists $cli->{$mac};
}
}
elsif ( exists $pkt->{icmp} && $pkt->{ip}{version} eq 6 ) {
my $mac = $pkt->{eth}{dst};
push @{$cli->{$mac}{queue}}, make_icmp_reply($pkt) if exists $cli->{$mac};
}
}

sub cli_work {
my($mac) = @_;

print "check client tasks for mac $mac\n";
my $cl = $cli->{$mac};

if ( exists $cl->{ipv4} ) {
if ( $cl->{ipv4}{dhcp}{timer} < time() ) {
# send discover
if ( $cl->{ipv4}{dhcp}{state} eq 0 ) {
push @{$cl->{queue}}, make_dhcp($mac,"DISCOVER");
}
# expect offer
elsif ( $cl->{ipv4}{dhcp}{state} eq 1 ) {
}
# send request
elsif ( $cl->{ipv4}{dhcp}{state} eq 2 ) {
}
# expect ack
elsif ( $cl->{ipv4}{dhcp}{state} eq 3 ) {
}

}
}

if ( exists $cl->{ipv6} ) {
if ( $cl->{ipv6}{dhcp}{timer} < time() ) {
# send solicit
if ( $cl->{ipv4}{dhcp}{state} eq 0 ) {
push @{$cl->{queue}}, make_dhcp6($mac,"DISCOVER");
}
# expect advertise
elsif ( $cl->{ipv4}{dhcp}{state} eq 1 ) {
}
# send request
elsif ( $cl->{ipv4}{dhcp}{state} eq 2 ) {
}
# expect reply
elsif ( $cl->{ipv4}{dhcp}{state} eq 3 ) {
}
}
}

print Dumper($cl);

while((my $pkt = shift(@{$cl->{queue}}))) {
send_pkt($pkt);
}

}

sub recv_work_pkt {
my($ret, $hdr, $pkt) = @_;

if ( $hdr->{caplen} ne $hdr->{len} ) {
die "ERROR truncated packet, caplen $hdr->{caplen} < len $hdr->{len}. Increase snaplen!\n";
}

print Dumper("header",$hdr);
hexdump($pkt,"Packet");

my $dec = recv_work_dec($hdr,$pkt);
$dec->{hdr} = $hdr;
push @{$ret}, $dec;

}

sub recv_work_dec {
my($hdr,$pkt) = @_;
my $d = { };

my $offset = 0;
$d->{eth}{dst} = unpack("H12", substr($pkt, $offset, 6)); $offset+=6;
$d->{eth}{src} = unpack("H12", substr($pkt, $offset, 6)); $offset+=6;
$d->{eth}{type} = unpack("H4", substr($pkt, $offset, 2)); $offset+=2;

# dot1q
if ( $d->{eth}{type} eq '8100' ) {
$d->{eth}{vlan} = unpack("n",substr($pkt,$offset,2)); $offset+=2;
$d->{eth}{vlan} &= 0x0fff;
$d->{eth}{type} = unpack("H4",substr($pkt, $offset, 2)); $offset+=2;
}
# q-in-q
elsif ( $d->{eth}{type} eq '88a8' ) {
$d->{eth}{ovlan} = unpack("n",substr($pkt,$offset,2)); $offset+=2;
$d->{eth}{ovlan} &= 0x0fff;
$offset+=2; #skip next ethertype, it's supposed to be 8100 but we don't care
$d->{eth}{ivlan} = unpack("n",substr($pkt,$offset,2)); $offset+=2;
$d->{eth}{ivlan} &= 0x0fff;
}

# ARP
if ( $d->{eth}{type} eq '0806' ) {
$d->{eth}{child} = 'arp';
$d->{arp}{htype} = unpack("n", substr($pkt, $offset, 2)); $offset+=2;
$d->{arp}{ptype} = unpack("H4", substr($pkt, $offset, 2)); $offset+=2;
$d->{arp}{hlen} = unpack("C", substr($pkt, $offset, 1)); $offset++;
$d->{arp}{plen} = unpack("C", substr($pkt, $offset, 1)); $offset++;
$d->{arp}{opcode} = unpack("n", substr($pkt, $offset, 2)); $offset+=2;

switch ($d->{arp}{opcode}) {
case '1' { $d->{arp}{opcode} = 'request'; }
case '2' { $d->{arp}{opcode} = 'reply'; }
case '3' { $d->{arp}{opcode} = 'request reverse'; }
case '4' { $d->{arp}{opcode} = 'reply reverse'; }
}

# error handling
$d->{error} = 'ARP: non ethernet arp' if $d->{arp}{htype} ne 1;
$d->{error} = 'ARP: non IPv4 arp' if $d->{arp}{ptype} ne '0800';
$d->{error} = 'ARP: ethernet addr isnt 6 octets long' if $d->{arp}{hlen} ne 6;
$d->{error} = 'ARP: IPv4 isnt 4 octets long' if $d->{arp}{plen} ne 4;

return $d if exists $d->{error};
$d->{arp}{src}{eth} = unpack("H*",substr($pkt, $offset, $d->{arp}{hlen}));
$offset += $d->{arp}{hlen};

$d->{arp}{src}{addr} = inet_ntoa(substr($pkt, $offset, $d->{arp}{plen}));
$offset += $d->{arp}{plen};

$d->{arp}{dst}{eth} = unpack("H*",substr($pkt, $offset, $d->{arp}{hlen}));
$offset += $d->{arp}{hlen};

$d->{arp}{dst}{addr} = inet_ntoa(substr($pkt, $offset, $d->{arp}{plen}));
$offset += $d->{arp}{plen};
}
# IPv4
elsif ( $d->{eth}{type} eq '0800' ) {
$d->{eth}{child} = 'ip';
my $hdrpos = $offset;
$d->{ip}{version} = ( unpack("C",substr($pkt,$offset,1)) & 0xf0 ) >> 4;
$d->{ip}{hdr_len} = unpack("C",substr($pkt,$offset,1)) & 0x0f; $offset++;
$offset++; # skip diffserv
$d->{ip}{tot_len} = unpack("n",substr($pkt, $offset, 2)); $offset+=2;
$offset+=5; # skip identification, flags, fragment offset, ttl
$d->{ip}{proto} = unpack("C",substr($pkt, $offset, 1)); $offset++;
$offset+=2; #skip checksum
$d->{ip}{src} = inet_ntoa(substr($pkt, $offset, 4)); $offset+=4;
$d->{ip}{dst} = inet_ntoa(substr($pkt, $offset, 4)); $offset+=4;

# skip the IP options
$offset = $hdrpos + ( $d->{ip}{hdr_len} * 4 );

# UDP
if ( $d->{ip}{proto} eq '17' ) {
$d->{ip}{child} = 'udp';
$d->{udp}{src} = unpack("n",substr($pkt, $offset, 2)); $offset+=2;
$d->{udp}{dst} = unpack("n",substr($pkt, $offset, 2)); $offset+=2;
$d->{udp}{len} = unpack("n",substr($pkt, $offset, 2)); $offset+=2;
$offset+=2; #skip checksum

# DHCP
if ( $d->{udp}{dst} eq 67 or $d->{udp}{dst} eq 68 ) {
$d->{udp} = 'dhcp';
$d->{dhcp}{opcode} = unpack("C",substr($pkt, $offset, 1)); $offset++;
$d->{dhcp}{htype} = unpack("C",substr($pkt, $offset, 1)); $offset++;
$d->{dhcp}{hlen} = unpack("C",substr($pkt, $offset, 1)); $offset++;
$offset++; #skip hop count
$d->{dhcp}{transid}= unpack("H*",substr($pkt, $offset, 4)); $offset+=4;
$offset+=4; # secs, flags
$d->{dhcp}{ciaddr} = inet_ntoa(substr($pkt, $offset, 4)); $offset+=4;
$d->{dhcp}{yiaddr} = inet_ntoa(substr($pkt, $offset, 4)); $offset+=4;
$d->{dhcp}{siaddr} = inet_ntoa(substr($pkt, $offset, 4)); $offset+=4;
$d->{dhcp}{giaddr} = inet_ntoa(substr($pkt, $offset, 4)); $offset+=4;

$d->{dhcp}{haddr} = unpack("H*",substr($pkt, $offset, $d->{dhcp}{hlen})); $offset += 16;
$offset+=64; #skip hostname
$offset+=128; #skip filename
$offset+=4; #skip magic

while($offset<$hdr->{caplen}) {
my $t = unpack("C",substr($pkt, $offset, 1)); $offset++;
next if $t eq 0;
last if $t eq 255;
my $l = unpack("C",substr($pkt, $offset, 1)); $offset++;
my $v = substr($pkt, $offset, $l); $offset+=$l;

if ( $t eq 1 ) {
$d->{dhcp}{opt}{netmask} = inet_ntoa($v);
}
elsif ( $t eq 3 ) {
$d->{dhcp}{opt}{gateway} = inet_ntoa($v);
}
elsif ( $t eq 51 ) {
$d->{dhcp}{opt}{leasetime} = unpack("N",$v);
}
elsif ( $t eq 53 ) {
$d->{dhcp}{opt}{type} = unpack("C",$v);
switch ($d->{dhcp}{opt}{type}) {
case 1 { $d->{dhcp}{opt}{type} = 'DISCOVER'; }
case 2 { $d->{dhcp}{opt}{type} = 'OFFER'; }
case 3 { $d->{dhcp}{opt}{type} = 'REQUEST'; }
case 4 { $d->{dhcp}{opt}{type} = 'DECLINE'; }
case 5 { $d->{dhcp}{opt}{type} = 'ACK'; }
case 6 { $d->{dhcp}{opt}{type} = 'NAK'; }
case 7 { $d->{dhcp}{opt}{type} = 'RELEASE'; }
}
}
}
}
}
}
# IPv6
elsif ( $d->{eth}{type} eq '86dd' ) {
$d->{eth}{child} = 'ip';
$d->{ip}{version} = ( unpack("C",substr($pkt,$offset,1)) & 0xf0 ) >> 4;
$offset++;
$offset+=3; #skip flow label
$d->{ip}{len} = unpack("n",substr($pkt,$offset,2)); $offset+=2;
$d->{ip}{proto} = unpack("C",substr($pkt,$offset,1)); $offset++;
$offset++; #skip ttl
$d->{ip}{src} = inet_ntop(AF_INET6, substr($pkt,$offset,16)); $offset+=16;
$d->{ip}{dst} = inet_ntop(AF_INET6, substr($pkt,$offset,16)); $offset+=16;

# ICMPv6
if ( $d->{ip}{proto} eq 58 ) {
$d->{ip}{child} = 'icmp';
$d->{icmp}{type} = unpack("C",substr($pkt,$offset,1)); $offset++;
$d->{icmp}{code} = unpack("C",substr($pkt,$offset,1)); $offset++;
$offset+=2; #skip checksum

if ( $d->{icmp}{type} >= 133 && $d->{icmp}{type} <= 136 ) {

my $chd;

switch ($d->{icmp}{type}) {
case 133 { $chd = 'rs'; }
case 134 { $chd = 'ra'; }
case 135 { $chd = 'ns'; }
case 136 { $chd = 'na'; }
}


if ( $chd eq 'ns' ) {
$offset+=4; #skip reserved
$d->{$chd}{dst_addr} = inet_ntop(AF_INET6,substr($pkt,$offset,16));
$offset+=16;
}
elsif ( $chd eq 'ra' ) {
$offset+=12; #skip the whole fixed header
}
elsif ( $chd eq 'na' ) {
$offset+=4; # skip RSO and Reserved
$d->{$chd}{dst_addr} = inet_ntop(AF_INET6,substr($pkt,$offset,16));
$offset+=16;
}
elsif ( $chd eq 'rs' ) {
$offset+=4; #skip reserved
}

while($offset<$hdr->{caplen}) {
my $t = unpack("C",substr($pkt,$offset,1)); $offset++;
my $l = 8 * unpack("C",substr($pkt,$offset,1)); $offset++;
last if !$l;
my $v = substr($pkt,$offset,$l-2); $offset+=$l-2;


if ( $t eq 1 ) {
$d->{$chd}{src_lladdr} = unpack("H*",$v);
}
elsif ( $t eq 2 ) {
$d->{$chd}{dst_lladdr} = unpack("H*",$v);
}
elsif ( $t eq 13 ) {
$d->{$chd}{timestamp}{sec} = hex(unpack("H12",substr($v, 6, 6)));
$d->{$chd}{timestamp}{frac} = unpack("n",substr($v,12,2));
}
elsif ( $t eq 14 ) {
$d->{$chd}{nonce} = unpack("H*",$v);
}
else {
$d->{$chd}{opt}{$t} = unpack("H*",$v);
}
}
}
}
# UDP
elsif ( $d->{ip}{proto} eq 17 ) {
$d->{ip}{child} = 'udp';
$d->{udp}{src} = unpack("n",substr($pkt, $offset, 2)); $offset+=2;
$d->{udp}{dst} = unpack("n",substr($pkt, $offset, 2)); $offset+=2;
$d->{udp}{len} = unpack("n",substr($pkt, $offset, 2)); $offset+=2;
$offset+=2; #skip checksum

# DHCP
if ( $d->{udp}{dst} eq 546 or $d->{udp}{dst} eq 547 ) {
$d->{udp}{child} = 'dhcp';

$d->{dhcp}{msgtype} = unpack("C", substr($pkt,$offset,1)); $offset++;
$d->{dhcp}{transactionid} = unpack("H*",substr($pkt,$offset,3)); $offset+=3;

while($offset<$hdr->{caplen}) {
my $t = unpack("n",substr($pkt,$offset,2)); $offset+=2;
my $l = unpack("n",substr($pkt,$offset,2)); $offset+=2;
my $v = substr($pkt,$offset,$l); $offset+=$l;

$d->{dhcp}{opt}{$t} = unpack("H*",$v);
}
}
}
}

return $d;
}

sub hexdump {
my($bin, $name) = @_;

my $len = length($bin);

print "=== hexdump $name ===\n";
print "=== len: $len\n";
my $b=0;
while($b<$len) {
printf("0x%04x:", $b);
# print hex part
for(my $h=0; $h<16; $h++) {
print " " if !($h % 2);
# read octet is within length range
if ( $b+$h<$len ) {
printf("%02x", unpack("C",substr($bin, $b+$h, 1)));
} else {
print " ";
}
}
print " ";
# print chars
for(my $h=0; $h<16; $h++) {

my $c = 32; # default char

if ( $b+$h < $len ) {
$c = unpack("C",substr($bin, $b+$h, 1));
# clear non-printable chars (46 is .)
$c = 46 if ( $c < 33 || $c > 126 );
}

# print the char
printf("%c", $c);
}
print "\n";
$b+=16;
}
print "=====================\n";
}

Loading…
Cancel
Save