blob: cdd2788d5e3a187d288d7b4b2bae8ca184176096 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/bringup/bin/netsvc/netsvc.h"
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/cpp/task.h>
#include <lib/fdio/namespace.h>
#include <lib/fit/defer.h>
#include <lib/netboot/netboot.h>
#include <lib/zx/clock.h>
#include <unistd.h>
#include <zircon/syscalls.h>
#include <zircon/time.h>
#include <algorithm>
#include <climits>
#include <cstdio>
#include <cstring>
#include <string>
#include "src/bringup/bin/netsvc/args.h"
#include "src/bringup/bin/netsvc/debuglog.h"
#include "src/bringup/bin/netsvc/inet6.h"
#include "src/bringup/bin/netsvc/netboot.h"
#include "src/bringup/bin/netsvc/netifc.h"
#include "src/bringup/bin/netsvc/tftp.h"
#include "src/sys/lib/stdout-to-debuglog/cpp/stdout-to-debuglog.h"
#ifndef ENABLE_SLAAC
// SLAAC RA's are disabled by default as they intefere with tests in environments where there are
// many devices on the same LAN.
#define ENABLE_SLAAC 0
#endif
namespace {
bool g_netbootloader = false;
// When false (default), will only respond to a limited number of commands.
// Currently, NETBOOT_COMMAND_QUERY (to support netls and netaddr), as well as responding to
// ICMP as usual.
bool g_all_features = false;
bool g_log_packets = false;
char g_nodename[HOST_NAME_MAX];
} // namespace
bool netbootloader() { return g_netbootloader; }
bool all_features() { return g_all_features; }
const char* nodename() { return g_nodename; }
void udp6_recv(async_dispatcher_t* dispatcher, void* data, size_t len, const ip6_addr_t* daddr,
uint16_t dport, const ip6_addr_t* saddr, uint16_t sport) {
bool mcast = (memcmp(daddr, &ip6_ll_all_nodes, sizeof(ip6_addr_t)) == 0);
if (!all_features()) {
if (dport != NETBOOT_PORT_SERVER) {
// Only some netboot commands allowed in limited mode.
return;
}
}
switch (dport) {
case NETBOOT_PORT_SERVER:
netboot_recv(data, len, mcast, daddr, dport, saddr, sport);
break;
case NETBOOT_DEBUGLOG_PORT_ACK:
debuglog_recv(dispatcher, data, len, mcast);
break;
case NETBOOT_PORT_TFTP_INCOMING:
case NETBOOT_PORT_TFTP_OUTGOING:
tftp_recv(dispatcher, data, len, daddr, dport, saddr, sport);
break;
}
}
void netifc_recv(async_dispatcher_t* dispatcher, void* data, size_t len) {
if (g_log_packets) {
printf("netsvc: received %ld bytes: ", len);
// Only print enough of a packet to help diagnose issues, we don't need the
// full packet. The number was picked because it's the exact full length of
// a neighbor solicitation accounting for Ethernet, IPv6, and ICMPv6 headers
// plus the NS payload. It is also enough to fully observe a UDP packet's
// headers, which is most of the transport-layer traffic.
size_t print_len = std::min(len, static_cast<size_t>(86));
for (const uint8_t& b : cpp20::span(reinterpret_cast<const uint8_t*>(data), print_len)) {
printf("%02x", b);
}
if (print_len != len) {
printf("...");
}
printf("\n");
}
eth_recv(dispatcher, data, len);
}
constexpr char zedboot_banner[] =
" _ _ _ \n"
" | | | | | \n"
" _______ __| | |__ ___ ___ | |_ \n"
" |_ / _ \\/ _` | '_ \\ / _ \\ / _ \\| __|\n"
" / / __/ (_| | |_) | (_) | (_) | |_ \n"
" /___\\___|\\__,_|_.__/ \\___/ \\___/ \\__|\n"
" \n"
"\n";
int main(int argc, char** argv) {
if (zx_status_t status = StdoutToDebuglog::Init(); status != ZX_OK) {
// TODO(https://fxbug.dev/42073486): Remove this once the elf runner no longer
// fools libc into block-buffering stdout.
setlinebuf(stdout);
printf("Failed to redirect stdout to debuglog, assuming test environment and continuing: %s\n",
zx_status_get_string(status));
}
NetsvcArgs args;
{
fdio_flat_namespace_t* ns;
if (zx_status_t status = fdio_ns_export_root(&ns); status != ZX_OK) {
printf("netsvc: FATAL: fdio_ns_export_root() = %s\n", zx_status_get_string(status));
return -1;
}
auto free = fit::defer([ns]() { fdio_ns_free_flat_ns(ns); });
fidl::ClientEnd<fuchsia_io::Directory> svc_root;
for (size_t i = 0; i < ns->count; ++i) {
if (std::string_view{ns->path[i]} == "/svc") {
svc_root = decltype(svc_root){zx::channel{std::exchange(ns->handle[i], ZX_HANDLE_INVALID)}};
break;
}
}
if (!svc_root.is_valid()) {
printf("netsvc: FATAL: did not find /svc in namespace\n");
return -1;
}
const char* error;
if (ParseArgs(argc, argv, svc_root, &error, &args) < 0) {
printf("netsvc: fatal error: %s\n", error);
return -1;
};
}
if (args.disable) {
printf("netsvc: Disabled. Exiting.\n");
return 0;
}
bool print_nodename_and_exit = args.print_nodename_and_exit;
bool should_advertise = args.advertise;
g_netbootloader = args.netboot;
g_all_features = args.all_features;
g_log_packets = args.log_packets;
const std::string& interface = args.interface;
if (g_netbootloader && !g_all_features) {
printf("netsvc: fatal: --netboot requires --all-features\n");
return -1;
}
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
if (g_all_features) {
if (zx_status_t status = debuglog_init(loop.dispatcher()); status != ZX_OK) {
printf("netsvc: fatal: error initializing debuglog: %s\n", zx_status_get_string(status));
return -1;
}
}
printf("netsvc: running in %s mode\n", g_all_features ? "full" : "limited");
if (gethostname(g_nodename, sizeof(g_nodename)) != 0) {
printf("netsvc: gethostname failed: %s\n", strerror(errno));
return -1;
}
printf("netsvc: nodename='%s'\n", g_nodename);
if (print_nodename_and_exit) {
return 0;
}
if (!interface.empty()) {
printf("netsvc: looking for interface %s\n", interface.c_str());
}
if (!should_advertise) {
printf("netsvc: will not advertise\n");
}
if (g_netbootloader) {
printf("%szedboot: version: %s\n\n", zedboot_banner, NETBOOT_BOOTLOADER_VERSION);
}
for (;;) {
if (zx::result status = netifc_open(loop.dispatcher(), interface,
[&loop](zx_status_t error) {
printf("netsvc: interface error: %s\n",
zx_status_get_string(error));
loop.Quit();
});
status.is_error()) {
printf("netsvc: fatal: failed to open interface: %s\n", status.status_string());
return -1;
}
printf("netsvc: start\n");
async::Task advertise_task(
[](async_dispatcher_t* dispatcher, async::Task* task, zx_status_t status) {
if (status == ZX_ERR_CANCELED) {
return;
}
ZX_ASSERT_MSG(status == ZX_OK, "advertise task dispatch failed %s",
zx_status_get_string(status));
#if ENABLE_SLAAC
send_router_advertisement();
#endif
netboot_advertise(g_nodename);
status = task->PostDelayed(dispatcher, zx::sec(1));
ZX_ASSERT_MSG(status == ZX_OK, "failed to post advertise task %s",
zx_status_get_string(status));
});
if (g_netbootloader && should_advertise) {
if (zx_status_t status = advertise_task.Post(loop.dispatcher()); status != ZX_OK) {
printf("netsvc: fatal: failed to post advertise task: %s\n", zx_status_get_string(status));
return -1;
}
}
loop.Run();
if (zx_status_t status = advertise_task.Cancel();
status != ZX_OK && status != ZX_ERR_NOT_FOUND) {
printf("netsvc: fatal: failed to cancel advertise task: %s\n", zx_status_get_string(status));
return -1;
}
netifc_close();
// Something went wrong bringing up the interface, reset the loop and go
// again in a bit.
{
zx_status_t status = loop.ResetQuit();
ZX_ASSERT_MSG(status == ZX_OK, "failed to reset loop: %s", zx_status_get_string(status));
printf("netsvc: waiting before retrying...\n");
sleep(1);
}
}
}