From 85ac78b173525f650cb1f934378fe9ed13f10310 Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Tue, 2 Aug 2022 00:07:28 +0000 Subject: [PATCH 1/2] Add the dut command from linuxboot It is much easier to bring dut into cpu than have 2 repos. Further, dut is not a combined cpu and cpud; it is useful to have this illustrative use of the cpu package. Also, dut is changed so that it will be able to work with different kinds of networks, although vsock support will likely require special handling. Signed-off-by: Ronald G. Minnich --- cmds/dut/doc.go | 66 +++++++++++++++ cmds/dut/dut.go | 189 +++++++++++++++++++++++++++++++++++++++++++ cmds/dut/dut_test.go | 52 ++++++++++++ cmds/dut/rpc.go | 80 ++++++++++++++++++ cmds/dut/serve.go | 66 +++++++++++++++ cmds/dut/uinit.go | 60 ++++++++++++++ dut/doc.go | 66 +++++++++++++++ dut/dut.go | 189 +++++++++++++++++++++++++++++++++++++++++++ dut/dut_test.go | 51 ++++++++++++ dut/rpc.go | 79 ++++++++++++++++++ dut/serve.go | 66 +++++++++++++++ dut/uinit.go | 62 ++++++++++++++ go.mod | 2 + go.sum | 1 + 14 files changed, 1029 insertions(+) create mode 100644 cmds/dut/doc.go create mode 100644 cmds/dut/dut.go create mode 100644 cmds/dut/dut_test.go create mode 100644 cmds/dut/rpc.go create mode 100644 cmds/dut/serve.go create mode 100644 cmds/dut/uinit.go create mode 100644 dut/doc.go create mode 100644 dut/dut.go create mode 100644 dut/dut_test.go create mode 100644 dut/rpc.go create mode 100644 dut/serve.go create mode 100644 dut/uinit.go diff --git a/cmds/dut/doc.go b/cmds/dut/doc.go new file mode 100644 index 00000000..eb238c15 --- /dev/null +++ b/cmds/dut/doc.go @@ -0,0 +1,66 @@ +// dut manages Devices Under Test (a.k.a. DUT) from a host. +// A primary goal is allowing multiple hosts with any architecture to connect. +// +// This program was designed to be used in u-root images, as the uinit, +// or in other initramfs systems. It can not function as a standalone +// init: it assumes network is set up, for example. +// +// In this document, dut refers to this program, and DUT refers to +// Devices Under Test. Hopefully this is not too confusing, but it is +// convenient. Also, please note: DUT is plural (Devices). We don't need +// to say DUTs -- at least one is assumed. +// +// The same dut binary runs on host and DUT, in either device mode (i.e. +// on the DUT), or in some host-specific mode. The mode is chosen by +// the first non-flag argument. If there are flags specific to that mode, +// they follow that argument. +// E.g., when uinit is run on the host and we want it to enable cpu daemons +// on the DUT, we run it as follows: +// dut cpu -key ... +// the -key switch is only valid following the cpu mode argument. +// +// modes +// dut currently supports 3 modes. +// +// The first, default, mode, is "device". In device mode, dut makes an http connection +// to a dut running on a host, then starts an HTTP RPC server. +// +// The second mode is "tester". In this mode, dut calls the Welcome service, followed +// by the Reboot service. Tester can be useful, run by a shell script in a for loop, for +// ensure reboot is reliable. +// +// The third mode is "cpu". dut will direct the DUT to start a cpu service, and block until +// it exits. Flags for this service: +// pubkey: name of the public key file +// hostkey: name of the host key file +// cpuport: port on which to serve the cpu service +// +// Theory of Operation +// dut runs on the host, accepting connections from DUT, and controlling them via +// Go HTTP RPC commands. As each command is executed, its response is printed. +// Commands are: +// +// Welcome -- get a welcome message +// Argument: None +// Return: a welcome message in cowsay format: +// < welcome to DUT > +// -------------- +// \ ^__^ +// \ (oo)\_______ +// (__)\ )\/\ +// ||----w | +// || || +// +// Die -- force dut on DUT to exit +// Argument: time to sleep before exiting as a time.Duration +// Return: no return; kills the program running on DUT +// +// Reboot +// Argument: time to sleep before rebooting as a time.Duration +// +// CPU -- Start a CPU server on DUT +// Arguments: public key and host key as a []byte, service port as a string +// Returns: returns (possibly nil) error exit value of cpu server; blocks until it is done +// +// +package main diff --git a/cmds/dut/dut.go b/cmds/dut/dut.go new file mode 100644 index 00000000..7b47576a --- /dev/null +++ b/cmds/dut/dut.go @@ -0,0 +1,189 @@ +// This is a very simple dut program. It builds into one binary to implement +// both client and server. It's just easier to see both sides of the code and test +// that way. +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + "net" + "net/rpc" + "os" + "time" + + "github.com/u-root/u-root/pkg/ulog" + "golang.org/x/sys/unix" +) + +var ( + debug = flag.Bool("d", false, "Enable debug prints") + addr = flag.String("addr", "192.168.0.1:8080", "DUT addr in addr:port format") + network = flag.String("net", "tcp", "Network to use") + klog = flag.Bool("klog", false, "Direct all logging to klog -- depends on debug") + + // for debug + v = func(string, ...interface{}) {} +) + +func dutStart(network, addr string) (net.Listener, error) { + ln, err := net.Listen(network, addr) + if err != nil { + log.Print(err) + return nil, err + } + log.Printf("Listening on %v at %v", ln.Addr(), time.Now()) + return ln, nil +} + +func dutAccept(l net.Listener) (net.Conn, error) { + if err := l.(*net.TCPListener).SetDeadline(time.Now().Add(3 * time.Minute)); err != nil { + return nil, err + } + c, err := l.Accept() + if err != nil { + log.Printf("Listen failed: %v at %v", err, time.Now()) + log.Print(err) + return nil, err + } + log.Printf("Accepted %v", c) + return c, nil +} + +func dutRPC(network, addr string) error { + l, err := dutStart(network, addr) + if err != nil { + return err + } + c, err := dutAccept(l) + if err != nil { + return err + } + cl := rpc.NewClient(c) + for _, cmd := range []struct { + call string + args interface{} + }{ + {"Command.Welcome", &RPCWelcome{}}, + {"Command.Reboot", &RPCReboot{}}, + } { + var r RPCRes + if err := cl.Call(cmd.call, cmd.args, &r); err != nil { + return err + } + fmt.Printf("%v(%v): %v\n", cmd.call, cmd.args, string(r.C)) + } + + if c, err = dutAccept(l); err != nil { + return err + } + cl = rpc.NewClient(c) + var r RPCRes + if err := cl.Call("Command.Welcome", &RPCWelcome{}, &r); err != nil { + return err + } + fmt.Printf("%v(%v): %v\n", "Command.Welcome", nil, string(r.C)) + + return nil +} + +func dutcpu(network, addr, pubkey, hostkey, cpuport string) error { + var req = &RPCCPU{Network: network, Addr: addr} + var err error + + // we send the pubkey and hostkey as the value of the key, not the + // name of the file. + // TODO: maybe use ssh_config to find keys? the cpu client can do that. + // Note: the public key is not optional. That said, we do not test + // for len(*pubKey) > 0; if it is set to ""< ReadFile will return + // an error. + if req.PubKey, err = ioutil.ReadFile(pubkey); err != nil { + return fmt.Errorf("Reading pubKey:%w", err) + } + if len(hostkey) > 0 { + if req.HostKey, err = ioutil.ReadFile(hostkey); err != nil { + return fmt.Errorf("Reading hostKey:%w", err) + } + } + + l, err := dutStart(network, addr) + if err != nil { + return err + } + + c, err := dutAccept(l) + if err != nil { + return err + } + + cl := rpc.NewClient(c) + + for _, cmd := range []struct { + call string + args interface{} + }{ + {"Command.Welcome", &RPCWelcome{}}, + {"Command.Welcome", &RPCWelcome{}}, + {"Command.CPU", req}, + } { + var r RPCRes + if err := cl.Call(cmd.call, cmd.args, &r); err != nil { + return err + } + fmt.Printf("%v(%v): %v\n", cmd.call, cmd.args, string(r.C)) + } + return err +} + +func main() { + // for CPU + flag.Parse() + + if *debug { + v = log.Printf + if *klog { + ulog.KernelLog.Reinit() + v = ulog.KernelLog.Printf + } + } + a := flag.Args() + if len(a) == 0 { + a = []string{"device"} + } + + os.Args = a + var err error + v("Mode is %v", a[0]) + switch a[0] { + case "tester": + err = dutRPC(*network, *addr) + case "cpu": + // These flags are separated out as the only have meaning for the "cpu" client option + var ( + pubKey = flag.String("pubkey", "key.pub", "public key file") + hostKey = flag.String("hostkey", "", "host key file -- usually empty") + cpuPort = flag.String("cpuport", "17010", "cpu port -- IANA value is ncpu tcp/17010") + ) + v("Parse %v", os.Args) + flag.Parse() + v("pubkey %v", *pubKey) + if err := dutcpu(*network, *addr, *pubKey, *hostKey, *cpuPort); err != nil { + log.Printf("cpu service: %v", err) + } + case "device": + err = uinit(*network, *addr) + // What to do after a return? Reboot I suppose. + log.Printf("Device returns with error %v", err) + if err := unix.Reboot(int(unix.LINUX_REBOOT_CMD_RESTART)); err != nil { + log.Printf("Reboot failed, not sure what to do now.") + } + default: + log.Printf("Unknown mode %v", a[0]) + } + log.Printf("We are now done ......................") + if err != nil { + log.Printf("%v", err) + os.Exit(2) + } +} diff --git a/cmds/dut/dut_test.go b/cmds/dut/dut_test.go new file mode 100644 index 00000000..cf3d79c0 --- /dev/null +++ b/cmds/dut/dut_test.go @@ -0,0 +1,52 @@ +package main + +import ( + "log" + "net/rpc" + "testing" + "time" +) + +func TestUinit(t *testing.T) { + var tests = []struct { + c string + r interface{} + err string + }{ + {c: "Welcome", r: RPCWelcome{}}, + {c: "Reboot", r: RPCReboot{}}, + } + l, err := dutStart("tcp", ":") + if err != nil { + t.Fatal(err) + } + + a := l.Addr() + t.Logf("listening on %v", a) + // Kick off our node. + go func() { + time.Sleep(1 * time.Second) + if err := uinit(a.Network(), a.String()); err != nil { + log.Printf("starting uinit: got %v, want nil", err) + } + }() + + c, err := dutAccept(l) + if err != nil { + t.Fatal(err) + } + t.Logf("Connected on %v", c) + + cl := rpc.NewClient(c) + for _, tt := range tests { + t.Run(tt.c, func(t *testing.T) { + var r RPCRes + if err = cl.Call("Command."+tt.c, tt.r, &r); err != nil { + t.Fatalf("Call to %v: got %v, want nil", tt.c, err) + } + if r.Err != tt.err { + t.Errorf("%v: got %v, want %v", tt, r.Err, tt.err) + } + }) + } +} diff --git a/cmds/dut/rpc.go b/cmds/dut/rpc.go new file mode 100644 index 00000000..4c2a995e --- /dev/null +++ b/cmds/dut/rpc.go @@ -0,0 +1,80 @@ +package main + +import ( + "fmt" + "log" + "os" + "time" + + "golang.org/x/sys/unix" +) + +type RPCRes struct { + C []byte + Err string +} + +type Command int + +type RPCWelcome struct { +} + +func (*Command) Welcome(args *RPCWelcome, r *RPCRes) error { + r.C = []byte(welcome) + r.Err = "" + log.Printf("welcome") + return nil +} + +type RPCExit struct { + When time.Duration +} + +func (*Command) Die(args *RPCExit, r *RPCRes) error { + go func() { + time.Sleep(args.When) + log.Printf("die exits") + os.Exit(0) + }() + *r = RPCRes{} + log.Printf("die returns") + return nil +} + +type RPCReboot struct { + When time.Duration +} + +func (*Command) Reboot(args *RPCReboot, r *RPCRes) error { + go func() { + time.Sleep(args.When) + if err := unix.Reboot(unix.LINUX_REBOOT_CMD_RESTART); err != nil { + log.Printf("%v\n", err) + } + }() + *r = RPCRes{} + log.Printf("reboot returns") + return nil +} + +type RPCCPU struct { + Network string + Addr string + PubKey []byte + HostKey []byte +} + +func (*Command) CPU(args *RPCCPU, r *RPCRes) error { + v("CPU") + res := make(chan error) + go func(network, addr string, pubKey, hostKey []byte) { + v("cpu serve(%q, %q,%q,%q)", network, addr, pubKey, hostKey) + err := serve(network, addr, pubKey, hostKey) + v("cpu serve returns") + res <- err + }(args.Network, args.Addr, args.PubKey, args.HostKey) + err := <-res + *r = RPCRes{Err: fmt.Sprintf("%v", err)} + v("cpud returns") + return nil +} diff --git a/cmds/dut/serve.go b/cmds/dut/serve.go new file mode 100644 index 00000000..d1ebc030 --- /dev/null +++ b/cmds/dut/serve.go @@ -0,0 +1,66 @@ +// Copyright 2022 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "io/ioutil" + "log" + "net" + "time" + + // We use this ssh because it implements port redirection. + // It can not, however, unpack password-protected keys yet. + "github.com/gliderlabs/ssh" // TODO: get rid of krpty + "github.com/u-root/cpu/server" + "golang.org/x/sys/unix" +) + +// hang hangs for a VERY long time. +// This aids diagnosis, else you lose all messages in the +// kernel panic as init exits. +func hang() { + log.Printf("hang") + time.Sleep(10000 * time.Second) + log.Printf("done hang") +} + +func serve(network, addr string, pubKey, hostKey []byte) error { + if err := unix.Mount("cpu", "/tmp", "tmpfs", 0, ""); err != nil { + log.Printf("CPUD:Warning: tmpfs mount on /tmp (%v) failed. There will be no 9p mount", err) + } + + // Note that the keys are in a private mount; no need for a temp file. + if err := ioutil.WriteFile("/tmp/key.pub", pubKey, 0644); err != nil { + return fmt.Errorf("writing pubkey: %w", err) + } + if len(hostKey) > 0 { + if err := ioutil.WriteFile("/tmp/hostkey", hostKey, 0644); err != nil { + return fmt.Errorf("writing hostkey: %w", err) + } + } + + v("Kicked off startup jobs, now serve ssh") + s, err := server.New("/tmp/key.pub", "/tmp/hostkey") + if err != nil { + log.Printf(`New(%q, %q): %v != nil`, "/tmp/key.pub", "/tmp/hostkey", err) + hang() + } + v("Server is %v", s) + + ln, err := net.Listen(network, addr) + if err != nil { + log.Printf("net.Listen(): %v != nil", err) + hang() + } + v("Listening on %v", ln.Addr()) + if err := s.Serve(ln); err != ssh.ErrServerClosed { + log.Printf("s.Daemon(): %v != %v", err, ssh.ErrServerClosed) + hang() + } + v("Daemon returns") + hang() + return nil +} diff --git a/cmds/dut/uinit.go b/cmds/dut/uinit.go new file mode 100644 index 00000000..aecef0fa --- /dev/null +++ b/cmds/dut/uinit.go @@ -0,0 +1,60 @@ +package main + +import ( + "log" + "net" + "net/rpc" + "os" + "time" + + "github.com/cenkalti/backoff/v4" +) + +var ( + welcome = ` ______________ +< welcome to DUT > + -------------- + \ ^__^ + \ (oo)\_______ + (__)\ )\/\ + ||----w | + || || +` +) + +func uinit(network, addr string) error { + log.Printf("here we are in uinit") + log.Printf("UINIT uid is %d", os.Getuid()) + + log.Printf("Now dial %v %v", network, addr) + b := backoff.NewExponentialBackOff() + // We'll go at it for 5 minutes, then reboot. + b.MaxElapsedTime = 5 * time.Minute + + var c net.Conn + f := func() error { + nc, err := net.Dial(network, addr) + if err != nil { + log.Printf("Dial went poorly") + return err + } + c = nc + return nil + } + if err := backoff.Retry(f, b); err != nil { + return err + } + log.Printf("Start the RPC server") + var Cmd Command + s := rpc.NewServer() + log.Printf("rpc server is %v", s) + if err := s.Register(&Cmd); err != nil { + log.Printf("register failed: %v", err) + return err + } + log.Printf("Serve and protect") + s.ServeConn(c) + log.Printf("And uinit is all done.") + return nil + +} diff --git a/dut/doc.go b/dut/doc.go new file mode 100644 index 00000000..eb238c15 --- /dev/null +++ b/dut/doc.go @@ -0,0 +1,66 @@ +// dut manages Devices Under Test (a.k.a. DUT) from a host. +// A primary goal is allowing multiple hosts with any architecture to connect. +// +// This program was designed to be used in u-root images, as the uinit, +// or in other initramfs systems. It can not function as a standalone +// init: it assumes network is set up, for example. +// +// In this document, dut refers to this program, and DUT refers to +// Devices Under Test. Hopefully this is not too confusing, but it is +// convenient. Also, please note: DUT is plural (Devices). We don't need +// to say DUTs -- at least one is assumed. +// +// The same dut binary runs on host and DUT, in either device mode (i.e. +// on the DUT), or in some host-specific mode. The mode is chosen by +// the first non-flag argument. If there are flags specific to that mode, +// they follow that argument. +// E.g., when uinit is run on the host and we want it to enable cpu daemons +// on the DUT, we run it as follows: +// dut cpu -key ... +// the -key switch is only valid following the cpu mode argument. +// +// modes +// dut currently supports 3 modes. +// +// The first, default, mode, is "device". In device mode, dut makes an http connection +// to a dut running on a host, then starts an HTTP RPC server. +// +// The second mode is "tester". In this mode, dut calls the Welcome service, followed +// by the Reboot service. Tester can be useful, run by a shell script in a for loop, for +// ensure reboot is reliable. +// +// The third mode is "cpu". dut will direct the DUT to start a cpu service, and block until +// it exits. Flags for this service: +// pubkey: name of the public key file +// hostkey: name of the host key file +// cpuport: port on which to serve the cpu service +// +// Theory of Operation +// dut runs on the host, accepting connections from DUT, and controlling them via +// Go HTTP RPC commands. As each command is executed, its response is printed. +// Commands are: +// +// Welcome -- get a welcome message +// Argument: None +// Return: a welcome message in cowsay format: +// < welcome to DUT > +// -------------- +// \ ^__^ +// \ (oo)\_______ +// (__)\ )\/\ +// ||----w | +// || || +// +// Die -- force dut on DUT to exit +// Argument: time to sleep before exiting as a time.Duration +// Return: no return; kills the program running on DUT +// +// Reboot +// Argument: time to sleep before rebooting as a time.Duration +// +// CPU -- Start a CPU server on DUT +// Arguments: public key and host key as a []byte, service port as a string +// Returns: returns (possibly nil) error exit value of cpu server; blocks until it is done +// +// +package main diff --git a/dut/dut.go b/dut/dut.go new file mode 100644 index 00000000..1f9964d3 --- /dev/null +++ b/dut/dut.go @@ -0,0 +1,189 @@ +// This is a very simple dut program. It builds into one binary to implement +// both client and server. It's just easier to see both sides of the code and test +// that way. +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + "net" + "net/rpc" + "os" + "time" + + "github.com/u-root/u-root/pkg/ulog" + "golang.org/x/sys/unix" +) + +var ( + debug = flag.Bool("d", false, "Enable debug prints") + host = flag.String("host", "192.168.0.1", "hostname") + klog = flag.Bool("klog", false, "Direct all logging to klog -- depends on debug") + port = flag.String("port", "8080", "port number") + dir = flag.String("dir", ".", "directory to serve") + + // for debug + v = func(string, ...interface{}) {} +) + +func dutStart(t, host, port string) (net.Listener, error) { + ln, err := net.Listen(t, host+":"+port) + if err != nil { + log.Print(err) + return nil, err + } + log.Printf("Listening on %v at %v", ln.Addr(), time.Now()) + return ln, nil +} + +func dutAccept(l net.Listener) (net.Conn, error) { + if err := l.(*net.TCPListener).SetDeadline(time.Now().Add(3 * time.Minute)); err != nil { + return nil, err + } + c, err := l.Accept() + if err != nil { + log.Printf("Listen failed: %v at %v", err, time.Now()) + log.Print(err) + return nil, err + } + log.Printf("Accepted %v", c) + return c, nil +} + +func dutRPC(host, port string) error { + l, err := dutStart("tcp", host, port) + if err != nil { + return err + } + c, err := dutAccept(l) + if err != nil { + return err + } + cl := rpc.NewClient(c) + for _, cmd := range []struct { + call string + args interface{} + }{ + {"Command.Welcome", &RPCWelcome{}}, + {"Command.Reboot", &RPCReboot{}}, + } { + var r RPCRes + if err := cl.Call(cmd.call, cmd.args, &r); err != nil { + return err + } + fmt.Printf("%v(%v): %v\n", cmd.call, cmd.args, string(r.C)) + } + + if c, err = dutAccept(l); err != nil { + return err + } + cl = rpc.NewClient(c) + var r RPCRes + if err := cl.Call("Command.Welcome", &RPCWelcome{}, &r); err != nil { + return err + } + fmt.Printf("%v(%v): %v\n", "Command.Welcome", nil, string(r.C)) + + return nil +} + +func dutcpu(host, port, pubkey, hostkey, cpuport string) error { + var req = &RPCCPU{Port: cpuport} + var err error + + // we send the pubkey and hostkey as the value of the key, not the + // name of the file. + // TODO: maybe use ssh_config to find keys? the cpu client can do that. + // Note: the public key is not optional. That said, we do not test + // for len(*pubKey) > 0; if it is set to ""< ReadFile will return + // an error. + if req.PubKey, err = ioutil.ReadFile(pubkey); err != nil { + return fmt.Errorf("Reading pubKey:%w", err) + } + if len(hostkey) > 0 { + if req.HostKey, err = ioutil.ReadFile(hostkey); err != nil { + return fmt.Errorf("Reading hostKey:%w", err) + } + } + + l, err := dutStart("tcp", host, port) + if err != nil { + return err + } + + c, err := dutAccept(l) + if err != nil { + return err + } + + cl := rpc.NewClient(c) + + for _, cmd := range []struct { + call string + args interface{} + }{ + {"Command.Welcome", &RPCWelcome{}}, + {"Command.Welcome", &RPCWelcome{}}, + {"Command.CPU", req}, + } { + var r RPCRes + if err := cl.Call(cmd.call, cmd.args, &r); err != nil { + return err + } + fmt.Printf("%v(%v): %v\n", cmd.call, cmd.args, string(r.C)) + } + return err +} + +func main() { + // for CPU + flag.Parse() + + if *debug { + v = log.Printf + if *klog { + ulog.KernelLog.Reinit() + v = ulog.KernelLog.Printf + } + } + a := flag.Args() + if len(a) == 0 { + a = []string{"device"} + } + + os.Args = a + var err error + v("Mode is %v", a[0]) + switch a[0] { + case "tester": + err = dutRPC(*host, *port) + case "cpu": + var ( + pubKey = flag.String("pubkey", "key.pub", "public key file") + hostKey = flag.String("hostkey", "", "host key file -- usually empty") + cpuPort = flag.String("cpuport", "17010", "cpu port -- IANA value is ncpu tcp/17010") + ) + v("Parse %v", os.Args) + flag.Parse() + v("pubkey %v", *pubKey) + if err := dutcpu(*host, *port, *pubKey, *hostKey, *cpuPort); err != nil { + log.Printf("cpu service: %v", err) + } + case "device": + err = uinit(*host, *port) + // What to do after a return? Reboot I suppose. + log.Printf("Device returns with error %v", err) + if err := unix.Reboot(int(unix.LINUX_REBOOT_CMD_RESTART)); err != nil { + log.Printf("Reboot failed, not sure what to do now.") + } + default: + log.Printf("Unknown mode %v", a[0]) + } + log.Printf("We are now done ......................") + if err != nil { + log.Printf("%v", err) + os.Exit(2) + } +} diff --git a/dut/dut_test.go b/dut/dut_test.go new file mode 100644 index 00000000..67db65f2 --- /dev/null +++ b/dut/dut_test.go @@ -0,0 +1,51 @@ +package main + +import ( + "net/rpc" + "testing" + "time" +) + +func TestUinit(t *testing.T) { + var tests = []struct { + c string + r interface{} + err string + }{ + {c: "Welcome", r: RPCWelcome{}}, + {c: "Reboot", r: RPCReboot{}}, + } + l, err := dutStart("tcp", "localhost", "") + if err != nil { + t.Fatal(err) + } + + a := l.Addr() + t.Logf("listening on %v", a) + // Kick off our node. + go func() { + time.Sleep(1) + if err := uinit(a.Network(), a.String(), "17010"); err != nil { + t.Fatalf("starting uinit: got %v, want nil", err) + } + }() + + c, err := dutAccept(l) + if err != nil { + t.Fatal(err) + } + t.Logf("Connected on %v", c) + + cl := rpc.NewClient(c) + for _, tt := range tests { + t.Run(tt.c, func(t *testing.T) { + var r RPCRes + if err = cl.Call("Command."+tt.c, tt.r, &r); err != nil { + t.Fatalf("Call to %v: got %v, want nil", tt.c, err) + } + if r.Err != tt.err { + t.Errorf("%v: got %v, want %v", tt, r.Err, tt.err) + } + }) + } +} diff --git a/dut/rpc.go b/dut/rpc.go new file mode 100644 index 00000000..2d2aaf9d --- /dev/null +++ b/dut/rpc.go @@ -0,0 +1,79 @@ +package main + +import ( + "fmt" + "log" + "os" + "time" + + "golang.org/x/sys/unix" +) + +type RPCRes struct { + C []byte + Err string +} + +type Command int + +type RPCWelcome struct { +} + +func (*Command) Welcome(args *RPCWelcome, r *RPCRes) error { + r.C = []byte(welcome) + r.Err = "" + log.Printf("welcome") + return nil +} + +type RPCExit struct { + When time.Duration +} + +func (*Command) Die(args *RPCExit, r *RPCRes) error { + go func() { + time.Sleep(args.When) + log.Printf("die exits") + os.Exit(0) + }() + *r = RPCRes{} + log.Printf("die returns") + return nil +} + +type RPCReboot struct { + When time.Duration +} + +func (*Command) Reboot(args *RPCReboot, r *RPCRes) error { + go func() { + time.Sleep(args.When) + if err := unix.Reboot(unix.LINUX_REBOOT_CMD_RESTART); err != nil { + log.Printf("%v\n", err) + } + }() + *r = RPCRes{} + log.Printf("reboot returns") + return nil +} + +type RPCCPU struct { + PubKey []byte + HostKey []byte + Port string +} + +func (*Command) CPU(args *RPCCPU, r *RPCRes) error { + v("CPU") + res := make(chan error) + go func(pubKey, hostKey []byte, port string) { + v("cpu serve(%q,%q,%q)", pubKey, hostKey, port) + err := serve(pubKey, hostKey, port) + v("cpu serve returns") + res <- err + }(args.PubKey, args.HostKey, args.Port) + err := <-res + *r = RPCRes{Err: fmt.Sprintf("%v", err)} + v("cpud returns") + return nil +} diff --git a/dut/serve.go b/dut/serve.go new file mode 100644 index 00000000..52eccb4f --- /dev/null +++ b/dut/serve.go @@ -0,0 +1,66 @@ +// Copyright 2022 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "io/ioutil" + "log" + "net" + "time" + + // We use this ssh because it implements port redirection. + // It can not, however, unpack password-protected keys yet. + "github.com/gliderlabs/ssh" // TODO: get rid of krpty + "github.com/u-root/cpu/server" + "golang.org/x/sys/unix" +) + +// hang hangs for a VERY long time. +// This aids diagnosis, else you lose all messages in the +// kernel panic as init exits. +func hang() { + log.Printf("hang") + time.Sleep(10000 * time.Second) + log.Printf("done hang") +} + +func serve(pubKey, hostKey []byte, port string) error { + if err := unix.Mount("cpu", "/tmp", "tmpfs", 0, ""); err != nil { + log.Printf("CPUD:Warning: tmpfs mount on /tmp (%v) failed. There will be no 9p mount", err) + } + + // Note that the keys are in a private mount; no need for a temp file. + if err := ioutil.WriteFile("/tmp/key.pub", pubKey, 0644); err != nil { + return fmt.Errorf("writing pubkey: %w", err) + } + if len(hostKey) > 0 { + if err := ioutil.WriteFile("/tmp/hostkey", hostKey, 0644); err != nil { + return fmt.Errorf("writing hostkey: %w", err) + } + } + + v("Kicked off startup jobs, now serve ssh") + s, err := server.New("/tmp/key.pub", "/tmp/hostkey") + if err != nil { + log.Printf(`New(%q, %q): %v != nil`, "/tmp/key.pub", "/tmp/hostkey", err) + hang() + } + v("Server is %v", s) + + ln, err := net.Listen("tcp", ":"+port) + if err != nil { + log.Printf("net.Listen(): %v != nil", err) + hang() + } + v("Listening on %v", ln.Addr()) + if err := s.Serve(ln); err != ssh.ErrServerClosed { + log.Printf("s.Daemon(): %v != %v", err, ssh.ErrServerClosed) + hang() + } + v("Daemon returns") + hang() + return nil +} diff --git a/dut/uinit.go b/dut/uinit.go new file mode 100644 index 00000000..1a3c77d0 --- /dev/null +++ b/dut/uinit.go @@ -0,0 +1,62 @@ +package main + +import ( + "log" + "net" + "net/rpc" + "os" + "time" + + "github.com/cenkalti/backoff/v4" +) + +var ( + rebooting = "Rebooting!" + welcome = ` ______________ +< welcome to DUT > + -------------- + \ ^__^ + \ (oo)\_______ + (__)\ )\/\ + ||----w | + || || +` +) + +func uinit(r, p string) error { + log.Printf("here we are in uinit") + log.Printf("UINIT uid is %d", os.Getuid()) + + na := r + ":" + p + log.Printf("Now dial %v", na) + b := backoff.NewExponentialBackOff() + // We'll go at it for 5 minutes, then reboot. + b.MaxElapsedTime = 5 * time.Minute + + var c net.Conn + f := func() error { + nc, err := net.Dial("tcp", na) + if err != nil { + log.Printf("Dial went poorly") + return err + } + c = nc + return nil + } + if err := backoff.Retry(f, b); err != nil { + return err + } + log.Printf("Start the RPC server") + var Cmd Command + s := rpc.NewServer() + log.Printf("rpc server is %v", s) + if err := s.Register(&Cmd); err != nil { + log.Printf("register failed: %v", err) + return err + } + log.Printf("Serve and protect") + s.ServeConn(c) + log.Printf("And uinit is all done.") + return nil + +} diff --git a/go.mod b/go.mod index 7bd224bf..c1a5bf0a 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,8 @@ require ( github.com/mdlayher/vsock v1.1.1 ) +require github.com/cenkalti/backoff/v4 v4.0.2 + require ( github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/hashicorp/errwrap v1.0.0 // indirect diff --git a/go.sum b/go.sum index 287c1c76..b451bd2a 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,7 @@ github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NR github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY= +github.com/cenkalti/backoff/v4 v4.0.2 h1:JIufpQLbh4DkbQoii76ItQIUFzevQSqOLZca4eamEDs= github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= From a6c1c12d3fe00de517687fb50f64256d3591eedf Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Thu, 4 Aug 2022 22:13:31 +0000 Subject: [PATCH 2/2] minor modifications for real use - only call reboot if uinit returned with an error - convert all host + port (2 strings) uses to standard go addr:port usage - add dut to the TESTCPU command to ease testing - when starting cpu, do not block on it These changes support a usage model where dut starts on the device, and the test manager has it start up a cpu service. At that point, dut is no longer needed and can exit. The reboot-on-exit model no longer makes sense. Signed-off-by: Ronald G. Minnich --- TESTCPU | 1 + cmds/dut/dut.go | 25 +++--- cmds/dut/serve.go | 12 +-- dut/doc.go | 66 ---------------- dut/dut.go | 189 ---------------------------------------------- dut/dut_test.go | 51 ------------- dut/rpc.go | 79 ------------------- dut/serve.go | 66 ---------------- dut/uinit.go | 62 --------------- 9 files changed, 19 insertions(+), 532 deletions(-) delete mode 100644 dut/doc.go delete mode 100644 dut/dut.go delete mode 100644 dut/dut_test.go delete mode 100644 dut/rpc.go delete mode 100644 dut/serve.go delete mode 100644 dut/uinit.go diff --git a/TESTCPU b/TESTCPU index 887fcf4c..2abe206a 100755 --- a/TESTCPU +++ b/TESTCPU @@ -24,6 +24,7 @@ u-root \ all \ cmds/cpud \ cmds/cpu \ + cmds/dut \ echo NOT adding a host key at -files ssh_host_rsa_key:etc/ssh/ssh_host_rsa_key diff --git a/cmds/dut/dut.go b/cmds/dut/dut.go index 7b47576a..f060aa23 100644 --- a/cmds/dut/dut.go +++ b/cmds/dut/dut.go @@ -22,6 +22,9 @@ var ( addr = flag.String("addr", "192.168.0.1:8080", "DUT addr in addr:port format") network = flag.String("net", "tcp", "Network to use") klog = flag.Bool("klog", false, "Direct all logging to klog -- depends on debug") + pubKey = flag.String("key", "key.pub", "public key file") + hostKey = flag.String("hostkey", "", "host key file -- usually empty") + cpuAddr = flag.String("cpuaddr", ":17010", "cpu address -- IANA port value is ncpu tcp/17010") // for debug v = func(string, ...interface{}) {} @@ -88,8 +91,8 @@ func dutRPC(network, addr string) error { return nil } -func dutcpu(network, addr, pubkey, hostkey, cpuport string) error { - var req = &RPCCPU{Network: network, Addr: addr} +func dutcpu(network, addr, pubkey, hostkey, cpuaddr string) error { + var req = &RPCCPU{Network: network, Addr: cpuaddr} var err error // we send the pubkey and hostkey as the value of the key, not the @@ -137,7 +140,6 @@ func dutcpu(network, addr, pubkey, hostkey, cpuport string) error { } func main() { - // for CPU flag.Parse() if *debug { @@ -159,23 +161,20 @@ func main() { case "tester": err = dutRPC(*network, *addr) case "cpu": - // These flags are separated out as the only have meaning for the "cpu" client option - var ( - pubKey = flag.String("pubkey", "key.pub", "public key file") - hostKey = flag.String("hostkey", "", "host key file -- usually empty") - cpuPort = flag.String("cpuport", "17010", "cpu port -- IANA value is ncpu tcp/17010") - ) - v("Parse %v", os.Args) - flag.Parse() + // In this case, we chain the cpu daemon and exit. v("pubkey %v", *pubKey) - if err := dutcpu(*network, *addr, *pubKey, *hostKey, *cpuPort); err != nil { + if err = dutcpu(*network, *addr, *pubKey, *hostKey, *cpuAddr); err != nil { log.Printf("cpu service: %v", err) } case "device": err = uinit(*network, *addr) // What to do after a return? Reboot I suppose. log.Printf("Device returns with error %v", err) - if err := unix.Reboot(int(unix.LINUX_REBOOT_CMD_RESTART)); err != nil { + // We only reboot if there was an error. + if err == nil { + break + } + if err = unix.Reboot(int(unix.LINUX_REBOOT_CMD_RESTART)); err != nil { log.Printf("Reboot failed, not sure what to do now.") } default: diff --git a/cmds/dut/serve.go b/cmds/dut/serve.go index d1ebc030..2b67f9fc 100644 --- a/cmds/dut/serve.go +++ b/cmds/dut/serve.go @@ -56,11 +56,11 @@ func serve(network, addr string, pubKey, hostKey []byte) error { hang() } v("Listening on %v", ln.Addr()) - if err := s.Serve(ln); err != ssh.ErrServerClosed { - log.Printf("s.Daemon(): %v != %v", err, ssh.ErrServerClosed) - hang() - } - v("Daemon returns") - hang() + go func(ln net.Listener) { + if err := s.Serve(ln); err != ssh.ErrServerClosed { + log.Printf("cpu Serve() returned %v which indicates a problem; %v is the expected return", err, ssh.ErrServerClosed) + } + v("Daemon returns") + }(ln) return nil } diff --git a/dut/doc.go b/dut/doc.go deleted file mode 100644 index eb238c15..00000000 --- a/dut/doc.go +++ /dev/null @@ -1,66 +0,0 @@ -// dut manages Devices Under Test (a.k.a. DUT) from a host. -// A primary goal is allowing multiple hosts with any architecture to connect. -// -// This program was designed to be used in u-root images, as the uinit, -// or in other initramfs systems. It can not function as a standalone -// init: it assumes network is set up, for example. -// -// In this document, dut refers to this program, and DUT refers to -// Devices Under Test. Hopefully this is not too confusing, but it is -// convenient. Also, please note: DUT is plural (Devices). We don't need -// to say DUTs -- at least one is assumed. -// -// The same dut binary runs on host and DUT, in either device mode (i.e. -// on the DUT), or in some host-specific mode. The mode is chosen by -// the first non-flag argument. If there are flags specific to that mode, -// they follow that argument. -// E.g., when uinit is run on the host and we want it to enable cpu daemons -// on the DUT, we run it as follows: -// dut cpu -key ... -// the -key switch is only valid following the cpu mode argument. -// -// modes -// dut currently supports 3 modes. -// -// The first, default, mode, is "device". In device mode, dut makes an http connection -// to a dut running on a host, then starts an HTTP RPC server. -// -// The second mode is "tester". In this mode, dut calls the Welcome service, followed -// by the Reboot service. Tester can be useful, run by a shell script in a for loop, for -// ensure reboot is reliable. -// -// The third mode is "cpu". dut will direct the DUT to start a cpu service, and block until -// it exits. Flags for this service: -// pubkey: name of the public key file -// hostkey: name of the host key file -// cpuport: port on which to serve the cpu service -// -// Theory of Operation -// dut runs on the host, accepting connections from DUT, and controlling them via -// Go HTTP RPC commands. As each command is executed, its response is printed. -// Commands are: -// -// Welcome -- get a welcome message -// Argument: None -// Return: a welcome message in cowsay format: -// < welcome to DUT > -// -------------- -// \ ^__^ -// \ (oo)\_______ -// (__)\ )\/\ -// ||----w | -// || || -// -// Die -- force dut on DUT to exit -// Argument: time to sleep before exiting as a time.Duration -// Return: no return; kills the program running on DUT -// -// Reboot -// Argument: time to sleep before rebooting as a time.Duration -// -// CPU -- Start a CPU server on DUT -// Arguments: public key and host key as a []byte, service port as a string -// Returns: returns (possibly nil) error exit value of cpu server; blocks until it is done -// -// -package main diff --git a/dut/dut.go b/dut/dut.go deleted file mode 100644 index 1f9964d3..00000000 --- a/dut/dut.go +++ /dev/null @@ -1,189 +0,0 @@ -// This is a very simple dut program. It builds into one binary to implement -// both client and server. It's just easier to see both sides of the code and test -// that way. -package main - -import ( - "flag" - "fmt" - "io/ioutil" - "log" - "net" - "net/rpc" - "os" - "time" - - "github.com/u-root/u-root/pkg/ulog" - "golang.org/x/sys/unix" -) - -var ( - debug = flag.Bool("d", false, "Enable debug prints") - host = flag.String("host", "192.168.0.1", "hostname") - klog = flag.Bool("klog", false, "Direct all logging to klog -- depends on debug") - port = flag.String("port", "8080", "port number") - dir = flag.String("dir", ".", "directory to serve") - - // for debug - v = func(string, ...interface{}) {} -) - -func dutStart(t, host, port string) (net.Listener, error) { - ln, err := net.Listen(t, host+":"+port) - if err != nil { - log.Print(err) - return nil, err - } - log.Printf("Listening on %v at %v", ln.Addr(), time.Now()) - return ln, nil -} - -func dutAccept(l net.Listener) (net.Conn, error) { - if err := l.(*net.TCPListener).SetDeadline(time.Now().Add(3 * time.Minute)); err != nil { - return nil, err - } - c, err := l.Accept() - if err != nil { - log.Printf("Listen failed: %v at %v", err, time.Now()) - log.Print(err) - return nil, err - } - log.Printf("Accepted %v", c) - return c, nil -} - -func dutRPC(host, port string) error { - l, err := dutStart("tcp", host, port) - if err != nil { - return err - } - c, err := dutAccept(l) - if err != nil { - return err - } - cl := rpc.NewClient(c) - for _, cmd := range []struct { - call string - args interface{} - }{ - {"Command.Welcome", &RPCWelcome{}}, - {"Command.Reboot", &RPCReboot{}}, - } { - var r RPCRes - if err := cl.Call(cmd.call, cmd.args, &r); err != nil { - return err - } - fmt.Printf("%v(%v): %v\n", cmd.call, cmd.args, string(r.C)) - } - - if c, err = dutAccept(l); err != nil { - return err - } - cl = rpc.NewClient(c) - var r RPCRes - if err := cl.Call("Command.Welcome", &RPCWelcome{}, &r); err != nil { - return err - } - fmt.Printf("%v(%v): %v\n", "Command.Welcome", nil, string(r.C)) - - return nil -} - -func dutcpu(host, port, pubkey, hostkey, cpuport string) error { - var req = &RPCCPU{Port: cpuport} - var err error - - // we send the pubkey and hostkey as the value of the key, not the - // name of the file. - // TODO: maybe use ssh_config to find keys? the cpu client can do that. - // Note: the public key is not optional. That said, we do not test - // for len(*pubKey) > 0; if it is set to ""< ReadFile will return - // an error. - if req.PubKey, err = ioutil.ReadFile(pubkey); err != nil { - return fmt.Errorf("Reading pubKey:%w", err) - } - if len(hostkey) > 0 { - if req.HostKey, err = ioutil.ReadFile(hostkey); err != nil { - return fmt.Errorf("Reading hostKey:%w", err) - } - } - - l, err := dutStart("tcp", host, port) - if err != nil { - return err - } - - c, err := dutAccept(l) - if err != nil { - return err - } - - cl := rpc.NewClient(c) - - for _, cmd := range []struct { - call string - args interface{} - }{ - {"Command.Welcome", &RPCWelcome{}}, - {"Command.Welcome", &RPCWelcome{}}, - {"Command.CPU", req}, - } { - var r RPCRes - if err := cl.Call(cmd.call, cmd.args, &r); err != nil { - return err - } - fmt.Printf("%v(%v): %v\n", cmd.call, cmd.args, string(r.C)) - } - return err -} - -func main() { - // for CPU - flag.Parse() - - if *debug { - v = log.Printf - if *klog { - ulog.KernelLog.Reinit() - v = ulog.KernelLog.Printf - } - } - a := flag.Args() - if len(a) == 0 { - a = []string{"device"} - } - - os.Args = a - var err error - v("Mode is %v", a[0]) - switch a[0] { - case "tester": - err = dutRPC(*host, *port) - case "cpu": - var ( - pubKey = flag.String("pubkey", "key.pub", "public key file") - hostKey = flag.String("hostkey", "", "host key file -- usually empty") - cpuPort = flag.String("cpuport", "17010", "cpu port -- IANA value is ncpu tcp/17010") - ) - v("Parse %v", os.Args) - flag.Parse() - v("pubkey %v", *pubKey) - if err := dutcpu(*host, *port, *pubKey, *hostKey, *cpuPort); err != nil { - log.Printf("cpu service: %v", err) - } - case "device": - err = uinit(*host, *port) - // What to do after a return? Reboot I suppose. - log.Printf("Device returns with error %v", err) - if err := unix.Reboot(int(unix.LINUX_REBOOT_CMD_RESTART)); err != nil { - log.Printf("Reboot failed, not sure what to do now.") - } - default: - log.Printf("Unknown mode %v", a[0]) - } - log.Printf("We are now done ......................") - if err != nil { - log.Printf("%v", err) - os.Exit(2) - } -} diff --git a/dut/dut_test.go b/dut/dut_test.go deleted file mode 100644 index 67db65f2..00000000 --- a/dut/dut_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "net/rpc" - "testing" - "time" -) - -func TestUinit(t *testing.T) { - var tests = []struct { - c string - r interface{} - err string - }{ - {c: "Welcome", r: RPCWelcome{}}, - {c: "Reboot", r: RPCReboot{}}, - } - l, err := dutStart("tcp", "localhost", "") - if err != nil { - t.Fatal(err) - } - - a := l.Addr() - t.Logf("listening on %v", a) - // Kick off our node. - go func() { - time.Sleep(1) - if err := uinit(a.Network(), a.String(), "17010"); err != nil { - t.Fatalf("starting uinit: got %v, want nil", err) - } - }() - - c, err := dutAccept(l) - if err != nil { - t.Fatal(err) - } - t.Logf("Connected on %v", c) - - cl := rpc.NewClient(c) - for _, tt := range tests { - t.Run(tt.c, func(t *testing.T) { - var r RPCRes - if err = cl.Call("Command."+tt.c, tt.r, &r); err != nil { - t.Fatalf("Call to %v: got %v, want nil", tt.c, err) - } - if r.Err != tt.err { - t.Errorf("%v: got %v, want %v", tt, r.Err, tt.err) - } - }) - } -} diff --git a/dut/rpc.go b/dut/rpc.go deleted file mode 100644 index 2d2aaf9d..00000000 --- a/dut/rpc.go +++ /dev/null @@ -1,79 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os" - "time" - - "golang.org/x/sys/unix" -) - -type RPCRes struct { - C []byte - Err string -} - -type Command int - -type RPCWelcome struct { -} - -func (*Command) Welcome(args *RPCWelcome, r *RPCRes) error { - r.C = []byte(welcome) - r.Err = "" - log.Printf("welcome") - return nil -} - -type RPCExit struct { - When time.Duration -} - -func (*Command) Die(args *RPCExit, r *RPCRes) error { - go func() { - time.Sleep(args.When) - log.Printf("die exits") - os.Exit(0) - }() - *r = RPCRes{} - log.Printf("die returns") - return nil -} - -type RPCReboot struct { - When time.Duration -} - -func (*Command) Reboot(args *RPCReboot, r *RPCRes) error { - go func() { - time.Sleep(args.When) - if err := unix.Reboot(unix.LINUX_REBOOT_CMD_RESTART); err != nil { - log.Printf("%v\n", err) - } - }() - *r = RPCRes{} - log.Printf("reboot returns") - return nil -} - -type RPCCPU struct { - PubKey []byte - HostKey []byte - Port string -} - -func (*Command) CPU(args *RPCCPU, r *RPCRes) error { - v("CPU") - res := make(chan error) - go func(pubKey, hostKey []byte, port string) { - v("cpu serve(%q,%q,%q)", pubKey, hostKey, port) - err := serve(pubKey, hostKey, port) - v("cpu serve returns") - res <- err - }(args.PubKey, args.HostKey, args.Port) - err := <-res - *r = RPCRes{Err: fmt.Sprintf("%v", err)} - v("cpud returns") - return nil -} diff --git a/dut/serve.go b/dut/serve.go deleted file mode 100644 index 52eccb4f..00000000 --- a/dut/serve.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2022 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package main - -import ( - "fmt" - "io/ioutil" - "log" - "net" - "time" - - // We use this ssh because it implements port redirection. - // It can not, however, unpack password-protected keys yet. - "github.com/gliderlabs/ssh" // TODO: get rid of krpty - "github.com/u-root/cpu/server" - "golang.org/x/sys/unix" -) - -// hang hangs for a VERY long time. -// This aids diagnosis, else you lose all messages in the -// kernel panic as init exits. -func hang() { - log.Printf("hang") - time.Sleep(10000 * time.Second) - log.Printf("done hang") -} - -func serve(pubKey, hostKey []byte, port string) error { - if err := unix.Mount("cpu", "/tmp", "tmpfs", 0, ""); err != nil { - log.Printf("CPUD:Warning: tmpfs mount on /tmp (%v) failed. There will be no 9p mount", err) - } - - // Note that the keys are in a private mount; no need for a temp file. - if err := ioutil.WriteFile("/tmp/key.pub", pubKey, 0644); err != nil { - return fmt.Errorf("writing pubkey: %w", err) - } - if len(hostKey) > 0 { - if err := ioutil.WriteFile("/tmp/hostkey", hostKey, 0644); err != nil { - return fmt.Errorf("writing hostkey: %w", err) - } - } - - v("Kicked off startup jobs, now serve ssh") - s, err := server.New("/tmp/key.pub", "/tmp/hostkey") - if err != nil { - log.Printf(`New(%q, %q): %v != nil`, "/tmp/key.pub", "/tmp/hostkey", err) - hang() - } - v("Server is %v", s) - - ln, err := net.Listen("tcp", ":"+port) - if err != nil { - log.Printf("net.Listen(): %v != nil", err) - hang() - } - v("Listening on %v", ln.Addr()) - if err := s.Serve(ln); err != ssh.ErrServerClosed { - log.Printf("s.Daemon(): %v != %v", err, ssh.ErrServerClosed) - hang() - } - v("Daemon returns") - hang() - return nil -} diff --git a/dut/uinit.go b/dut/uinit.go deleted file mode 100644 index 1a3c77d0..00000000 --- a/dut/uinit.go +++ /dev/null @@ -1,62 +0,0 @@ -package main - -import ( - "log" - "net" - "net/rpc" - "os" - "time" - - "github.com/cenkalti/backoff/v4" -) - -var ( - rebooting = "Rebooting!" - welcome = ` ______________ -< welcome to DUT > - -------------- - \ ^__^ - \ (oo)\_______ - (__)\ )\/\ - ||----w | - || || -` -) - -func uinit(r, p string) error { - log.Printf("here we are in uinit") - log.Printf("UINIT uid is %d", os.Getuid()) - - na := r + ":" + p - log.Printf("Now dial %v", na) - b := backoff.NewExponentialBackOff() - // We'll go at it for 5 minutes, then reboot. - b.MaxElapsedTime = 5 * time.Minute - - var c net.Conn - f := func() error { - nc, err := net.Dial("tcp", na) - if err != nil { - log.Printf("Dial went poorly") - return err - } - c = nc - return nil - } - if err := backoff.Retry(f, b); err != nil { - return err - } - log.Printf("Start the RPC server") - var Cmd Command - s := rpc.NewServer() - log.Printf("rpc server is %v", s) - if err := s.Register(&Cmd); err != nil { - log.Printf("register failed: %v", err) - return err - } - log.Printf("Serve and protect") - s.ServeConn(c) - log.Printf("And uinit is all done.") - return nil - -}