diff --git a/cmd/localrelay/args.go b/cmd/localrelay/args.go index eb98a7d..ebdea6a 100644 --- a/cmd/localrelay/args.go +++ b/cmd/localrelay/args.go @@ -241,6 +241,7 @@ func help() { fmt.Println(" localrelay start") fmt.Println(" localrelay status") fmt.Println(" localrelay monitor") + fmt.Println(" localrelay connections") fmt.Println(" localrelay stop") fmt.Println(" localrelay stop ") fmt.Println(" localrelay restart") diff --git a/cmd/localrelay/conns.go b/cmd/localrelay/conns.go new file mode 100644 index 0000000..46ae166 --- /dev/null +++ b/cmd/localrelay/conns.go @@ -0,0 +1,44 @@ +package main + +import ( + "fmt" + "log" + "os" + "time" + + "github.com/containerd/console" +) + +func displayOpenConns() error { + // make terminal raw to allow the use of colour on windows terminals + current, _ := console.ConsoleFromFile(os.Stdout) + // NOTE: Docker healthchecks will panic "provided file is not a console" + + if current != nil { + defer current.Reset() + } + + if current != nil { + if err := current.SetRaw(); err != nil { + log.Println(err) + } + } + + // we don't set terminal to raw here because print statements don't use + // carriage returns + conns, err := activeConnections() + if err != nil { + + fmt.Printf("Daemon: \x1b[31m [OFFLINE] \x1b[0m\r\n") + fmt.Println(err) + + // exit with error + os.Exit(1) + } + + for _, conn := range conns { + fmt.Printf("%s -> %s (%s) (%s)\r\n", conn.RemoteAddr, conn.ForwardedAddr, conn.RelayName, formatDuration(time.Since(time.Unix(conn.Opened, 0)))) + } + + return nil +} diff --git a/cmd/localrelay/daemon.go b/cmd/localrelay/daemon.go index 5ebbb1a..be5fd3f 100644 --- a/cmd/localrelay/daemon.go +++ b/cmd/localrelay/daemon.go @@ -35,3 +35,19 @@ type metrics struct { In, Out, Active, DialAvg int TotalConns, TotalRequests uint64 } + +type connection struct { + ID int64 + + LocalAddr string + RemoteAddr string + Network string + + RelayName string + RelayHost string + + ForwardedAddr string + + // Opened is a unix timestamp + Opened int64 +} diff --git a/cmd/localrelay/ipc.go b/cmd/localrelay/ipc.go index 5a53a47..babf550 100644 --- a/cmd/localrelay/ipc.go +++ b/cmd/localrelay/ipc.go @@ -27,6 +27,7 @@ const ( daemonRun uint8 = iota daemonStatus daemonStop + daemonConns maxErrors = 40 ) @@ -215,6 +216,36 @@ func ipcLoop(conn io.ReadWriteCloser) error { lenbuf := make([]byte, 2) binary.BigEndian.PutUint16(lenbuf, uint16(respBuf.Len())) + conn.Write(lenbuf) + conn.Write(respBuf.Bytes()) + case daemonConns: + respBuf := bytes.NewBuffer(nil) + + relayConns := make([]connection, 0, 200) + + relays := runningRelaysCopy() + for _, r := range relays { + for _, conn := range r.GetConns() { + + relayConns = append(relayConns, connection{ + LocalAddr: conn.Conn.LocalAddr().String(), + RemoteAddr: conn.Conn.RemoteAddr().String(), + Network: conn.Conn.LocalAddr().Network(), + + RelayName: r.Name, + RelayHost: r.Host, + ForwardedAddr: conn.RemoteAddr, + + Opened: conn.Opened.Unix(), + }) + } + } + + json.NewEncoder(respBuf).Encode(relayConns) + + lenbuf := make([]byte, 2) + binary.BigEndian.PutUint16(lenbuf, uint16(respBuf.Len())) + conn.Write(lenbuf) conn.Write(respBuf.Bytes()) default: diff --git a/cmd/localrelay/ipcClient.go b/cmd/localrelay/ipcClient.go index 22b3598..347452b 100644 --- a/cmd/localrelay/ipcClient.go +++ b/cmd/localrelay/ipcClient.go @@ -159,3 +159,29 @@ func stopRelay(relayName string) error { return nil } + +func activeConnections() ([]connection, error) { + conn, err := IPCConnect() + if err != nil { + return nil, err + } + + defer conn.Close() + + _, err = conn.Write([]byte{0, 3, daemonConns, 0, 0}) + if err != nil { + return nil, err + } + + payload, err := readCommand(conn) + if err != nil { + return nil, err + } + + var pool []connection + if err := json.Unmarshal(payload, &pool); err != nil { + return nil, err + } + + return pool, nil +} diff --git a/cmd/localrelay/main.go b/cmd/localrelay/main.go index 4bc792f..a9ab199 100644 --- a/cmd/localrelay/main.go +++ b/cmd/localrelay/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "log" + "os" "strings" "time" @@ -131,6 +132,13 @@ func main() { } fmt.Println("Daemon has been started") + return + case "conns", "connections": + if err := displayOpenConns(); err != nil { + fmt.Println(err) + os.Exit(1) + } + return case "status": if err := relayStatus(); err != nil { diff --git a/relay.go b/relay.go index 1df5183..b22411f 100644 --- a/relay.go +++ b/relay.go @@ -6,6 +6,7 @@ import ( "net/http" "strings" "sync" + "time" "github.com/pkg/errors" "golang.org/x/net/proxy" @@ -61,7 +62,14 @@ type Relay struct { protocolSwitching map[int]string // connPool contains a list of ACTIVE connections - connPool []net.Conn + connPool []*PooledConn +} + +// PooledConn allows meta data to be attached to a connection +type PooledConn struct { + Conn net.Conn + RemoteAddr string + Opened time.Time } const ( @@ -294,7 +302,7 @@ func (r *Relay) storeConn(conn net.Conn) { r.m.Lock() defer r.m.Unlock() - r.connPool = append(r.connPool, conn) + r.connPool = append(r.connPool, &PooledConn{conn, "", time.Now()}) } // popConn removes the provided connection from the conn pool @@ -303,10 +311,32 @@ func (r *Relay) popConn(conn net.Conn) { defer r.m.Unlock() for i := 0; i < len(r.connPool); i++ { - if r.connPool[i] == conn { + if r.connPool[i].Conn == conn { // remove conn r.connPool = append(r.connPool[:i], r.connPool[i+1:]...) return } } } + +// setConnRemote will update the conn pool with the remote +func (r *Relay) setConnRemote(conn net.Conn, remote net.Addr) { + r.m.Lock() + defer r.m.Unlock() + + for i := 0; i < len(r.connPool); i++ { + if r.connPool[i].Conn == conn { + // remove conn + r.connPool[i].RemoteAddr = remote.String() + return + } + } +} + +// GetConns returns all the active connections to this relay +func (r *Relay) GetConns() []*PooledConn { + r.m.Lock() + defer r.m.Unlock() + + return r.connPool +} diff --git a/relay_test.go b/relay_test.go index 67165b5..5959eb2 100644 --- a/relay_test.go +++ b/relay_test.go @@ -63,7 +63,7 @@ func TestConnPool(t *testing.T) { relay.popConn(conn) for _, c := range relay.connPool { - if c == conn { + if c.Conn == conn { t.Fatal("correct conn was not removed") } } diff --git a/relaytcp.go b/relaytcp.go index 3296570..f5747d9 100644 --- a/relaytcp.go +++ b/relaytcp.go @@ -83,6 +83,7 @@ func handleConn(r *Relay, conn net.Conn, network string) { r.Metrics.dial(1, 0, start) r.logger.Info.Printf("CONNECTED TO %s\n", r.ForwardAddr) + r.setConnRemote(conn, c.RemoteAddr()) err = streamConns(conn, c, r.Metrics) if err != nil { @@ -120,6 +121,8 @@ func handleConn(r *Relay, conn net.Conn, network string) { r.Metrics.dial(1, 0, start) r.logger.Info.Printf("CONNECTED TO %s\n", r.ForwardAddr) + r.setConnRemote(conn, c.RemoteAddr()) + err = streamConns(conn, c, r.Metrics) if err != nil { r.logger.Info.Printf("ERROR FROM %q ON %q: ERR=%s\n", conn.RemoteAddr(), conn.LocalAddr(), err)