package sshmuxer import ( "encoding/base64" "fmt" "log/slog" "net" "net/url" "strings" "github.com/antoniomika/multilistener" "github.com/antoniomika/sish/utils" "github.com/antoniomika/syncmap" "github.com/logrusorgru/aurora" "github.com/pires/go-proxyproto" "github.com/spf13/viper" "github.com/vulcand/oxy/v2/roundrobin" ) // handleTCPListener handles the creation of the tcpHandler // (or addition for load balancing) and set's up the underlying listeners. func handleTCPListener(check *channelForwardMsg, bindPort uint32, requestMessages string, listenerHolder *utils.ListenerHolder, state *utils.State, sshConn *utils.SSHConnection, sniProxyEnabled bool) (*utils.TCPHolder, *roundrobin.RoundRobin, string, *url.URL, string, string, error) { tcpAddr, tcpPort, tH := utils.GetOpenPort(check.Addr, bindPort, state, sshConn, sniProxyEnabled) if tcpPort != bindPort && viper.GetBool("force-requested-ports") { return nil, nil, "", nil, "", "", fmt.Errorf("error assigning requested port to tunnel") } var balancer *roundrobin.RoundRobin balancerName := "" if tH != nil && tH.SNIProxy { balancerName = check.Addr } if tH == nil { lis, err := utils.Listen(tcpAddr) if err != nil { slog.Error( "error listening on address", "err", err, ) return nil, nil, "", nil, "", "", err } tH = &utils.TCPHolder{ TCPHost: tcpAddr, SSHConnections: syncmap.New[string, *utils.SSHConnection](), Balancers: syncmap.New[string, *roundrobin.RoundRobin](), SNIProxy: sshConn.SNIProxy, } if sshConn.SNIProxy { balancerName = check.Addr } var l net.Listener if viper.GetBool("proxy-protocol-listener") { ln := &proxyproto.Listener{ Listener: lis, } utils.LoadProxyProtoConfig(ln) l = ln } else { l = lis } tH.Listener = l state.Listeners.Store(tcpAddr, l) state.TCPListeners.Store(tcpAddr, tH) } if sniProxyEnabled { newName, err := utils.GetOpenSNIHost(balancerName, state, sshConn, tH) if err != nil || (!strings.HasPrefix(newName, check.Addr) && viper.GetBool("force-requested-subdomains")) { return nil, nil, "", nil, "", "", fmt.Errorf("error assigning requested address to tunnel") } balancerName = newName } foundBalancer, ok := tH.Balancers.Load(balancerName) if ok { balancer = foundBalancer } else { newBalancer, err := roundrobin.New(nil) if err != nil { slog.Error( "error initializing tcp balancer", "err", err, ) return nil, nil, "", nil, "", "", err } tH.Balancers.Store(balancerName, newBalancer) balancer = newBalancer } tH.SSHConnections.Store(listenerHolder.Addr().String(), sshConn) serverURL := &url.URL{ Host: base64.StdEncoding.EncodeToString([]byte(listenerHolder.Addr().String())), } err := balancer.UpsertServer(serverURL) if err != nil { slog.Error( "unable to add server to balancer", "err", err, ) } domainName := viper.GetString("domain") if balancerName != "" { domainName = balancerName } connType := "TCP" if sniProxyEnabled { connType = "TLS" } listenPort := tH.Listener.Addr().(*multilistener.MultiListener).Addresses()[0].(*net.TCPAddr).Port requestMessages += sshConn.FormatLog( slog.LevelInfo, fmt.Sprintf("%s: %s:%d\r\n", aurora.BgBlue(connType), domainName, listenPort), "forwarding started", "forwardingType", connType, "addr", fmt.Sprintf("%s:%d", domainName, listenPort), ) sshConn.Logger().Info( "forwarding started", "forwardingType", connType, "addr", fmt.Sprintf("%s:%d", domainName, listenPort), "listenerAddr", listenerHolder.Addr().String(), ) return tH, balancer, balancerName, serverURL, tcpAddr, requestMessages, nil }