working server

* administration appears coorect
* multiple clients for one agent
* logging of active connections
* simple echo server on the agent.
This commit is contained in:
Erik Brakkee 2024-07-20 13:35:49 +02:00
parent 6c8e0adccf
commit cfccf04f9d
6 changed files with 150 additions and 66 deletions

View File

@ -14,6 +14,7 @@ import (
"github.com/creack/pty" "github.com/creack/pty"
"github.com/gliderlabs/ssh" "github.com/gliderlabs/ssh"
"github.com/hashicorp/yamux"
"github.com/pkg/sftp" "github.com/pkg/sftp"
) )
@ -113,7 +114,31 @@ func main() {
wsConn := iowrappers.NewWebSocketConn(conn) wsConn := iowrappers.NewWebSocketConn(conn)
defer wsConn.Close() defer wsConn.Close()
// echo server listener, err := yamux.Client(wsConn, nil)
iowrappers.SynchronizeStreams(wsConn, wsConn) if err != nil {
panic(err)
}
log.Println("Connection established to rendez-vous server, waiting for debug sessions")
// Session is a listener
for {
conn, err := listener.Accept()
if err != nil {
panic(err)
}
go handleConnection(conn)
}
} }
func handleConnection(conn io.ReadWriter) {
//stdio := bufio.NewReadWriter(
// bufio.NewReaderSize(os.Stdin, 0),
// bufio.NewWriterSize(os.Stdout, 0))
// echo server
io.Copy(conn, conn)
//iowrappers.SynchronizeStreams(conn, stdio)
}

View File

@ -4,10 +4,14 @@ import (
"cidebug/pkg/iowrappers" "cidebug/pkg/iowrappers"
"fmt" "fmt"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/hashicorp/yamux"
"io"
"log" "log"
"net"
"net/http" "net/http"
"regexp" "regexp"
"sync" "sync"
"time"
) )
var upgrader = websocket.Upgrader{ var upgrader = websocket.Upgrader{
@ -16,7 +20,7 @@ var upgrader = websocket.Upgrader{
} }
func handleWebSocket(w http.ResponseWriter, r *http.Request, func handleWebSocket(w http.ResponseWriter, r *http.Request,
handler func(w http.ResponseWriter, r *http.Request, websockerConnection *iowrappers.WebSocketConn)) { handler func(w http.ResponseWriter, r *http.Request, websockerConnection iowrappers.ReadWriteAddrCloser)) {
conn, err := upgrader.Upgrade(w, r, nil) conn, err := upgrader.Upgrade(w, r, nil)
if err != nil { if err != nil {
log.Println("Error upgrading to WebSocket:", err) log.Println("Error upgrading to WebSocket:", err)
@ -29,7 +33,7 @@ func handleWebSocket(w http.ResponseWriter, r *http.Request,
} }
type WebSocketService struct { type WebSocketService struct {
handler func(w http.ResponseWriter, r *http.Request, conn *iowrappers.WebSocketConn) handler func(w http.ResponseWriter, r *http.Request, conn iowrappers.ReadWriteAddrCloser)
} }
func (endpoint *WebSocketService) handle(w http.ResponseWriter, r *http.Request) { func (endpoint *WebSocketService) handle(w http.ResponseWriter, r *http.Request) {
@ -37,72 +41,106 @@ func (endpoint *WebSocketService) handle(w http.ResponseWriter, r *http.Request)
} }
type Agent struct { type Agent struct {
agent *iowrappers.WebSocketConn // server session
publicId string clientSession *yamux.Session
client *iowrappers.WebSocketConn publicId string
agentAvailable chan bool startTime time.Time
clientClosed chan bool }
type Client struct {
publicId string
agent net.Conn
client iowrappers.ReadWriteAddrCloser
startTime time.Time
} }
func NewAgent(publicId string, func NewAgent(publicId string,
agentConn *iowrappers.WebSocketConn, agentSession *yamux.Session) *Agent {
clientConn *iowrappers.WebSocketConn) *Agent {
return &Agent{ return &Agent{
agent: agentConn, clientSession: agentSession,
publicId: publicId, publicId: publicId,
client: clientConn, startTime: time.Now(),
agentAvailable: make(chan bool, 1), }
clientClosed: make(chan bool, 1), }
func NewClient(publicId string, clientConn iowrappers.ReadWriteAddrCloser, agentConn net.Conn) *Client {
return &Client{
publicId: publicId,
agent: agentConn,
client: clientConn,
startTime: time.Now(),
} }
} }
type Admin struct { type Admin struct {
// map of public id to agent // map of public id to agent
mutex sync.Mutex mutex sync.Mutex
agents map[string]*Agent agents map[string]*Agent
clients []*Client
} }
func NewAdmin() *Admin { func NewAdmin() *Admin {
admin := Admin{ admin := Admin{
mutex: sync.Mutex{}, mutex: sync.Mutex{},
agents: make(map[string]*Agent), agents: make(map[string]*Agent),
clients: make([]*Client, 0), // not strictly needed
} }
return &admin return &admin
} }
func (admin *Admin) addAgent(publicId string, conn *iowrappers.WebSocketConn) (*Agent, error) { func (admin *Admin) logStatus() {
log.Printf("%-20s %-20s %-20s\n", "AGENT", "ACTIVE_SINCE", "REMOTE_ADDRESS")
for _, agent := range admin.agents {
agent.clientSession.RemoteAddr()
log.Printf("%-20s %-20s %-20s\n", agent.publicId,
agent.startTime.Format("2006-01-02 15:04:05"),
agent.clientSession.RemoteAddr().String())
}
log.Println("")
log.Printf("%-20s %-20s %-20s\n", "CLIENT", "ACTIVE_SINCE", "REMOTE_ADDRESS")
for _, client := range admin.clients {
log.Printf("%-20s %-20s %-20s", client.publicId,
client.startTime.Format("2006-01-02 15:04:05"),
client.client.RemoteAddr())
}
log.Printf("\n")
}
func (admin *Admin) addAgent(publicId string, conn io.ReadWriteCloser) (*Agent, error) {
admin.mutex.Lock() admin.mutex.Lock()
defer admin.mutex.Unlock() defer admin.mutex.Unlock()
agent := admin.agents[publicId] agent := admin.agents[publicId]
if agent != nil && agent.agent != nil && agent.agent != conn { if agent != nil {
return nil, fmt.Errorf("A different agent with same publicId '%s' already registered", publicId) return nil, fmt.Errorf("A different agent with same publicId '%s' already registered", publicId)
} }
if agent != nil { session, err := yamux.Client(conn, nil)
agent.agent = conn if err != nil {
return agent, nil return nil, err
} }
agent = NewAgent(publicId, conn, nil) agent = NewAgent(publicId, session)
admin.agents[publicId] = agent admin.agents[publicId] = agent
admin.logStatus()
return agent, nil return agent, nil
} }
func (admin *Admin) addClient(publicId string, conn *iowrappers.WebSocketConn) (*Agent, error) { func (admin *Admin) addClient(publicId string, clientConn iowrappers.ReadWriteAddrCloser) (*Client, error) {
admin.mutex.Lock() admin.mutex.Lock()
defer admin.mutex.Unlock() defer admin.mutex.Unlock()
agent := admin.agents[publicId] agent := admin.agents[publicId]
if agent != nil && agent.client != nil && agent.client != conn { if agent == nil {
// we should setup on-demend connections ot agents later. // we should setup on-demend connections ot agents later.
return nil, fmt.Errorf("A different client with same publicId '%s' already registered", publicId) return nil, fmt.Errorf("No agent found for publicId '%s'", publicId)
} }
if agent != nil { agentConn, err := agent.clientSession.Open()
agent.client = conn if err != nil {
return agent, nil return nil, err
} }
agent = NewAgent(publicId, nil, conn) client := NewClient(publicId, clientConn, agentConn)
admin.agents[publicId] = agent admin.clients = append(admin.clients, client)
return agent, nil admin.logStatus()
return client, nil
} }
func (admin *Admin) RemoveAgent(publicId string) error { func (admin *Admin) RemoveAgent(publicId string) error {
@ -113,32 +151,39 @@ func (admin *Admin) RemoveAgent(publicId string) error {
if agent == nil { if agent == nil {
return fmt.Errorf("Cannot remove agent: '%s' not found", publicId) return fmt.Errorf("Cannot remove agent: '%s' not found", publicId)
} }
agent.agent = nil log.Printf("Removing agent: '%s'", publicId)
if agent.client == nil { err := agent.clientSession.Close()
log.Printf("Removing agent: '%s'", publicId) if err != nil {
delete(admin.agents, publicId) log.Printf("Could not close yamux client session for '%s'\n", publicId)
} }
delete(admin.agents, publicId)
admin.logStatus()
return nil return nil
} }
func (admin *Admin) RemoveClient(publicId string) error { func (admin *Admin) RemoveClient(client *Client) error {
admin.mutex.Lock() admin.mutex.Lock()
defer admin.mutex.Unlock() defer admin.mutex.Unlock()
agent := admin.agents[publicId] log.Printf("Removing client: '%s' created at %s\n", client.publicId,
if agent == nil { client.startTime.Format("2006-01-02 15:04:05"))
return fmt.Errorf("Cannot remove agent: '%s' not found", publicId) // try to explicitly close connection to the agent.
} _ = client.agent.Close()
agent.client = nil _ = client.client.Close()
if agent.agent == nil {
log.Printf("Removing client: '%s'", publicId) for i, _client := range admin.clients {
delete(admin.agents, publicId) if _client == _client {
admin.clients = append(admin.clients[:i], admin.clients[i+1:]...)
break
}
} }
admin.logStatus()
return nil return nil
} }
func (admin *Admin) Register(publicId string, conn *iowrappers.WebSocketConn) error { func (admin *Admin) Register(publicId string, conn io.ReadWriteCloser) error {
defer conn.Close() defer conn.Close()
// TODO: remove agent return value
agent, err := admin.addAgent(publicId, conn) agent, err := admin.addAgent(publicId, conn)
if err != nil { if err != nil {
return err return err
@ -146,26 +191,24 @@ func (admin *Admin) Register(publicId string, conn *iowrappers.WebSocketConn) er
defer func() { defer func() {
admin.RemoveAgent(publicId) admin.RemoveAgent(publicId)
}() }()
log.Printf("After defer remove agent\n")
agent.agentAvailable <- true
log.Printf("Agent registered: '%s'\n", publicId) log.Printf("Agent registered: '%s'\n", publicId)
<-agent.clientClosed for !agent.clientSession.IsClosed() {
time.Sleep(1 * time.Second)
}
return nil return nil
} }
func (admin *Admin) Connect(publicId string, conn *iowrappers.WebSocketConn) error { func (admin *Admin) Connect(publicId string, conn iowrappers.ReadWriteAddrCloser) error {
defer conn.Close() defer conn.Close()
agent, err := admin.addClient(publicId, conn) client, err := admin.addClient(publicId, conn)
if err != nil { if err != nil {
return err return err
} }
defer func() { defer func() {
admin.RemoveClient(publicId) admin.RemoveClient(client)
}() }()
<-agent.agentAvailable
log.Printf("Connecting client and agent: '%s'\n", publicId) log.Printf("Connecting client and agent: '%s'\n", publicId)
iowrappers.SynchronizeStreams(agent.client, agent.agent) iowrappers.SynchronizeStreams(client.client, client.agent)
agent.clientClosed <- true
return nil return nil
} }
@ -184,14 +227,13 @@ func parsePublicId(path string) (publicId string, _ error) {
return "", fmt.Errorf("Invalid URL path '%s'", path) return "", fmt.Errorf("Invalid URL path '%s'", path)
} }
return matches[1], nil return matches[1], nil
} }
func main() { func main() {
admin := NewAdmin() admin := NewAdmin()
registrationService := WebSocketService{ registrationService := WebSocketService{
handler: func(w http.ResponseWriter, r *http.Request, conn *iowrappers.WebSocketConn) { handler: func(w http.ResponseWriter, r *http.Request, conn iowrappers.ReadWriteAddrCloser) {
publicId, err := parsePublicId(r.URL.Path) publicId, err := parsePublicId(r.URL.Path)
if err != nil { if err != nil {
log.Printf("Cannot parse public id from url: '%v'\n", err) log.Printf("Cannot parse public id from url: '%v'\n", err)
@ -205,7 +247,7 @@ func main() {
}, },
} }
clientService := WebSocketService{ clientService := WebSocketService{
handler: func(w http.ResponseWriter, r *http.Request, conn *iowrappers.WebSocketConn) { handler: func(w http.ResponseWriter, r *http.Request, conn iowrappers.ReadWriteAddrCloser) {
publicId, err := parsePublicId(r.URL.Path) publicId, err := parsePublicId(r.URL.Path)
if err != nil { if err != nil {
log.Printf("Cannot parse public id from url: '%v'\n", err) log.Printf("Cannot parse public id from url: '%v'\n", err)

1
go.mod
View File

@ -6,6 +6,7 @@ require (
github.com/creack/pty v1.1.21 github.com/creack/pty v1.1.21
github.com/gliderlabs/ssh v0.3.7 github.com/gliderlabs/ssh v0.3.7
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/hashicorp/yamux v0.1.1
github.com/pkg/sftp v1.13.6 github.com/pkg/sftp v1.13.6
golang.org/x/crypto v0.25.0 golang.org/x/crypto v0.25.0
golang.org/x/term v0.22.0 golang.org/x/term v0.22.0

2
go.sum
View File

@ -9,6 +9,8 @@ github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=

View File

@ -1,14 +1,20 @@
package iowrappers package iowrappers
import "github.com/gorilla/websocket" import (
"github.com/gorilla/websocket"
"io"
"net"
)
type WebSocketConn struct { type WebSocketConn struct {
conn *websocket.Conn conn *websocket.Conn
buf []byte buf []byte
} }
func NewWebSocketConn(conn *websocket.Conn) *WebSocketConn { type ReadWriteAddrCloser interface {
return &WebSocketConn{conn: conn} io.ReadWriteCloser
RemoteAddr() net.Addr
} }
func (websocketConn *WebSocketConn) Read(p []byte) (n int, err error) { func (websocketConn *WebSocketConn) Read(p []byte) (n int, err error) {
@ -26,6 +32,10 @@ func (websocketConn *WebSocketConn) Read(p []byte) (n int, err error) {
return n, err return n, err
} }
func NewWebSocketConn(conn *websocket.Conn) *WebSocketConn {
return &WebSocketConn{conn: conn}
}
func (websocketConn *WebSocketConn) Write(p []byte) (n int, err error) { func (websocketConn *WebSocketConn) Write(p []byte) (n int, err error) {
err = websocketConn.conn.WriteMessage(websocket.BinaryMessage, p) err = websocketConn.conn.WriteMessage(websocket.BinaryMessage, p)
if err == nil { if err == nil {
@ -37,3 +47,7 @@ func (websocketConn *WebSocketConn) Write(p []byte) (n int, err error) {
func (websocketConn *WebSocketConn) Close() error { func (websocketConn *WebSocketConn) Close() error {
return websocketConn.conn.Close() return websocketConn.conn.Close()
} }
func (websocketConn *WebSocketConn) RemoteAddr() net.Addr {
return websocketConn.conn.RemoteAddr()
}

View File

@ -14,7 +14,7 @@ func SynchronizeStreams(stream1, stream2 io.ReadWriter) {
}() }()
_, err := io.Copy(stream1, stream2) _, err := io.Copy(stream1, stream2)
if err != nil { if err != nil {
log.Printf("error %v\n", err) log.Printf("sync streams error(1) %v\n", err)
} }
}() }()
@ -23,7 +23,7 @@ func SynchronizeStreams(stream1, stream2 io.ReadWriter) {
waitChannel <- true waitChannel <- true
}() }()
_, err := io.Copy(stream2, stream1) _, err := io.Copy(stream2, stream1)
log.Printf("Error %v\n", err) log.Printf("sync streams error(2) %v\n", err)
}() }()
<-waitChannel <-waitChannel