summaryrefslogtreecommitdiff
path: root/internal/clients/connectors/serverconnection.go
diff options
context:
space:
mode:
authorPaul Buetow <pbuetow@mimecast.com>2022-02-04 21:37:29 +0000
committerPaul Buetow <pbuetow@mimecast.com>2022-02-04 21:37:29 +0000
commit20bd2a6330b2a5da3dc42c92e4c4e634c78561ff (patch)
treecab22128af9e54131facd0062a474459383a1c90 /internal/clients/connectors/serverconnection.go
parent8c714057a07ab494689c9e262d95519e34c204e1 (diff)
parent1e205898c1270915b192db51acfdfc6e1a92e3e3 (diff)
merge 4.0.0-RC
Diffstat (limited to 'internal/clients/connectors/serverconnection.go')
-rw-r--r--internal/clients/connectors/serverconnection.go206
1 files changed, 206 insertions, 0 deletions
diff --git a/internal/clients/connectors/serverconnection.go b/internal/clients/connectors/serverconnection.go
new file mode 100644
index 0000000..aeb2a41
--- /dev/null
+++ b/internal/clients/connectors/serverconnection.go
@@ -0,0 +1,206 @@
+package connectors
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/mimecast/dtail/internal/clients/handlers"
+ "github.com/mimecast/dtail/internal/config"
+ "github.com/mimecast/dtail/internal/io/dlog"
+ "github.com/mimecast/dtail/internal/ssh/client"
+
+ "golang.org/x/crypto/ssh"
+)
+
+// ServerConnection represents a connection to a single remote dtail server via
+// SSH protocol.
+type ServerConnection struct {
+ // The full server string as received from the server discovery (can be with port number)
+ server string
+ // Only the hostname or FQDN (without the port number)
+ hostname string
+ // Only the port number.
+ port int
+ config *ssh.ClientConfig
+ handler handlers.Handler
+ commands []string
+ hostKeyCallback client.HostKeyCallback
+ throttlingDone bool
+}
+
+// NewServerConnection returns a new DTail SSH server connection.
+func NewServerConnection(server string, userName string,
+ authMethods []ssh.AuthMethod, hostKeyCallback client.HostKeyCallback,
+ handler handlers.Handler, commands []string) *ServerConnection {
+
+ dlog.Client.Debug(server, "Creating new connection", server, handler, commands)
+ c := ServerConnection{
+ hostKeyCallback: hostKeyCallback,
+ server: server,
+ handler: handler,
+ commands: commands,
+ config: &ssh.ClientConfig{
+ User: userName,
+ Auth: authMethods,
+ HostKeyCallback: hostKeyCallback.Wrap(),
+ Timeout: time.Second * 2,
+ },
+ }
+
+ c.initServerPort()
+ return &c
+}
+
+// Server returns the server hostname connected to.
+func (c *ServerConnection) Server() string { return c.server }
+
+// Handler returns the handler used for the connection.
+func (c *ServerConnection) Handler() handlers.Handler { return c.handler }
+
+// Attempt to parse the server port address from the provided server FQDN.
+func (c *ServerConnection) initServerPort() {
+ parts := strings.Split(c.server, ":")
+ if len(parts) == 1 {
+ c.hostname = c.server
+ c.port = config.Common.SSHPort
+ return
+ }
+
+ dlog.Client.Debug("Parsing port from hostname", parts)
+ port, err := strconv.Atoi(parts[1])
+ if err != nil {
+ dlog.Client.FatalPanic("Unable to parse client port", c.server, parts, err)
+ }
+ c.hostname = parts[0]
+ c.port = port
+}
+
+// Start the connection to the server.
+func (c *ServerConnection) Start(ctx context.Context, cancel context.CancelFunc,
+ throttleCh, statsCh chan struct{}) {
+
+ // Throttle how many connections can be established concurrently (based on ch length)
+ dlog.Client.Debug(c.server, "Throttling connection", len(throttleCh), cap(throttleCh))
+
+ select {
+ case throttleCh <- struct{}{}:
+ case <-ctx.Done():
+ dlog.Client.Debug(c.server, "Not establishing connection as context is done",
+ len(throttleCh), cap(throttleCh))
+ return
+ }
+
+ dlog.Client.Debug(c.server, "Throttling says that the connection can be established",
+ len(throttleCh), cap(throttleCh))
+
+ go func() {
+ defer func() {
+ if !c.throttlingDone {
+ dlog.Client.Debug(c.server, "Unthrottling connection (1)",
+ len(throttleCh), cap(throttleCh))
+ c.throttlingDone = true
+ <-throttleCh
+ }
+ cancel()
+ }()
+
+ if err := c.dial(ctx, cancel, throttleCh, statsCh); err != nil {
+ dlog.Client.Warn(c.server, err)
+ if c.hostKeyCallback.Untrusted(c.server) {
+ dlog.Client.Debug(c.server, "Not trusting host")
+ }
+ }
+ }()
+
+ <-ctx.Done()
+}
+
+// Dail into a new SSH connection. Close connection in case of an error.
+func (c *ServerConnection) dial(ctx context.Context, cancel context.CancelFunc,
+ throttleCh, statsCh chan struct{}) error {
+
+ dlog.Client.Debug(c.server, "Incrementing connection stats")
+ statsCh <- struct{}{}
+ defer func() {
+ dlog.Client.Debug(c.server, "Decrementing connection stats")
+ <-statsCh
+ }()
+
+ address := fmt.Sprintf("%s:%d", c.hostname, c.port)
+ dlog.Client.Debug(c.server, "Dialing into the connection", address)
+
+ client, err := ssh.Dial("tcp", address, c.config)
+ if err != nil {
+ return err
+ }
+ defer client.Close()
+
+ return c.session(ctx, cancel, client, throttleCh)
+}
+
+// Create the SSH session. Close the session in case of an error.
+func (c *ServerConnection) session(ctx context.Context, cancel context.CancelFunc,
+ client *ssh.Client, throttleCh chan struct{}) error {
+
+ dlog.Client.Debug(c.server, "Creating SSH session")
+ session, err := client.NewSession()
+ if err != nil {
+ return err
+ }
+ defer session.Close()
+ return c.handle(ctx, cancel, session, throttleCh)
+}
+
+func (c *ServerConnection) handle(ctx context.Context, cancel context.CancelFunc,
+ session *ssh.Session, throttleCh chan struct{}) error {
+
+ dlog.Client.Debug(c.server, "Creating handler for SSH session")
+ stdinPipe, err := session.StdinPipe()
+ if err != nil {
+ return err
+ }
+ stdoutPipe, err := session.StdoutPipe()
+ if err != nil {
+ return err
+ }
+ if err := session.Shell(); err != nil {
+ return err
+ }
+
+ go func() {
+ io.Copy(stdinPipe, c.handler)
+ cancel()
+ }()
+ go func() {
+ io.Copy(c.handler, stdoutPipe)
+ cancel()
+ }()
+ go func() {
+ select {
+ case <-c.handler.Done():
+ case <-ctx.Done():
+ }
+ cancel()
+ }()
+
+ // Send all commands to client.
+ for _, command := range c.commands {
+ dlog.Client.Debug(command)
+ c.handler.SendMessage(command)
+ }
+
+ if !c.throttlingDone {
+ dlog.Client.Debug(c.server, "Unthrottling connection (2)",
+ len(throttleCh), cap(throttleCh))
+ c.throttlingDone = true
+ <-throttleCh
+ }
+
+ <-ctx.Done()
+ c.handler.Shutdown()
+ return nil
+}