发送 IGMP 包的 Perl 脚本

因为 Fedora 上的 scapy (2.2.0)还不支持 IGMP,所以我用 Perl 写了一个发送 IGMP 的脚本。最初代码来自这里,原脚本只能发送 IGMP query,我添加了 IGMP leave 和一些命令行选项。用法如下:

% ./igmp.pl -q 192.168.10.2 224.0.0.22
% ./igmp.pl -l 224.8.8.8 192.168.10.2 224.0.0.22

具体代码如下:

[perl]

! /usr/bin/perl -w

Adapted from : http://code.google.com/p/perl-igmp-querier/

use strict;
use POSIX;
use Socket qw(inet_pton AF_INET SOCK_RAW);
use Getopt::Long qw(:config no_auto_abbrev);
my $leave;
my $leave_addr;
my $query = 1;
my $help;

sub forgepkt {

my $src_host = shift;
my $dst_host = shift;

my $zero_cksum = 0;
my $igmp_proto = 2;
my $igmp_type = $query? ‘11’: ‘17’; #11 query, 17 leave#
my $igmp_mrt = ‘64’;
my $igmp_pay = $query? “” : $leave_addr; # 0 for query
my $igmp_chk = 0;
my $igmp_len = 0;

my ($igmp_pseudo) = pack(‘H2H2va4’,$igmp_type,$igmp_mrt,$igmp_chk,$igmp_pay);
$igmp_chk = &checksum($igmp_pseudo);
$igmp_pseudo = pack(‘H2H2va4’,$igmp_type,$igmp_mrt,$igmp_chk,$igmp_pay);
$igmp_len = length($igmp_pseudo);

my $ip_ver = 4;
my $ip_len = 6;
my $ip_ver_len = $ip_ver . $ip_len;
my $ip_tos = 00;
my ($ip_tot_len) = $igmp_len + 20 + 4;
my $ip_frag_id = 11243;
my $ip_frag_flag = “010”;
my $ip_frag_oset = “0000000000000”;
my $ip_fl_fr = $ip_frag_flag . $ip_frag_oset;
my $ip_ttl = 1;
my $ip_opts = ‘94040000’; # router alert

my ($head) = pack(‘H2H2nnB16C2n’,
$ip_ver_len,$ip_tos,$ip_tot_len,$ip_frag_id,
$ip_fl_fr,$ip_ttl,$igmp_proto);
my ($addresses) = pack(‘a4a4’,$src_host,$dst_host);
my ($pkt) = pack(‘aaH8a*’,$head,$addresses,$ip_opts,$igmp_pseudo);

return $pkt;
}

sub checksum {
my ($msg) = @_;
my ($len_msg,$num_short,$short,$chk);
$len_msg = length($msg);
$num_short = $len_msg / 2;
$chk = 0;
foreach $short (unpack(“S$num_short”, $msg)) {
$chk += $short;
}
$chk += unpack(“C”, substr($msg, $len_msg - 1, 1)) if $len_msg % 2;
$chk = ($chk >> 16) + ($chk & 0xffff);
return(~(($chk >> 16) + $chk) & 0xffff);
}

sub help {
my ($exitcode) = @_;

    print < $leave,
'q|query'       => $query,
'h|help'        => $help,

) or help(0);

help(1) if ($#ARGV < 1);

if ($leave) {
$query = 0;
$leave_addr = inet_pton(AF_INET, $leave);
}

my $src = $ARGV[0];
my $dst = $ARGV[1]; # 224.0.0.22, igmp all-hosts

socket(RAW, AF_INET, SOCK_RAW, 255) || die $!;
setsockopt(RAW, 0, 1, 1);

my $src_host = (gethostbyname($src))[4];
my $dst_host = (gethostbyname($dst))[4];
my ($packet) = forgepkt($src_host,$dst_host);
my ($dest) = pack('Sna4x8', AF_INET, 0, $dst_host);

Send general query packet twice for reliability

send(RAW,$packet,0,$dest);
send(RAW,$packet,0,$dest);

[/perl]