|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 |
- #!/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";
- }
|