4 # Copyright (C) 2004 Jan Kratochvil <project-harpy@jankratochvil.net>
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; exactly version 2 of June 1991 is required
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 # ha:2345:respawn:/usr/local/sbin/harpy >>/var/log/harpy.log eth1 eth2
22 # ha:2345:respawn:/usr/local/sbin/harpy >>/var/log/harpy.log eth_intra
26 sub __KERNEL__ { 1; } # for "linux/socket.ph"
30 require "linux/if_ether.ph";
31 require "linux/socket.ph";
32 require "linux/sockios.ph";
33 require "linux/if_arp.ph";
35 sub SOCKADDR_SIZEOF { 16; }
41 my $opt_timeout=0.5; # [sec]
43 "t|timeout=s",\$opt_timeout,
54 my $a=unpack("H*",$n);
72 socket $sock,AF_PACKET(),SOCK_RAW(),ETH_P_ARP() or die "No ARP socket: $!";
74 local *ioctl_ifhwaddr=sub($)
78 my $buf=$ifname.("\x00"x0x1000);
79 ioctl $sock,SIOCGIFHWADDR(),$buf or die "ioctl($ifname,SIOCGIFHWADDR): $!";
80 my($trash,$sockaddr)=unpack("a16a16",$buf);
81 my($sa_len,$sa_data)=unpack("a2a6",$sockaddr);
85 local *ioctl_ifipaddr=sub($)
89 my $buf=$ifname.("\x00"x0x1000);
90 ioctl $sock,SIOCGIFADDR(),$buf or die "ioctl($ifname,SIOCGIFADDR): $!";
91 my($trash,$sockaddr)=unpack("a16a16",$buf);
92 my($sa_len,$sin_port,$sin_addr)=unpack("a2a2a4",$sockaddr);
96 local *ioctl_ifindex=sub($)
100 my $buf=$ifname.("\x00"x0x1000);
101 ioctl $sock,SIOCGIFINDEX(),$buf or die "ioctl($ifname,SIOCGIFINDEX): $!";
102 my($trash,$ifindex)=unpack("a16i",$buf);
106 my $hw=ioctl_ifhwaddr($ifname);
107 my $ip=ioctl_ifipaddr($ifname);
108 my $sockaddr_ll=pack "SniSCCa8", # struct sockaddr_ll:
109 AF_PACKET(), # unsigned short int sll_family;
110 ETH_P_ARP(), # unsigned short int sll_protocol;
111 ioctl_ifindex($ifname), # int sll_ifindex;
112 ARPHRD_ETHER(), # unsigned short int sll_hatype;
113 PACKET_BROADCAST(), # unsigned char sll_pkttype;
114 ETH_ALEN(), # unsigned char sll_halen;
115 $hw; # unsigned char sll_addr[8];
117 bind $sock,$sockaddr_ll or die "bind($ifname): $!";
119 return $sock,hw_ntoa($hw),inet_ntoa($ip);
122 #struct ether_arp_frame {
123 # struct ether_header {
124 # u_int8_t ether_dhost[ETH_ALEN]; /* destination eth addr */
125 # u_int8_t ether_shost[ETH_ALEN]; /* source ether addr */
126 # u_int16_t ether_type; /* packet type ID field */
129 # struct arphdr { /* fixed-size header */
130 # unsigned short int ar_hrd; /* Format of hardware address. */
131 # unsigned short int ar_pro; /* Format of protocol address. */
132 # unsigned char ar_hln; /* Length of hardware address. */
133 # unsigned char ar_pln; /* Length of protocol address. */
134 # unsigned short int ar_op; /* ARP opcode (command). */
136 # u_int8_t arp_sha[ETH_ALEN]; /* sender hardware address */
137 # u_int8_t arp_spa[4]; /* sender protocol address */
138 # u_int8_t arp_tha[ETH_ALEN]; /* target hardware address */
139 # u_int8_t arp_tpa[4]; /* target protocol address */
145 my($dst_hw,$src_hw,$dst,$src,$type)=@_;
148 $type_bin=ARPOP_REQUEST() if $type eq "REQUEST";
149 $type_bin=ARPOP_REPLY() if $type eq "REPLY";
150 die "INTERNAL" if !defined $type_bin;
151 my $msg=pack "a6a6nnnCCna6a4a6a4",
152 hw_aton($dst_hw), # $ether_dhost
153 hw_aton($src_hw), # $ether_shost
160 hw_aton($src_hw), # $arp_sha
161 inet_aton($src), # $arp_spa
162 hw_aton($dst_hw), # $arp_tha
163 inet_aton($dst); # $arp_tpa
171 return if 42>length $msg;
185 )=unpack "a6a6nnnCCna6a4a6a4",$msg;
186 $V>=3 and print Data::Dumper->Dump([
214 $type="REQUEST" if $ar_op==ARPOP_REQUEST();
215 $type="REPLY" if $ar_op==ARPOP_REPLY();
217 return if $ar_hln!=6;
218 return if $ar_pln!=4;
219 my $tell=inet_ntoa $arp_spa;
220 my $who_has=inet_ntoa $arp_tpa;
221 my $tell_hw=hw_ntoa $ether_shost;
222 return $tell,$who_has,$tell_hw,$type;
225 my $clock_ticks=POSIX::sysconf(&POSIX::_SC_CLK_TCK);
228 return (POSIX::times())[0]/$clock_ticks;
235 my $ifname=shift @ifnames;
236 if ($ifname=~/{(\d+)-(\d+)}/) {
237 push @ifnames,$`.$_.$' for $1..$2;
240 my($sock,$hw,$ip)=sock($ifname);
247 $V>=2 and print Dumper(\%socks);
249 # $pending{$who_has}=[{"when"=>now()+$timeout,"sock"=>$hash->{"sock"},...}...];
251 $V and print localtime()." START\n";
254 vec($rfds,fileno($_->{"sock"}),1)=1 for values(%socks);
256 while (my($who_has,$arrayref)=each(%pending)) {
258 for my $pendingi (0..$#$arrayref) {
259 my $pending=$arrayref->[$pendingi-$deleted];
260 my $when=$pending->{"when"};
262 my $sock_hash=$socks{$pending->{"ifname"}};
263 my $msg_reply=arp_pack(
264 $pending->{"tell_hw"}, # dst_hw
265 $sock_hash->{"hw"}, # src_hw
266 $pending->{"tell"}, # dst
267 $pending->{"who_has"}, # src
270 send $sock_hash->{"sock"},$msg_reply,0,$pending->{"from_addr"}
271 or die "send(".$pending->{"ifname"}."): $!";
272 splice @$arrayref,$pendingi-$deleted,1;
274 $V and print localtime()." replied: type=REPLY,"
275 .join(",",map(("$_=".$pending->{$_}),qw(tell who_has tell_hw)))."\n";
278 $first=$when if !$first || $when<=$first;
281 delete $pending{$who_has} if !@$arrayref;
284 $first=$now if $first && $first<$now;
285 $V>=3 and print Data::Dumper->Dump([\%pending],['\%pending']);
286 my $got=select $rfds,undef(),undef(),(!$first ? undef() : $first-$now);
287 die "Invalid select(2): ".Dumper($got) if !defined $got || $got<0;
289 while (my($ifname,$hash)=each(%socks)) {
290 next if !vec($rfds,fileno($hash->{"sock"}),1);
291 $V>=2 and print localtime()." got packet: $ifname\n";
293 defined(my $from_addr=recv $hash->{"sock"},$msg,0x1000,0) or die "recv($ifname): $!";
294 next if !(my($tell,$who_has,$tell_hw,$type)=arp_unpack($msg));
295 $V and print localtime()." got: type=$type,tell=$tell,who_has=$who_has,tell_hw=$tell_hw\n";
296 if ($type eq "REQUEST") {
297 next if $tell eq $who_has; # do not reply to self-detection queries
298 next if $tell eq "0.0.0.0"; # self-detection by Red Hat 7.3
299 my $msg_reply=arp_pack(
300 "FF:FF:FF:FF:FF:FF", # dst_hw
301 $hash->{"hw"}, # src_hw
306 send $hash->{"sock"},$msg_reply,0,$from_addr or die "send($ifname): $!";
307 $V and print localtime()." probing: type=REQUEST,"
308 ."tell=".$hash->{"ip"}.",who_has=$who_has,tell_hw=".$hash->{"hw"}."\n";
309 push @{$pending{$who_has}},{
310 "when"=>now()+$opt_timeout,
315 "from_addr"=>$from_addr,
318 elsif ($type eq "REPLY") {
319 # Rename the fields a bit for REPLY
320 my($told,$who,$is_at_hw)=($who_has,$tell,$tell_hw);
321 for my $pending (@{$pending{$who}}) {
322 $V and print localtime()." discarded: "
323 .join(",",map(("$_=".$pending->{$_}),qw(tell who_has tell_hw)))."\n";
325 delete $pending{$who};