aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2017-04-11 14:45:07 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2017-04-11 14:45:07 +0200
commit5e2eafd7c43545740efb0c3304ff68bba1f4d0a6 (patch)
tree4b50859a8febb0b04f5a7fff0c649b68c7f1d18f
parentae714a0b77b5a5eda977e56c43205fbfb8821ca8 (diff)
Implement TFTP server support in agent
-rw-r--r--bbot/agent10
-rw-r--r--bbot/agent.cxx108
-rw-r--r--bbot/buildfile1
-rw-r--r--bbot/machine4
-rw-r--r--bbot/machine.cxx77
-rw-r--r--bbot/tftp44
-rw-r--r--bbot/tftp.cxx127
-rw-r--r--bbot/utility12
-rw-r--r--tests/agent/testscript2
9 files changed, 352 insertions, 33 deletions
diff --git a/bbot/agent b/bbot/agent
index c333fe5..619fb36 100644
--- a/bbot/agent
+++ b/bbot/agent
@@ -5,6 +5,8 @@
#ifndef BBOT_AGENT
#define BBOT_AGENT
+#include <sys/types.h> // uid_t
+
#include <bbot/types>
#include <bbot/utility>
@@ -19,6 +21,14 @@ namespace bbot
extern string tc_name; // Toolchain name.
extern string tc_num; // Toolchain number.
extern string tc_id; // Toolchain id.
+
+ extern uid_t uid; // Our effective user id.
+ extern string uname; // Our effective user name.
+
+ // Return the IPv4 address of an interface.
+ //
+ string
+ iface_addr (const string&);
}
#endif // BBOT_AGENT
diff --git a/bbot/agent.cxx b/bbot/agent.cxx
index e608af2..2489ce4 100644
--- a/bbot/agent.cxx
+++ b/bbot/agent.cxx
@@ -4,9 +4,16 @@
#include <bbot/agent>
+#include <pwd.h> // getpwuid()
#include <limits.h> // PATH_MAX
#include <signal.h> // signal()
-#include <unistd.h> // sleep(), realink()
+#include <unistd.h> // sleep(), realink(), getuid()
+
+#include <net/if.h> // ifreq
+#include <netinet/in.h> // sockaddr_in
+#include <arpa/inet.h> // inet_ntop()
+#include <sys/ioctl.h>
+#include <sys/socket.h>
#include <iostream>
@@ -19,23 +26,57 @@
#include <bbot/utility>
#include <bbot/diagnostics>
+#include <bbot/tftp>
#include <bbot/machine>
#include <bbot/bootstrap-manifest>
+using namespace std;
+using namespace butl;
+using namespace bbot;
+
namespace bbot
{
agent_options ops;
- const string bs_prot ("1"); // Bootstrap protocol version.
+ const string bs_prot ("1");
- string tc_name; // Toolchain name.
- string tc_num; // Toolchain number.
- string tc_id; // Toolchain id.
-}
+ string tc_name;
+ string tc_num;
+ string tc_id;
-using namespace std;
-using namespace butl;
-using namespace bbot;
+ uid_t uid;
+ string uname;
+
+ // Note: Linux-specific implementation.
+ //
+ string
+ iface_addr (const string& i)
+ {
+ if (i.size () >= IFNAMSIZ)
+ throw invalid_argument ("interface nama too long");
+
+ auto_fd fd (socket (AF_INET, SOCK_DGRAM, 0));
+
+ if (fd.get () == -1)
+ throw_system_error (errno);
+
+ ifreq ifr;
+ ifr.ifr_addr.sa_family = AF_INET;
+ strcpy (ifr.ifr_name, i.c_str ());
+
+ if (ioctl (fd.get (), SIOCGIFADDR, &ifr) == -1)
+ throw_system_error (errno);
+
+ char buf[3 * 4 + 3 + 1]; // IPv4 address.
+ if (inet_ntop (AF_INET,
+ &reinterpret_cast<sockaddr_in*> (&ifr.ifr_addr)->sin_addr,
+ buf,
+ sizeof (buf)) == nullptr)
+ throw_system_error (errno);
+
+ return buf;
+ }
+}
// The btrfs tool likes to print informational messages, like "Created
// snapshot such and such". Luckily, it writes them to stdout while proper
@@ -65,6 +106,8 @@ bootstrap_machine (const dir_path& md,
const machine_manifest& mm,
optional<bootstrapped_machine_manifest> obmm)
{
+ tracer trace ("bootstrap_machine");
+
bootstrapped_machine_manifest r {
mm,
toolchain_manifest {tc_id},
@@ -83,15 +126,51 @@ bootstrap_machine (const dir_path& md,
r.machine.mac = "de:ad:be:ef:de:ad";
}
else
+ try
{
+ string br ("br1"); // Use private bridge for now.
+
+ // Start the TFTP server (server chroot is /build/tftp). Map:
+ //
+ // GET requests to /build/tftp/toolchain/<name>/*
+ // PUT requests to /build/tftp/bootstrap/<name>/*
+ //
+ auto_rmdir arm (dir_path ("/build/tftp/bootstrap/" + tc_name));
+ try_mkdir_p (arm.path ());
+
+ tftp_server tftpd ("Gr ^/?(.+)$ /toolchain/" + tc_name + "/\\1\n" +
+ "Pr ^/?(.+)$ /bootstrap/" + tc_name + "/\\1\n");
+
+ l2 ([&]{trace << "tftp server on port " << tftpd.port ();});
+
+ // Start the machine.
+ //
unique_ptr<machine> m (
start_machine (md,
mm,
- obmm ? obmm->machine.mac : nullopt));
+ obmm ? obmm->machine.mac : nullopt,
+ br,
+ tftpd.port ()));
r.machine.mac = m->mac;
- sleep (10);
+ // The first request should be the toolchain download. Wait for up to 60
+ // seconds for that to arrive. In a sense we use it as an indication that
+ // the machine has booted and the bootstrap process has started.
+ //
+ size_t timeout (60);
+ if (tftpd.serve (timeout))
+ {
+ l2 ([&]{trace << "received first request in " << 60 - timeout << "s";});
+ }
+ else
+ {
+ // @@ What should be do here? Non-fatal? Mark the machine as failed?
+ //
+ error << "bootstrap timeout during first request for machine " << md;
+ m->forcedown ();
+ throw failed ();
+ }
if (!m->shutdown ())
{
@@ -100,6 +179,10 @@ bootstrap_machine (const dir_path& md,
throw failed ();
}
}
+ catch (const system_error& e)
+ {
+ fail << "tftp server error: " << e;
+ }
serialize_manifest (r, md / "manifest", "bootstrapped machine");
return r;
@@ -389,6 +472,9 @@ try
verb = ops.verbose ();
+ uid = getuid ();
+ uname = getpwuid (uid)->pw_name;
+
if (ops.systemd_daemon ())
{
// Map to systemd severity prefixes (see sd-daemon(3) for details). Note
diff --git a/bbot/buildfile b/bbot/buildfile
index b43bc63..7e35a29 100644
--- a/bbot/buildfile
+++ b/bbot/buildfile
@@ -30,6 +30,7 @@ if ($cxx.target.class == "linux")
{hxx cxx}{ diagnostics } \
{hxx cxx}{ machine-manifest } \
{hxx cxx}{ machine } \
+ {hxx cxx}{ tftp } \
{hxx }{ types } \
{hxx cxx}{ types-parsers } \
{hxx txx cxx}{ utility } \
diff --git a/bbot/machine b/bbot/machine
index 5ab8c15..ecdce22 100644
--- a/bbot/machine
+++ b/bbot/machine
@@ -44,7 +44,9 @@ namespace bbot
unique_ptr<machine>
start_machine (const dir_path&,
const machine_manifest&,
- const optional<string>& mac);
+ const optional<string>& mac,
+ const string& br_iface,
+ uint16_t tftp_port);
}
#endif // BBOT_MACHINE
diff --git a/bbot/machine.cxx b/bbot/machine.cxx
index e8f40b3..6677de5 100644
--- a/bbot/machine.cxx
+++ b/bbot/machine.cxx
@@ -4,12 +4,11 @@
#include <bbot/machine>
+#include <unistd.h> // sleep()
+
#include <sys/un.h> // sockaddr_un
#include <sys/socket.h>
-#include <unistd.h> // getuid()
-#include <sys/types.h> // getuid()
-
#include <cstdio> // snprintf()
#include <cstring> // strcpy()
@@ -21,32 +20,58 @@ using namespace butl;
namespace bbot
{
+ // Forward TFTP requests (UDP/69) coming from the machine to the specified
+ // port.
+ //
+ // This allows the machine to connect to any "unknown" IP (e.g., link-local
+ // 196.254.111.222) port 69 and end up being redirected to out TFTP server.
+ //
+ static void
+ iptables (tracer& t,
+ const char* a,
+ const string& tap,
+ const string& br,
+ uint16_t port)
+ {
+ run (t,
+ "sudo", "iptables",
+ "-t", "nat",
+ a, "PREROUTING",
+ "-p", "udp",
+ "-m", "udp",
+ "-m", "physdev",
+ "-i", br,
+ "--physdev-in", tap,
+ "--dport", 69,
+ "-j", "DNAT",
+ "--to-destination", iface_addr (br) + ':' + to_string (port));
+ }
+
static string
- create_tap ()
+ create_tap (const string& br, uint16_t port)
{
tracer trace ("create_tap");
- string b ("br1"); // Use private bridge for now.
string t ("tap" + tc_num);
- auto uid (getuid ());
-
// First try to delete it in case there is one from a previous run.
//
run_exit (trace, "sudo", "ip", "tuntap", "delete", t, "mode", "tap");
run (trace, "sudo", "ip", "tuntap", "add", t, "mode", "tap", "user", uid);
run (trace, "sudo", "ip", "link", "set", t, "up");
- //sleep (1);
- run (trace, "sudo", "ip", "link", "set", t, "master", b);
+ run (trace, "sudo", "ip", "link", "set", t, "master", br);
+
+ iptables (trace, "-A", t, br, port); // Add.
return t;
}
static void
- destroy_tap (const string& t)
+ destroy_tap (const string& t, const string& br, uint16_t port)
{
- tracer trace ("create_tap");
+ tracer trace ("destroy_tap");
+ iptables (trace, "-D", t, br, port); // Delete.
run (trace, "sudo", "ip", "tuntap", "delete", t, "mode", "tap");
}
@@ -74,7 +99,9 @@ namespace bbot
public:
kvm_machine (const dir_path&,
const machine_manifest&,
- const optional<string>& mac);
+ const optional<string>& mac,
+ const string& br_iface,
+ uint16_t tftp_port);
virtual bool
shutdown () override;
@@ -91,7 +118,11 @@ namespace bbot
private:
path kvm; // Hypervisor binary.
- string tap; // Tap network interface.
+
+ string br; // Bridge network interface.
+ string tap; // Tap network interface.
+ uint16_t port; // TFTP port.
+
path monitor; // QEMU monitor UNIX socket.
process proc;
};
@@ -99,12 +130,16 @@ namespace bbot
kvm_machine::
kvm_machine (const dir_path& md,
const machine_manifest& mm,
- const optional<string>& omac)
+ const optional<string>& omac,
+ const string& br,
+ uint16_t port)
: machine (mm.mac ? *mm.mac : // Fixed mac from machine manifest.
omac ? *omac : // Generated mac from previous bootstrap.
generate_mac ()),
kvm ("kvm"),
- tap (create_tap ()),
+ br (br),
+ tap (create_tap (br, port)),
+ port (port),
monitor ("/tmp/" + tc_name + "-monitor")
{
tracer trace ("kvm_machine");
@@ -203,7 +238,7 @@ namespace bbot
{
run_io_finish (trace, proc, kvm);
- destroy_tap (tap);
+ destroy_tap (tap, br, port);
try_rmfile (monitor, true); // QEMU doesn't seem to remove it.
}
@@ -281,12 +316,16 @@ namespace bbot
unique_ptr<machine>
start_machine (const dir_path& md,
const machine_manifest& mm,
- const optional<string>& mac)
+ const optional<string>& mac,
+ const string& br_iface,
+ uint16_t tftp_port)
{
switch (mm.type)
{
- case machine_type::kvm: return make_unique<kvm_machine> (md, mm, mac);
- case machine_type::nspawn: assert (false); //@@ TODO
+ case machine_type::kvm:
+ return make_unique<kvm_machine> (md, mm, mac, br_iface, tftp_port);
+ case machine_type::nspawn:
+ assert (false); //@@ TODO
}
return nullptr;
diff --git a/bbot/tftp b/bbot/tftp
new file mode 100644
index 0000000..cc39419
--- /dev/null
+++ b/bbot/tftp
@@ -0,0 +1,44 @@
+// file : bbot/tftp -*- C++ -*-
+// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BBOT_TFTP
+#define BBOT_TFTP
+
+#include <bbot/types>
+#include <bbot/utility>
+
+namespace bbot
+{
+ // A TFTP server "wrapper" over tftpd-hpa.
+ //
+ // In a nutshell, we are pretending to be inetd and when a request arrives,
+ // spawn tftpd-hpa to handle it.
+ //
+ class tftp_server
+ {
+ public:
+ // The map argument specifies the path mapping rules, one per line (see
+ // the tftpd-hpa --map-file|-m option for details).
+ //
+ tftp_server (const string& map);
+
+ // Return the assigned port.
+ //
+ uint16_t
+ port () const;
+
+ // Wait for a TFTP request for up to the specified number of seconds. If
+ // a request was served, update the timeout value and return true. Retain
+ // the original timeout value and return false otherwise.
+ //
+ bool
+ serve (size_t& seconds);
+
+ private:
+ auto_fd fd_;
+ auto_rmfile map_;
+ };
+}
+
+#endif // BBOT_TFTP
diff --git a/bbot/tftp.cxx b/bbot/tftp.cxx
new file mode 100644
index 0000000..a7398be
--- /dev/null
+++ b/bbot/tftp.cxx
@@ -0,0 +1,127 @@
+// file : bbot/tftp.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <bbot/tftp>
+
+#include <arpa/inet.h> // htonl()
+#include <netinet/in.h> // sockaddr_in
+#include <sys/socket.h>
+#include <sys/select.h>
+
+#include <cstring> // memset()
+
+#include <bbot/agent>
+
+using namespace std;
+using namespace butl;
+
+namespace bbot
+{
+ tftp_server::
+ tftp_server (const string& map)
+ {
+ int fd (socket (AF_INET, SOCK_DGRAM, 0));
+
+ if (fd == -1)
+ throw_system_error (errno);
+
+ fd_.reset (fd);
+
+ // Bind to ephemeral port.
+ //
+ sockaddr_in addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl (INADDR_ANY);
+ addr.sin_port = htons (0);
+
+ if (bind (fd,
+ reinterpret_cast<sockaddr*> (&addr),
+ sizeof (sockaddr_in)) == -1)
+ throw_system_error (errno);
+
+ // Create the map file.
+ //
+ map_ = auto_rmfile (path::temp_path ("bbot-agent-tftp-map"));
+ ofdstream ofs (map_.path ());
+ ofs << map << endl;
+ ofs.close ();
+ }
+
+ uint16_t tftp_server::
+ port () const
+ {
+ sockaddr_in addr;
+ socklen_t size (sizeof (addr));
+
+ if (getsockname (fd_.get (),
+ reinterpret_cast<sockaddr*> (&addr),
+ &size) == -1)
+ throw_system_error (errno);
+
+ assert (size == sizeof (addr));
+ return ntohs (addr.sin_port);
+ }
+
+ bool tftp_server::
+ serve (size_t& sec)
+ {
+ tracer trace ("tftp_server::serve");
+
+ int fd (fd_.get ());
+
+ // Note: Linux updates the timeout value which we rely upon.
+ //
+ timeval timeout {static_cast<long> (sec), 0};
+
+ fd_set rd;
+ FD_ZERO (&rd);
+
+ for (;;)
+ {
+ FD_SET (fd, &rd);
+
+ int r (select (fd + 1, &rd, nullptr, nullptr, &timeout));
+
+ if (r == -1)
+ {
+ if (errno == EINTR)
+ continue;
+
+ throw_system_error (errno);
+ }
+ else if (r == 0) // Timeout.
+ return false;
+
+ if (FD_ISSET (fd, &rd))
+ {
+ text << "connection";
+
+ // The inetd "protocol" is to pass the socket as stdin/stdout file
+ // descriptors.
+ //
+ // Notes/issues:
+ //
+ // 1. Writes diagnostics to syslog.
+ //
+ run_io (trace,
+ fddup (fd),
+ fddup (fd),
+ 2,
+ "sudo", // Required for --secure (chroot).
+ "/usr/sbin/in.tftpd", // Standard installation location.
+ "--timeout", 1, // Wait for more requests.
+ "--permissive", // Use inherited umask.
+ "--create", // Allow creating new files (PUT).
+ "--map-file", map_.path (), // Path remapping rules.
+ "--user", uname, // Run as our effective user.
+ "--secure", // Chroot to data directory.
+ "/build/tftp/");
+
+ sec = static_cast<size_t> (timeout.tv_sec);
+ return true;
+ }
+ }
+ }
+}
diff --git a/bbot/utility b/bbot/utility
index 5bd4821..4abb5a2 100644
--- a/bbot/utility
+++ b/bbot/utility
@@ -14,7 +14,8 @@
#include <butl/ft/lang>
#include <butl/process>
-#include <butl/utility> // casecmp(), reverse_iterate(), etc
+#include <butl/utility> // casecmp(), reverse_iterate(), etc
+#include <butl/fdstream>
#include <butl/filesystem>
#include <bbot/types>
@@ -40,6 +41,15 @@ namespace bbot
using butl::exception_guard;
using butl::make_exception_guard;
+ // <butl/fdstream>
+ //
+ using butl::auto_fd;
+
+ // <butl/filesystem>
+ //
+ using butl::auto_rmdir;
+ using butl::auto_rmfile;
+
// Random number generator (currently not MT-safe and limited to RAND_MAX).
//
size_t
diff --git a/tests/agent/testscript b/tests/agent/testscript
index 55f9d7f..4671950 100644
--- a/tests/agent/testscript
+++ b/tests/agent/testscript
@@ -133,6 +133,6 @@ rm = $src_base/btrfs-rmdir /build/machines
EOO
EOE
- -$rm
+ #-$rm
}
#\