diff --git a/uftp/README b/uftp/README new file mode 100644 index 0000000000..edb69ef973 --- /dev/null +++ b/uftp/README @@ -0,0 +1,37 @@ +Implementing UFTP in xCAT + +When a node boots via xCAT in stateless mode, it first receives a kernel and +an initial ramdisk from the management server. Inside the initial ramdisk is +a script named xcatroot which is responsible for copying the node's root +filesystem image from the management server with wget, uncompressing it into +RAM, and switching to it. The URL of the root filesystem image is passed to +xcatroot via a kernel boot parameter. + +In order to support uftp, xcatroot needs to start uftpd, and then signal the +management server that it is ready to receive the image file. The management +server also needs to wait for some amount of time before starting the transfer, +since there could be some variability in the hardware boot process, and the +slowest node might not have started uftpd yet. In order to make sure the image +transferred correctly, the md5 checksum is checked. If the md5 check fails, or +the transfer times out, xcatroot falls back to wget. + +To implement this, three kernel boot parameters were added, which are: + +uftp +uftpdelay +md5sum + +To use UFTP, set uftp=true. The uftpdelay parameter is for specifying the +number of seconds to wait before the transfer starts, and defaults to 30 if +not specified. The md5sum parameter should be set to the md5 checksum of the +root filesystem image. These need to be set in the addkcmdline field in the +osimage table in xCAT, so that nodeset will pick them up. The packimage +command has been extended with some options to facilitate this: + + packimage [-s| --sum] set md5sum= + packimage [-u| --uftp] set uftp=true + packimage [-d| --delay= set uftpdelay= + +Note that if -s is omitted, and an md5sum exists in addkcmdline, the existing +entry is removed because it would no longer be valid. Similarly, if -u is +omitted and uftp=true exists in addkcmdline, it is removed. diff --git a/uftp/uftp-4.9.3-1.ppc64le.rpm b/uftp/uftp-4.9.3-1.ppc64le.rpm new file mode 100644 index 0000000000..fba5046c42 Binary files /dev/null and b/uftp/uftp-4.9.3-1.ppc64le.rpm differ diff --git a/uftp/uftp-4.9.3.tar.gz b/uftp/uftp-4.9.3.tar.gz new file mode 100644 index 0000000000..930de55a87 Binary files /dev/null and b/uftp/uftp-4.9.3.tar.gz differ diff --git a/uftp/uftp-debuginfo-4.9.3-1.ppc64le.rpm b/uftp/uftp-debuginfo-4.9.3-1.ppc64le.rpm new file mode 100644 index 0000000000..faf83c76bb Binary files /dev/null and b/uftp/uftp-debuginfo-4.9.3-1.ppc64le.rpm differ diff --git a/uftp/uftp-listener b/uftp/uftp-listener new file mode 100755 index 0000000000..8fde3e3260 --- /dev/null +++ b/uftp/uftp-listener @@ -0,0 +1,100 @@ +#!/bin/bash +LOGDIR=/var/log/xcat/ +LOGFILE=uftp_listener.log +mkdir -p $LOGDIR + +usage() { + echo "usage: uftp-listener [ -i interface ] [ -s sleep time ]" + exit 1 +} + +debug() { + [ "$DEBUG" ] && echo "$@" + echo "$@" >> $LOGDIR/$LOGFILE +} + +start_copy() { + sleep $SLEEP + debug "copying $ROOTIMG" + debug "$UFTP -I $INTERFACE -R 950000 -p $UFTPPORT -D /rootimg.cpio.gz $ROOTIMG" + $UFTP -I $INTERFACE -R 950000 -p $UFTPPORT -D /rootimg.cpio.gz $ROOTIMG +} + +while getopts ":i:s:hd" options +do + case ${options} in + i ) INTERFACE=${OPTARG};; + s ) SLEEP=${OPTARG};; + d ) DEBUG=true;; + h ) usage + exit 1;; + * ) usage + exit 1;; + esac +done + +# if INTERFACE is null, try to detect +if [ -z "$INTERFACE" ]; then + MAN_NET=$(/opt/xcat/bin/lsdef -t network management | grep net | sed 's/.*=//') + INTERFACE=$(netstat -nr | grep $MAN_NET | awk '{print $8}') +fi + +if [ -z "$INTERFACE" ]; then + debug "ERROR: detection of interface failed and none supplied with -i, aborting..." + exit 1 +fi + +ip link show $INTERFACE > /dev/null 2>&1 +RC=$? + +if [ "$RC" != 0 ]; then + debug "ERROR: invalid interface $INTERFACE" + exit 1 +fi + +# if no sleep time supplied, use the default of 30 seconds +if [ -z "$SLEEP" ]; then + SLEEP=30 +fi + +UFTP=$(which uftp) +if [ ! -x $UFTP ]; then + debug "ERROR: uftp binary not found, exiting!" + exit 1 +fi + +# make sure 1045/tcp is not already in use +nc -i 500ms -l -p 1045 2>&1 | grep "Address already in use" > /dev/null +RC=$? +if [ "$RC" = 0 ]; then + debug "ERROR: 1045/tcp is already in use, sleeping 10 seconds and exiting" + sleep 10 + exit 1 +fi + +debug "listening for transfer request on interface $INTERFACE 1045/tcp" +declare -A copyjobs +while true +do + nc -l -p 1045 -k | while read LINE; do + VALIDATED=$(echo $LINE | grep -E "/install/.*/rootimg(\.sfs|\.cpio\.gz|\.cpio\.xz|\.tar.gz|\.tar\.xz|-statelite\.gz)") + if [ -n "$VALIDATED" ]; then + ROOTIMG=$(echo $VALIDATED | sed 's/.*GET //' | sed 's+ HTTP/1.1.*++') + if $(echo $ROOTIMG | grep uftpport > /dev/null); then + UFTPPORT=$(echo $ROOTIMG | sed 's+.*uftpport=++') + ROOTIMG=$(echo $ROOTIMG | sed 's+/uftpport=.*++') + else + UFTPPORT=1044 + fi + if [ ! -f "$ROOTIMG" ]; then + debug "$ROOTIMG: file not found" + elif [ ! x${copyjobs[$ROOTIMG]} = x ] && jobs -lr | grep ${copyjobs[$ROOTIMG]} > /dev/null; then + : # if we get here, there is already a uftp copy job staged for this url, so do nothing + # debug "$ROOTIMG job detected" + else + start_copy $ROOTIMG & + copyjobs[$ROOTIMG]=$! + fi + fi + done +done diff --git a/uftp/uftp-listener.service b/uftp/uftp-listener.service new file mode 100644 index 0000000000..a30f7bf077 --- /dev/null +++ b/uftp/uftp-listener.service @@ -0,0 +1,12 @@ +[Unit] +Description=UFTP listener +Wants=basic.target +After=basic.target network.target + +[Service] +Type=simple +ExecStart=/bin/bash /opt/xcat/bin/uftp-listener +KillMode=control-group + +[Install] +WantedBy=multi-user.target diff --git a/uftp/uftp.spec b/uftp/uftp.spec new file mode 100644 index 0000000000..ecde3e2261 --- /dev/null +++ b/uftp/uftp.spec @@ -0,0 +1,41 @@ +Name: uftp +Version: 4.9.3 +Release: 1 +Summary: UFTP - Encrypted UDP based FTP with multicast + +Group: FTP Server +License: GPL +URL: http://uftp-multicast.sourceforge.net +Source0: http://sourceforge.net/projects/uftp-multicast/files/source-tar/uftp-4.9.3.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +BuildRequires: openssl-devel + +%description +UFTP is an encrypted multicast file transfer program, designed to securely, reliably, and efficiently transfer files to multiple receivers simultaneously. This is useful for distributing large files to a large number of receivers, and is especially useful for data distribution over a satellite link (with two way communication), where the inherent delay makes any TCP based communication highly inefficient. The multicast encryption scheme is based on TLS with extensions to allow multiple receivers to share a common key. UFTP also has the capability to communicate over disjoint networks separated by one or more firewalls (NAT traversal) and without full end-to-end multicast capability (multicast tunneling) through the use of a UFTP proxy server. These proxies also provide scalability by aggregating responses from a group of receivers. + +%prep +%setup -q + +%build +make %{?_smp_mflags} + +%install +rm -rf $RPM_BUILD_ROOT +make install DESTDIR=$RPM_BUILD_ROOT + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root,-) +/usr/bin/uftp +/usr/sbin/uftpd +/usr/sbin/uftpproxyd +/usr/bin/uftp_keymgt +/usr/share/man/man1/uftp.1.gz +/usr/share/man/man1/uftp_keymgt.1.gz +/usr/share/man/man1/uftpd.1.gz +/usr/share/man/man1/uftpproxyd.1.gz + +%changelog diff --git a/xCAT-server/lib/xcat/plugins/packimage.pm b/xCAT-server/lib/xcat/plugins/packimage.pm index fe08e6c2b5..de1f3368d3 100755 --- a/xCAT-server/lib/xcat/plugins/packimage.pm +++ b/xCAT-server/lib/xcat/plugins/packimage.pm @@ -11,6 +11,10 @@ -p Profile (compute,service) -a Architecture (ppc64,x86_64,etc) -m Method (default cpio) + -s Compute MD5 checksum of image + -u Use uftp for copying image + -t uftp delay in seconds (default is 30) + -d uftp destination port =cut @@ -32,6 +36,7 @@ use Cwd; use File::Temp; use File::Basename; use File::Path; +use xCAT::DBobjUtils; #use xCAT::Utils qw(genpassword); use xCAT::Utils; @@ -82,7 +87,7 @@ sub process_request { @ARGV = @{$args}; } if (scalar(@ARGV) == 0) { - $callback->({ info => ["Usage:\n packimage [-m| --method=cpio|tar] [-c| --compress=gzip|pigz|xz] [--nosyncfiles] \n packimage [-h| --help]\n packimage [-v| --version]"] }); + $callback->({ info => ["Usage:\n packimage [-m| --method=cpio|tar] [-c| --compress=gzip|pigz|xz] \n packimage [-h| --help]\n packimage [-v| --version]\n packimage [-s| --sum]\n packimage [-u| --uftp]\n packimage [-t| --delay=]\n packimage [-d ]"] }); return 0; } @@ -98,11 +103,19 @@ sub process_request { my $nosyncfiles; my $imagename; my $dotorrent; + my $domd5sum; + my $douftp; + my $uftpdelay; + my $default_uftpdelay=30; + my $uftpport; + my $addkcmdline; my $provmethod; my $envars; my $help; my $version; my $lock; + my $newaddkcmdline; + my $linuximagetab; GetOptions( "profile|p=s" => \$profile, @@ -111,6 +124,10 @@ sub process_request { "method|m=s" => \$method, "compress|c=s" => \$compress, "tracker=s" => \$dotorrent, + "sum|s" => \$domd5sum, + "uftp|u" => \$douftp, + "port|d=s" => \$uftpport, + "delay|t=s" => \$uftpdelay, 'nosyncfiles' => \$nosyncfiles, "help|h" => \$help, "version|v" => \$version @@ -125,7 +142,7 @@ sub process_request { return 0; } if ($help) { - $callback->({ info => ["Usage:\n packimage [-m| --method=cpio|tar] [-c| --compress=gzip|pigz|xz] [--nosyncfiles] \n packimage [-h| --help]\n packimage [-v| --version]"] }); + $callback->({ info => ["Usage:\n packimage [-m| --method=cpio|tar] [-c| --compress=gzip|pigz|xz] \n packimage [-h| --help]\n packimage [-v| --version]\n packimage [-s| --sum]\n packimage [-u| --uftp]\n packimage [-t| --delay=]\n packimage [-d ]"] }); return 0; } @@ -149,7 +166,7 @@ sub process_request { $callback->({ error => ["The osimage table cannot be opened."], errorcode => [1] }); return 1; } - my $linuximagetab = xCAT::Table->new('linuximage', -create => 1); + $linuximagetab = xCAT::Table->new('linuximage', -create => 1); unless ($linuximagetab) { $callback->({ error => ["The linuximage table cannot be opened."], errorcode => [1] }); return 1; @@ -159,7 +176,7 @@ sub process_request { $callback->({ error => ["Cannot find image \'$imagename\' from the osimage table."], errorcode => [1] }); return 1; } - (my $ref1) = $linuximagetab->getAttribs({ imagename => $imagename }, 'exlist', 'rootimgdir'); + (my $ref1) = $linuximagetab->getAttribs({ imagename => $imagename }, 'exlist', 'rootimgdir', 'addkcmdline'); unless ($ref1) { $callback->({ error => ["Cannot find $imagename from the linuximage table."], errorcode => [1] }); return 1; @@ -182,8 +199,9 @@ sub process_request { return 1; } - $exlistloc = $ref1->{'exlist'}; - $destdir = $ref1->{'rootimgdir'}; + $exlistloc = $ref1->{'exlist'}; + $destdir = $ref1->{'rootimgdir'}; + $addkcmdline = $ref1->{'addkcmdline'}; } else { $provmethod = "netboot"; unless ($osver) { @@ -551,6 +569,92 @@ sub process_request { system("rm -rf $xcat_packimg_tmpfile"); return 1; } + $newaddkcmdline = $addkcmdline; + if ($domd5sum) { # if true, pass md5sum of root image as a boot parameter + my $md5 = Digest::MD5->new; + open(my $iorootimg, '<', "$destdir/rootimg.$suffix"); + binmode($iorootimg); + $md5->addfile($iorootimg); + my $md5sum = $md5->hexdigest; + if ($addkcmdline =~ m/md5sum/) { + $newaddkcmdline =~ s/md5sum=\w{32}/md5sum=$md5sum/; + } else { + $newaddkcmdline = $newaddkcmdline . " md5sum=" . $md5sum; + } + $linuximagetab->setAttribs({imagename => $imagename}, {addkcmdline => $newaddkcmdline}); + } else { # -s was not given, so remove any pre-existing md5sum since it will be invalid + if ($newaddkcmdline =~ m/md5sum/) { + $newaddkcmdline =~ s/ md5sum=\w{32}//; + $newaddkcmdline =~ s/^md5sum=\w{32} //; + $newaddkcmdline =~ s/^md5sum=\w{32}//; + $linuximagetab->setAttribs({imagename => $imagename}, {addkcmdline => $newaddkcmdline}); + } + } + + if ($douftp) { # if true, pass uftp=true as a boot parameter + if (!($newaddkcmdline =~ m/uftp=true/)) { + $newaddkcmdline = $newaddkcmdline . " uftp=true"; + $linuximagetab->setAttribs({imagename => $imagename}, {addkcmdline => $newaddkcmdline}); + } + } else { # -u was not given, so remove any pre-existing uftp boot parameters + if ($newaddkcmdline =~ m/uftp=true/) { + $newaddkcmdline =~ s/ uftp=true//; + $newaddkcmdline =~ s/^uftp=true //; + $newaddkcmdline =~ s/^uftp=true//; + } + if ($newaddkcmdline =~ m/uftpdelay=\d*/) { + $newaddkcmdline =~ s/ uftpdelay=\d*//; + $newaddkcmdline =~ s/^uftpdelay=\d* //; + $newaddkcmdline =~ s/^uftpdelay=\d*//; + } + if ($newaddkcmdline =~ m/uftpport=\d*/) { + $newaddkcmdline =~ s/ uftpport=\d*//; + $newaddkcmdline =~ s/^uftpport=\d* //; + $newaddkcmdline =~ s/^uftpport=\d*//; + } + $linuximagetab->setAttribs({imagename => $imagename}, {addkcmdline => $newaddkcmdline}); + } + + if (($uftpdelay) && ($douftp)) { # if true, pass uftpdelay as a boot parameter + if ($newaddkcmdline =~ m/uftpdelay/) { # if there is a pre-existing uftpdelay, replace + $newaddkcmdline =~ s/uftpdelay=\d*/uftpdelay=$uftpdelay/; + } else { # otherwise, add supplied value + $newaddkcmdline = $newaddkcmdline . " uftpdelay=$uftpdelay"; + } + $linuximagetab->setAttribs({imagename => $imagename}, {addkcmdline => $newaddkcmdline}); + } elsif ($douftp) { # no uftpdelay was given, so use the default of $default_uftpdelay + if ($newaddkcmdline =~ m/uftpdelay/) { # if there is a pre-existing uftpdelay, replace + $newaddkcmdline =~ s/uftpdelay=\d*/uftpdelay=$default_uftpdelay/; + } else { # otherwise, add default value + $newaddkcmdline = $newaddkcmdline . " uftpdelay=$default_uftpdelay"; + } + $linuximagetab->setAttribs({imagename => $imagename}, {addkcmdline => $newaddkcmdline}); + } + + my $port_conflict; + if (($douftp) && ($uftpport)) { + $port_conflict = `lsdef -t osimage -i addkcmdline | grep $uftpport`; + if ($port_conflict) { + $callback->({ error => ["Warning: uftpport $uftpport is already in use by another image!"], errorcode => [0] }); + } + } else { + while (($douftp) && !($uftpport)) { # no uftpport was given, so use a random one + $uftpport = 1025 + int(rand(64510)); + $port_conflict = `lsdef -t osimage -i addkcmdline | grep $uftpport`; + if ($port_conflict) { # a conflict was found, so pick a different port + $uftpport = ""; + } + } + } + + if ($douftp) { # if true, pass uftpport as a boot parameter + if ($newaddkcmdline =~ m/uftpport/) { # if there is a pre-existing uftpport, replace + $newaddkcmdline =~ s/uftpport=\d*/uftpport=$uftpport/; + } else { # otherwise, add supplied value + $newaddkcmdline = $newaddkcmdline . " uftpport=$uftpport"; + } + $linuximagetab->setAttribs({imagename => $imagename}, {addkcmdline => $newaddkcmdline}); + } if ($method =~ /cpio/) { chmod 0644, "$destdir/rootimg.$suffix"; diff --git a/xCAT-server/share/xcat/netboot/rh/dracut_033/install.netboot b/xCAT-server/share/xcat/netboot/rh/dracut_033/install.netboot index 5154c4cffb..e1fe5e08fd 100755 --- a/xCAT-server/share/xcat/netboot/rh/dracut_033/install.netboot +++ b/xCAT-server/share/xcat/netboot/rh/dracut_033/install.netboot @@ -2,7 +2,7 @@ echo $drivers dracut_install wget tar cpio gzip modprobe touch echo cut wc xz dracut_install grep ifconfig hostname awk egrep grep dirname expr -dracut_install mount.nfs +dracut_install mount.nfs md5sum uftpd dracut_install parted mke2fs bc mkswap swapon chmod dracut_install ethtool inst "$moddir/xcat-updateflag" "/tmp/updateflag" diff --git a/xCAT-server/share/xcat/netboot/rh/dracut_033/xcatroot b/xCAT-server/share/xcat/netboot/rh/dracut_033/xcatroot index 12888ba79a..1b1a03d202 100755 --- a/xCAT-server/share/xcat/netboot/rh/dracut_033/xcatroot +++ b/xCAT-server/share/xcat/netboot/rh/dracut_033/xcatroot @@ -32,11 +32,76 @@ fi imgurl="$(getarg imgurl=)"; +MD5="$(getarg md5sum=)" +TRYUFTP="$(getarg uftp=)" +uftpdelay="$(getarg uftpdelay=)" +uftpport="$(getarg uftpport=)" if [ ! -z "$imgurl" ]; then if [ xhttp = x${imgurl%%:*} ]; then logger $SYSLOGHOST -t $log_label -p local4.info "Downloading rootfs image from $imgurl..." NFS=0 FILENAME=${imgurl##*/} + [ "$xcatdebugmode" > "0" ] && logger -t xcat -p debug "TRYUFTP=$TRYUFTP" + if [ "$TRYUFTP" = "true" ]; then + if [ ! -z "$uftpport" ]; then + uftpd -D / -q -p $uftpport + uftpd_rc=$? + else + uftpd -D / -q + uftpd_rc=$? + fi + if [ "$uftpd_rc" = 0 ]; then + [ "$xcatdebugmode" > "0" ] && logger -t xcat -p debug "started uftpd" + echo "started uftpd" 1>&2 + # send a request to the uftp listener on management node + uftpurl=$(echo $imgurl | sed "s/80/1045/") + if [ ! -z "$uftpport" ]; then + uftpurl=$(echo $uftpurl | sed "s+$+/uftpport=$uftpport+") + fi + [ "$xcatdebugmode" > "0" ] && logger -t xcat -p debug "sending uftp request to $uftpurl" + echo "sending uftp request to $uftpurl" 1>&2 + wget -t 1 -T 1 -q $uftpurl + # uftpdelay should match the delay the listener is using, + # but use 30 if this was not defined + [ -z "uftpdelay" ] && uftpdelay=30 + [ "$xcatdebugmode" > "0" ] && logger -t xcat -p debug "sleeping $uftpdelay seconds..." + echo "sleeping $uftpdelay seconds..." 1>&2 + sleep $uftpdelay + timeout=90 + # wait for transfer to complete, or time out + started=0 + while [ "$timeout" -gt 0 ] && ! grep "File transfer complete" /tmp/uftpd.log; do + if [ "$started" = 0 ] && grep "Name of file to receive" /tmp/uftpd.log; then + [ "$xcatdebugmode" > "0" ] && logger -t xcat -p debug "uftp transfer started" + echo "uftp transfer started" 1>&2 + started=1 + fi + ((timeout-=1)) + sleep 1 + done + if [ "$timeout" = 0 ]; then + # we timed out, so remove any partial file and fall back + [ "$xcatdebugmode" > "0" ] && logger -t xcat -p debug "uftp transfer timed out, falling back to wget..." + echo "uftp transfer timed out, falling back to wget..." 1>&2 + rm -f $FILENAME + else + [ "$xcatdebugmode" > "0" ] && logger -t xcat -p debug "uftp transfer complete" + echo "uftp transfer complete" 1>&2 + # check md5sum if one was supplied + if [ -z "$MD5" ]; then + echo "no md5sum supplied, continuing..." 1>&2 + [ "$xcatdebugmode" > "0" ] && logger -t xcat -p debug "no md5sum supplied, continuing..." + elif ! md5sum $FILENAME | grep $MD5; then + [ "$xcatdebugmode" > "0" ] && logger -t xcat -p debug "md5sum check failed, trying wget..." + echo "md5sum check failed, trying wget..." 1>&2 + rm -f $FILENAME + else + [ "$xcatdebugmode" > "0" ] && logger -t xcat -p debug "md5sum check succeeded, continuing..." + echo "md5sum check succeeded, continuing..." 1>&2 + fi + fi + fi + fi while [ ! -r "$FILENAME" ]; do [ "$xcatdebugmode" > "0" ] && logger -t $log_label -p local4.debug "Downloading $imgurl..." echo Getting $imgurl...