#!/usr/bin/perl use Socket; use Sys::Hostname; @ARGV or die "usage: server.pl [my address]\n"; # --- create stuff --- # the ADVERTISE socket is used to periodically send a UDP packet to the DS # which identify the file offered for transfer. The DS will listen for # those advertisments on port 8888. # the LISTEN socket is used to receive TCP connections over which the file # will be transferred. # by design, we want ADVERTISE and LISTEN to use the same port so that the # DS just have to ring back on port N to get the file offered in adv N. # ports 8081..8099 are the only ones being used for transfers. $port=8081; socket(LISTEN, PF_INET, SOCK_STREAM, getprotobyname('tcp')) or die 'socket(t)'; socket(ADVERTISE,PF_INET, SOCK_DGRAM, getprotobyname('udp')) or die 'socket(u)'; # you may omit and in the command line if you # provide them through DS_ADDR and THIS_IPADDR respectively. $dsaddress = defined $ARGV[1]?$ARGV[1]:$ENV{DS_IPADDR}; $myaddr=defined $ARGV[2]?$ARGV[2]:(exists $ENV{THIS_IPADDR}?$ENV{THIS_IPADDR}:gethostbyname(hostname())); print STDERR "advertise to $dsaddress:8888, transfer on $myaddr:$port\n"; $la = sockaddr_in($port,inet_aton($myaddr)); # --- bind them --- # both sockets need to be bound so that source port of advertisments is $port # and that we listen for TCP connections on the same $port. while (!bind(LISTEN, $la) || !bind(ADVERTISE,$la)) { $port++; print STDERR "retry with $port\n"; $la = sockaddr_in($port,inet_aton($myaddr)); die "cannot bind" if $port>8100; } listen LISTEN, 2; $da = sockaddr_in(8888,inet_aton($dsaddress)); print STDERR "bound and listening\n"; # --- file name --- # we are only allowed 32 bytes per advertisement message, including IP:port # information that is displayed on the DS. We make sure that only the filename # (and not the complete path) is advertised and we shorten it (keeping last # characters) so that it fit the 32-bytes constraint. $fname=$ARGV[0]; $fname=~ s:.*/([^/]+)$:$1:; $fname=~ s:\.gz$::; $msg="$myaddr:$port:"; $msg.=substr($fname,-(30-length($msg)))."\n"; # --- the loop --- while(1) { close TRANSFER; print STDERR "advertise $msg on 8888.\n"; while (1) { # -- advertise, then check for a incoming transmission request -- send ADVERTISE,$msg,0,$da; sleep 1; $rin=''; vec($rin,fileno(LISTEN),1)=1; select ($rout=$rin,undef,undef,0.25); last if vec($rout,fileno(LISTEN),1); } # -- if there's a pending connection, we end up here. ($remport, $remaddr) = sockaddr_in(accept(TRANSFER,LISTEN)); print STDERR "connection received from ".inet_ntoa($remaddr)."\n"; # -- we want to ensure that only the DS can request the file. # this is not public offer. next if inet_ntoa($remaddr) ne $dsaddress; print STDERR "granted for file transfer.\n"; my $buf=''; my $tot=0; # -- handle the special case where the file happens to be gzip'd. # if it is, gunzip it on the fly. if ($ARGV[0]=~ /\.gz$/) { open DATA,"gunzip -c $ARGV[0]|" or die "couldn't read file $ARGV[0] for transfer.\n"; } else { open DATA,"<$ARGV[0]" or die "couldn't read file $ARGV[0] for transfer.\n"; } binmode DATA; # -- okay let's just beam data out until the file ends. while (!eof DATA) { my $n=read DATA,$buf,4096; $tot+=syswrite TRANSFER,$buf,$n if $n>0; } print STDERR "$tot bytes transferred.\n"; close DATA; close TRANSFER; # -- all done. re-enter the major loop, sending your advertisement, # and waiting for more connections. # N.B. we do not support multiple transfer at the same time, and it # would make no sense, since only the DS can download (we received # its IP address) and that it won't support parallel download of # multiple files. } exit 0;