an unfinished DHCP/ARP/ND client simulator that can use dot1q/QinQ to simulator thousands of FTTH customers.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

dhcpgen.pl 13KB

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