an unfinished DHCP/ARP/ND client simulator that can use dot1q/QinQ to simulator thousands of FTTH customers.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

dhcpgen.pl 13KB

5 vuotta sitten
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. #!/usr/bin/perl
  2. use strict;
  3. use JSON;
  4. use Data::Dumper;
  5. use Net::Pcap;
  6. use Time::HiRes qw(usleep);
  7. use Socket qw(AF_INET6 inet_ntop inet_ntoa);
  8. use Switch;
  9. use warnings;
  10. $Data::Dumper::Sortkeys = 1;
  11. my $c = loadconf(defined $ARGV[0] ? $ARGV[0] : "dhcpgen.conf");
  12. my $recv = recv_setup();
  13. my $send = send_setup();
  14. my $cli = gen_clients();
  15. my $ip2mac = { };
  16. # print Dumper($cli); exit;
  17. while(1) {
  18. my $recv_pkt = [ ];
  19. recv_work($recv, $recv_pkt);
  20. print Dumper($recv_pkt);
  21. # process received packets
  22. foreach my $pkt ( @{$recv_pkt} ) {
  23. pkt_work($pkt);
  24. }
  25. # check what clients have to send
  26. # foreach my $mac ( keys %{$cli} ) {
  27. # cli_work($mac);
  28. # }
  29. usleep $c->{sleep};
  30. }
  31. sub gen_clients {
  32. my %cl;
  33. my $toff = 2;
  34. for(my $i=0; $i<$c->{clients}{count}; $i++) {
  35. my $mac = sprintf("%s%06X", $c->{clients}{baseoui}, $i);
  36. $toff++ if !($i % $c->{clients}{rampup});
  37. $cl{$mac}{ipv4} = {
  38. dhcp => {
  39. state => 0,
  40. timer => time() + $toff,
  41. retry => 0,
  42. },
  43. } if $c->{clients}{ipv4};
  44. $cl{$mac}{ipv6} = {
  45. dhcp => {
  46. state => 0,
  47. timer => time() + $toff,
  48. retry => 0,
  49. },
  50. } if $c->{clients}{ipv6};
  51. $cl{$mac}{queue} = [ ];
  52. }
  53. return \%cl;
  54. }
  55. sub loadconf {
  56. my($file) = @_;
  57. my $x='';
  58. open(F,$file) || die "No such file '$file'";
  59. while(<F>) { $x.=$_; }
  60. close(F);
  61. return from_json($x);
  62. }
  63. sub recv_setup {
  64. my $err = '';
  65. my $pcap = pcap_open_live($c->{pcap}{interface}, $c->{pcap}{snaplen}, 100, 100, \$err);
  66. my $filter;
  67. if ( pcap_compile($pcap, \$filter, $c->{pcap}{filter}, 1, 0) ne 0 ) {
  68. die "pcap_compile failed -> filter -> '$c->{pcap}{filter}'";
  69. }
  70. pcap_setfilter($pcap, $filter);
  71. return $pcap;
  72. }
  73. sub send_setup {
  74. my $x = { };
  75. return $x;
  76. }
  77. sub recv_work {
  78. my($ctx, $ret) = @_;
  79. pcap_loop($ctx, $c->{maxpkt}, \&recv_work_pkt, $ret);
  80. }
  81. sub pkt_work {
  82. my ($pkt) = @_;
  83. print Dumper("received packet", $pkt);
  84. if ( exists $pkt->{arp} ) {
  85. my $dstmac = $pkt->{arp}{dst}{eth};
  86. my $dstip = $pkt->{arp}{dst}{addr};
  87. if ( exists $cli->{$dstmac} ) {
  88. push @{$cli->{$dstmac}{queue}}, make_arp_reply($pkt);
  89. }
  90. elsif ( exists $ip2mac->{$dstip} ) {
  91. push @{$cli->{$ip2mac->{$dstip}}{queue}}, make_arp_reply($pkt);
  92. }
  93. }
  94. elsif ( exists $pkt->{dhcp} ) {
  95. my $mac = $pkt->{eth}{dst};
  96. if ( $pkt->{ip}{version} eq 4 ) {
  97. push @{$cli->{$mac}{queue}}, make_dhcp_reply($pkt) if exists $cli->{$mac};
  98. }
  99. elsif ( $pkt->{ip}{version} eq 6 ) {
  100. push @{$cli->{$mac}{queue}}, make_dhcp6_reply($pkt) if exists $cli->{$mac};
  101. }
  102. }
  103. elsif ( exists $pkt->{icmp} && $pkt->{ip}{version} eq 6 ) {
  104. my $mac = $pkt->{eth}{dst};
  105. push @{$cli->{$mac}{queue}}, make_icmp_reply($pkt) if exists $cli->{$mac};
  106. }
  107. }
  108. sub cli_work {
  109. my($mac) = @_;
  110. print "check client tasks for mac $mac\n";
  111. my $cl = $cli->{$mac};
  112. if ( exists $cl->{ipv4} ) {
  113. if ( $cl->{ipv4}{dhcp}{timer} < time() ) {
  114. # send discover
  115. if ( $cl->{ipv4}{dhcp}{state} eq 0 ) {
  116. push @{$cl->{queue}}, make_dhcp($mac,"DISCOVER");
  117. }
  118. # expect offer
  119. elsif ( $cl->{ipv4}{dhcp}{state} eq 1 ) {
  120. }
  121. # send request
  122. elsif ( $cl->{ipv4}{dhcp}{state} eq 2 ) {
  123. }
  124. # expect ack
  125. elsif ( $cl->{ipv4}{dhcp}{state} eq 3 ) {
  126. }
  127. }
  128. }
  129. if ( exists $cl->{ipv6} ) {
  130. if ( $cl->{ipv6}{dhcp}{timer} < time() ) {
  131. # send solicit
  132. if ( $cl->{ipv4}{dhcp}{state} eq 0 ) {
  133. push @{$cl->{queue}}, make_dhcp6($mac,"DISCOVER");
  134. }
  135. # expect advertise
  136. elsif ( $cl->{ipv4}{dhcp}{state} eq 1 ) {
  137. }
  138. # send request
  139. elsif ( $cl->{ipv4}{dhcp}{state} eq 2 ) {
  140. }
  141. # expect reply
  142. elsif ( $cl->{ipv4}{dhcp}{state} eq 3 ) {
  143. }
  144. }
  145. }
  146. print Dumper($cl);
  147. while((my $pkt = shift(@{$cl->{queue}}))) {
  148. send_pkt($pkt);
  149. }
  150. }
  151. sub recv_work_pkt {
  152. my($ret, $hdr, $pkt) = @_;
  153. if ( $hdr->{caplen} ne $hdr->{len} ) {
  154. die "ERROR truncated packet, caplen $hdr->{caplen} < len $hdr->{len}. Increase snaplen!\n";
  155. }
  156. print Dumper("header",$hdr);
  157. hexdump($pkt,"Packet");
  158. my $dec = recv_work_dec($hdr,$pkt);
  159. $dec->{hdr} = $hdr;
  160. push @{$ret}, $dec;
  161. }
  162. sub recv_work_dec {
  163. my($hdr,$pkt) = @_;
  164. my $d = { };
  165. my $offset = 0;
  166. $d->{eth}{dst} = unpack("H12", substr($pkt, $offset, 6)); $offset+=6;
  167. $d->{eth}{src} = unpack("H12", substr($pkt, $offset, 6)); $offset+=6;
  168. $d->{eth}{type} = unpack("H4", substr($pkt, $offset, 2)); $offset+=2;
  169. # dot1q
  170. if ( $d->{eth}{type} eq '8100' ) {
  171. $d->{eth}{vlan} = unpack("n",substr($pkt,$offset,2)); $offset+=2;
  172. $d->{eth}{vlan} &= 0x0fff;
  173. $d->{eth}{type} = unpack("H4",substr($pkt, $offset, 2)); $offset+=2;
  174. }
  175. # q-in-q
  176. elsif ( $d->{eth}{type} eq '88a8' ) {
  177. $d->{eth}{ovlan} = unpack("n",substr($pkt,$offset,2)); $offset+=2;
  178. $d->{eth}{ovlan} &= 0x0fff;
  179. $offset+=2; #skip next ethertype, it's supposed to be 8100 but we don't care
  180. $d->{eth}{ivlan} = unpack("n",substr($pkt,$offset,2)); $offset+=2;
  181. $d->{eth}{ivlan} &= 0x0fff;
  182. }
  183. # ARP
  184. if ( $d->{eth}{type} eq '0806' ) {
  185. $d->{eth}{child} = 'arp';
  186. $d->{arp}{htype} = unpack("n", substr($pkt, $offset, 2)); $offset+=2;
  187. $d->{arp}{ptype} = unpack("H4", substr($pkt, $offset, 2)); $offset+=2;
  188. $d->{arp}{hlen} = unpack("C", substr($pkt, $offset, 1)); $offset++;
  189. $d->{arp}{plen} = unpack("C", substr($pkt, $offset, 1)); $offset++;
  190. $d->{arp}{opcode} = unpack("n", substr($pkt, $offset, 2)); $offset+=2;
  191. switch ($d->{arp}{opcode}) {
  192. case '1' { $d->{arp}{opcode} = 'request'; }
  193. case '2' { $d->{arp}{opcode} = 'reply'; }
  194. case '3' { $d->{arp}{opcode} = 'request reverse'; }
  195. case '4' { $d->{arp}{opcode} = 'reply reverse'; }
  196. }
  197. # error handling
  198. $d->{error} = 'ARP: non ethernet arp' if $d->{arp}{htype} ne 1;
  199. $d->{error} = 'ARP: non IPv4 arp' if $d->{arp}{ptype} ne '0800';
  200. $d->{error} = 'ARP: ethernet addr isnt 6 octets long' if $d->{arp}{hlen} ne 6;
  201. $d->{error} = 'ARP: IPv4 isnt 4 octets long' if $d->{arp}{plen} ne 4;
  202. return $d if exists $d->{error};
  203. $d->{arp}{src}{eth} = unpack("H*",substr($pkt, $offset, $d->{arp}{hlen}));
  204. $offset += $d->{arp}{hlen};
  205. $d->{arp}{src}{addr} = inet_ntoa(substr($pkt, $offset, $d->{arp}{plen}));
  206. $offset += $d->{arp}{plen};
  207. $d->{arp}{dst}{eth} = unpack("H*",substr($pkt, $offset, $d->{arp}{hlen}));
  208. $offset += $d->{arp}{hlen};
  209. $d->{arp}{dst}{addr} = inet_ntoa(substr($pkt, $offset, $d->{arp}{plen}));
  210. $offset += $d->{arp}{plen};
  211. }
  212. # IPv4
  213. elsif ( $d->{eth}{type} eq '0800' ) {
  214. $d->{eth}{child} = 'ip';
  215. my $hdrpos = $offset;
  216. $d->{ip}{version} = ( unpack("C",substr($pkt,$offset,1)) & 0xf0 ) >> 4;
  217. $d->{ip}{hdr_len} = unpack("C",substr($pkt,$offset,1)) & 0x0f; $offset++;
  218. $offset++; # skip diffserv
  219. $d->{ip}{tot_len} = unpack("n",substr($pkt, $offset, 2)); $offset+=2;
  220. $offset+=5; # skip identification, flags, fragment offset, ttl
  221. $d->{ip}{proto} = unpack("C",substr($pkt, $offset, 1)); $offset++;
  222. $offset+=2; #skip checksum
  223. $d->{ip}{src} = inet_ntoa(substr($pkt, $offset, 4)); $offset+=4;
  224. $d->{ip}{dst} = inet_ntoa(substr($pkt, $offset, 4)); $offset+=4;
  225. # skip the IP options
  226. $offset = $hdrpos + ( $d->{ip}{hdr_len} * 4 );
  227. # UDP
  228. if ( $d->{ip}{proto} eq '17' ) {
  229. $d->{ip}{child} = 'udp';
  230. $d->{udp}{src} = unpack("n",substr($pkt, $offset, 2)); $offset+=2;
  231. $d->{udp}{dst} = unpack("n",substr($pkt, $offset, 2)); $offset+=2;
  232. $d->{udp}{len} = unpack("n",substr($pkt, $offset, 2)); $offset+=2;
  233. $offset+=2; #skip checksum
  234. # DHCP
  235. if ( $d->{udp}{dst} eq 67 or $d->{udp}{dst} eq 68 ) {
  236. $d->{udp} = 'dhcp';
  237. $d->{dhcp}{opcode} = unpack("C",substr($pkt, $offset, 1)); $offset++;
  238. $d->{dhcp}{htype} = unpack("C",substr($pkt, $offset, 1)); $offset++;
  239. $d->{dhcp}{hlen} = unpack("C",substr($pkt, $offset, 1)); $offset++;
  240. $offset++; #skip hop count
  241. $d->{dhcp}{transid}= unpack("H*",substr($pkt, $offset, 4)); $offset+=4;
  242. $offset+=4; # secs, flags
  243. $d->{dhcp}{ciaddr} = inet_ntoa(substr($pkt, $offset, 4)); $offset+=4;
  244. $d->{dhcp}{yiaddr} = inet_ntoa(substr($pkt, $offset, 4)); $offset+=4;
  245. $d->{dhcp}{siaddr} = inet_ntoa(substr($pkt, $offset, 4)); $offset+=4;
  246. $d->{dhcp}{giaddr} = inet_ntoa(substr($pkt, $offset, 4)); $offset+=4;
  247. $d->{dhcp}{haddr} = unpack("H*",substr($pkt, $offset, $d->{dhcp}{hlen})); $offset += 16;
  248. $offset+=64; #skip hostname
  249. $offset+=128; #skip filename
  250. $offset+=4; #skip magic
  251. while($offset<$hdr->{caplen}) {
  252. my $t = unpack("C",substr($pkt, $offset, 1)); $offset++;
  253. next if $t eq 0;
  254. last if $t eq 255;
  255. my $l = unpack("C",substr($pkt, $offset, 1)); $offset++;
  256. my $v = substr($pkt, $offset, $l); $offset+=$l;
  257. if ( $t eq 1 ) {
  258. $d->{dhcp}{opt}{netmask} = inet_ntoa($v);
  259. }
  260. elsif ( $t eq 3 ) {
  261. $d->{dhcp}{opt}{gateway} = inet_ntoa($v);
  262. }
  263. elsif ( $t eq 51 ) {
  264. $d->{dhcp}{opt}{leasetime} = unpack("N",$v);
  265. }
  266. elsif ( $t eq 53 ) {
  267. $d->{dhcp}{opt}{type} = unpack("C",$v);
  268. switch ($d->{dhcp}{opt}{type}) {
  269. case 1 { $d->{dhcp}{opt}{type} = 'DISCOVER'; }
  270. case 2 { $d->{dhcp}{opt}{type} = 'OFFER'; }
  271. case 3 { $d->{dhcp}{opt}{type} = 'REQUEST'; }
  272. case 4 { $d->{dhcp}{opt}{type} = 'DECLINE'; }
  273. case 5 { $d->{dhcp}{opt}{type} = 'ACK'; }
  274. case 6 { $d->{dhcp}{opt}{type} = 'NAK'; }
  275. case 7 { $d->{dhcp}{opt}{type} = 'RELEASE'; }
  276. }
  277. }
  278. }
  279. }
  280. }
  281. }
  282. # IPv6
  283. elsif ( $d->{eth}{type} eq '86dd' ) {
  284. $d->{eth}{child} = 'ip';
  285. $d->{ip}{version} = ( unpack("C",substr($pkt,$offset,1)) & 0xf0 ) >> 4;
  286. $offset++;
  287. $offset+=3; #skip flow label
  288. $d->{ip}{len} = unpack("n",substr($pkt,$offset,2)); $offset+=2;
  289. $d->{ip}{proto} = unpack("C",substr($pkt,$offset,1)); $offset++;
  290. $offset++; #skip ttl
  291. $d->{ip}{src} = inet_ntop(AF_INET6, substr($pkt,$offset,16)); $offset+=16;
  292. $d->{ip}{dst} = inet_ntop(AF_INET6, substr($pkt,$offset,16)); $offset+=16;
  293. # ICMPv6
  294. if ( $d->{ip}{proto} eq 58 ) {
  295. $d->{ip}{child} = 'icmp';
  296. $d->{icmp}{type} = unpack("C",substr($pkt,$offset,1)); $offset++;
  297. $d->{icmp}{code} = unpack("C",substr($pkt,$offset,1)); $offset++;
  298. $offset+=2; #skip checksum
  299. if ( $d->{icmp}{type} >= 133 && $d->{icmp}{type} <= 136 ) {
  300. my $chd;
  301. switch ($d->{icmp}{type}) {
  302. case 133 { $chd = 'rs'; }
  303. case 134 { $chd = 'ra'; }
  304. case 135 { $chd = 'ns'; }
  305. case 136 { $chd = 'na'; }
  306. }
  307. if ( $chd eq 'ns' ) {
  308. $offset+=4; #skip reserved
  309. $d->{$chd}{dst_addr} = inet_ntop(AF_INET6,substr($pkt,$offset,16));
  310. $offset+=16;
  311. }
  312. elsif ( $chd eq 'ra' ) {
  313. $offset+=12; #skip the whole fixed header
  314. }
  315. elsif ( $chd eq 'na' ) {
  316. $offset+=4; # skip RSO and Reserved
  317. $d->{$chd}{dst_addr} = inet_ntop(AF_INET6,substr($pkt,$offset,16));
  318. $offset+=16;
  319. }
  320. elsif ( $chd eq 'rs' ) {
  321. $offset+=4; #skip reserved
  322. }
  323. while($offset<$hdr->{caplen}) {
  324. my $t = unpack("C",substr($pkt,$offset,1)); $offset++;
  325. my $l = 8 * unpack("C",substr($pkt,$offset,1)); $offset++;
  326. last if !$l;
  327. my $v = substr($pkt,$offset,$l-2); $offset+=$l-2;
  328. if ( $t eq 1 ) {
  329. $d->{$chd}{src_lladdr} = unpack("H*",$v);
  330. }
  331. elsif ( $t eq 2 ) {
  332. $d->{$chd}{dst_lladdr} = unpack("H*",$v);
  333. }
  334. elsif ( $t eq 13 ) {
  335. $d->{$chd}{timestamp}{sec} = hex(unpack("H12",substr($v, 6, 6)));
  336. $d->{$chd}{timestamp}{frac} = unpack("n",substr($v,12,2));
  337. }
  338. elsif ( $t eq 14 ) {
  339. $d->{$chd}{nonce} = unpack("H*",$v);
  340. }
  341. else {
  342. $d->{$chd}{opt}{$t} = unpack("H*",$v);
  343. }
  344. }
  345. }
  346. }
  347. # UDP
  348. elsif ( $d->{ip}{proto} eq 17 ) {
  349. $d->{ip}{child} = 'udp';
  350. $d->{udp}{src} = unpack("n",substr($pkt, $offset, 2)); $offset+=2;
  351. $d->{udp}{dst} = unpack("n",substr($pkt, $offset, 2)); $offset+=2;
  352. $d->{udp}{len} = unpack("n",substr($pkt, $offset, 2)); $offset+=2;
  353. $offset+=2; #skip checksum
  354. # DHCP
  355. if ( $d->{udp}{dst} eq 546 or $d->{udp}{dst} eq 547 ) {
  356. $d->{udp}{child} = 'dhcp';
  357. $d->{dhcp}{msgtype} = unpack("C", substr($pkt,$offset,1)); $offset++;
  358. $d->{dhcp}{transactionid} = unpack("H*",substr($pkt,$offset,3)); $offset+=3;
  359. while($offset<$hdr->{caplen}) {
  360. my $t = unpack("n",substr($pkt,$offset,2)); $offset+=2;
  361. my $l = unpack("n",substr($pkt,$offset,2)); $offset+=2;
  362. my $v = substr($pkt,$offset,$l); $offset+=$l;
  363. $d->{dhcp}{opt}{$t} = unpack("H*",$v);
  364. }
  365. }
  366. }
  367. }
  368. return $d;
  369. }
  370. sub hexdump {
  371. my($bin, $name) = @_;
  372. my $len = length($bin);
  373. print "=== hexdump $name ===\n";
  374. print "=== len: $len\n";
  375. my $b=0;
  376. while($b<$len) {
  377. printf("0x%04x:", $b);
  378. # print hex part
  379. for(my $h=0; $h<16; $h++) {
  380. print " " if !($h % 2);
  381. # read octet is within length range
  382. if ( $b+$h<$len ) {
  383. printf("%02x", unpack("C",substr($bin, $b+$h, 1)));
  384. } else {
  385. print " ";
  386. }
  387. }
  388. print " ";
  389. # print chars
  390. for(my $h=0; $h<16; $h++) {
  391. my $c = 32; # default char
  392. if ( $b+$h < $len ) {
  393. $c = unpack("C",substr($bin, $b+$h, 1));
  394. # clear non-printable chars (46 is .)
  395. $c = 46 if ( $c < 33 || $c > 126 );
  396. }
  397. # print the char
  398. printf("%c", $c);
  399. }
  400. print "\n";
  401. $b+=16;
  402. }
  403. print "=====================\n";
  404. }