diff options
| -rw-r--r-- | go.mod | 6 | ||||
| -rw-r--r-- | go.sum | 6 | ||||
| -rw-r--r-- | main.go | 183 |
3 files changed, 61 insertions, 134 deletions
@@ -1,3 +1,7 @@ module git.samanthony.xyz/hose -go 1.20 +go 1.22.0 + +toolchain go1.23.5 + +require github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect @@ -0,0 +1,6 @@ +code.cloudfoundry.org/bytefmt v0.29.0 h1:QaKuXtEY+gcSd1kXgdBN5U8h3mYmTvI2XyNh/5jEXXk= +code.cloudfoundry.org/bytefmt v0.29.0/go.mod h1:fVVUtTfimWCyT90RyJvmwZ0o8Q1d51RP8ByMvyceOXA= +github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0= +github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= @@ -1,164 +1,81 @@ package main import ( - "context" + "flag" "fmt" + "github.com/tonistiigi/units" "io" "net" "os" - "time" ) const ( - port = 60321 - timeout = time.Minute - usage = "Usage: hose <host>" + port = "60321" + network = "tcp" + usage = "Usage: hose <-r | -s <rhost>>" ) -func main() { - if len(os.Args) < 2 { - fmt.Fprintf(os.Stderr, "%s\n", usage) - os.Exit(1) - } - - remote, err := parseHost(os.Args[1]) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to resolve host '%s'\n", os.Args[1]) - os.Exit(1) - } else if remote == nil { - fmt.Fprintf(os.Stderr, "%s is not a valid IP address or host name\n", - os.Args[1]) - os.Exit(1) - } - - ifss, err := net.Interfaces() - if err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) - os.Exit(1) - } - ifs := selectIfs(ifss) - if ifs == nil { - fmt.Fprintf(os.Stderr, "no suitable network interfaces\n") - os.Exit(1) - } - local, err := ip(ifs) - if err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) - os.Exit(1) - } else if local == nil { - fmt.Fprintf(os.Stderr, "no suitable ip addresses\n") - os.Exit(1) - } +var ( + r = flag.Bool("r", false, "receive") + rhost = flag.String("s", "", "send to remote host") +) - errs := make(chan error) - go listen(local, remote, errs) - go send(addr(remote, port), errs) - // Wait for goroutines. - nroutines := 2 - for i := 0; i < nroutines; i++ { - if err := <-errs; err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) +func main() { + flag.Parse() + if *r { + if err := recv(); err != nil { + eprintf("%v\n", err) + } + } else if *rhost != "" { + if err := send(*rhost); err != nil { + eprintf("%v\n", err) } + } else { + fmt.Println(usage) + flag.Usage() + os.Exit(1) } } -// listen listens on local for connections from remote and copies received data -// to stdout. Sends exactly one (possible nil) error to errs. -func listen(local, remote net.IP, errs chan error) { - l, err := net.Listen("tcp", addr(local, port)) +// recv pipes data from the remote host to stdout. +func recv() error { + laddr := net.JoinHostPort("", port) + ln, err := net.Listen(network, laddr) if err != nil { - errs <- err - return - } - - var c net.Conn - for { - c, err = l.Accept() - if err != nil { - errs <- err - return - } - host, _, err := net.SplitHostPort(c.RemoteAddr().String()) - if err != nil { - errs <- err - return - } - if host := net.ParseIP(host); host != nil && host.Equal(remote) { - break - } + return err } + defer ln.Close() + fmt.Fprintf(os.Stderr, "listening on %s\n", laddr) - io.Copy(os.Stdout, c) - - c.Close() - l.Close() - errs <- nil -} - -// send sends data from stdin to addr. addr has the form "host:port" (see -// net.Dial). Sends exactly one (possible nil) error to errs. -func send(addr string, errs chan<- error) { - var d net.Dialer - ctx, cancel := context.WithTimeout(context.Background(), timeout) - c, err := d.DialContext(ctx, "tcp", addr) + conn, err := ln.Accept() if err != nil { - errs <- err - return + return err } + defer conn.Close() + fmt.Fprintf(os.Stderr, "accepted connection from %s\n", conn.RemoteAddr()) - io.Copy(c, os.Stdin) - - c.Close() - cancel() - errs <- nil + n, err := io.Copy(os.Stdout, conn) + fmt.Fprintf(os.Stderr, "received %.2f\n", units.Bytes(n)*units.B) + return err } -// addr joins an IP address and a port number into a full address. See net.Dial. -func addr(ip net.IP, port uint) string { - return net.JoinHostPort(fmt.Sprintf("%s", ip), fmt.Sprintf("%d", port)) -} - -// parseHost returns the IP address represented by s. s is either a literal IP -// address or a hostname. nil is returned if the hostname does not resolve to -// any addresses, -func parseHost(s string) (net.IP, error) { - if ip := net.ParseIP(s); ip != nil { - return ip, nil - } - addrs, err := net.LookupIP(s) +// send pipes data from stdin to the remote host. +func send(rhost string) error { + raddr := net.JoinHostPort(rhost, port) + fmt.Fprintf(os.Stderr, "connecting to %s...\n", raddr) + conn, err := net.Dial(network, raddr) if err != nil { - return nil, err - } - if len(addrs) < 1 { - return nil, nil + return err } - return addrs[0], nil -} + defer conn.Close() + fmt.Fprintf(os.Stderr, "connected to %s\n", raddr) -// selectIfs returns a network interface from ifs that is up, running and not -// a loopback interface. Returns nil if no such interface exists. -func selectIfs(ifs []net.Interface) *net.Interface { - for _, i := range ifs { - if (i.Flags&net.FlagUp != 0) && - (i.Flags&net.FlagRunning != 0) && - (i.Flags&net.FlagLoopback == 0) { - return &i - } - } - return nil + n, err := io.Copy(conn, os.Stdin) + fmt.Fprintf(os.Stderr, "sent %.2f\n", units.Bytes(n)*units.B) + return err } -// ip returns the first IP address bound to ifs or nil if none are bound. -func ip(ifs *net.Interface) (net.IP, error) { - addrs, err := ifs.Addrs() - if err != nil { - return nil, err - } else if len(addrs) < 1 { - return nil, nil - } - ip, _, err := net.ParseCIDR(addrs[0].String()) - if err != nil { - return nil, err - } - return ip, nil +func eprintf(format string, a ...any) { + fmt.Fprintf(os.Stderr, format, a...) + os.Exit(1) } |