1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
|
package services
import (
"crypto/tls"
"fmt"
"io"
"net"
"strconv"
"github.com/ansible/receptor/pkg/logger"
"github.com/ansible/receptor/pkg/netceptor"
"github.com/ansible/receptor/pkg/utils"
"github.com/ghjm/cmdline"
"github.com/spf13/viper"
)
//go:generate mockgen -package mock_services -source=tcp_proxy.go -destination=mock_services/tcp_proxy.go
type NetcForTCPProxy interface {
GetLogger() *logger.ReceptorLogger
Dial(node string, service string, tlscfg *tls.Config) (*netceptor.Conn, error)
ListenAndAdvertise(service string, tlscfg *tls.Config, tags map[string]string) (*netceptor.Listener, error)
}
// Interface for the net library to generate stubs with mockgen.
type NetLib interface {
Listen(network string, address string) (net.Listener, error)
Dial(network string, address string) (net.Conn, error)
}
type NetTCPWrapper struct{}
func (n *NetTCPWrapper) Listen(network string, address string) (net.Listener, error) {
return net.Listen(network, address)
}
func (n *NetTCPWrapper) Dial(network string, address string) (net.Conn, error) {
return net.Dial(network, address)
}
// Interface for the tls library to generate stubs with mockgen.
type TLSLib interface {
NewListener(inner net.Listener, config *tls.Config) net.Listener
Dial(network string, addr string, config *tls.Config) (*tls.Conn, error)
}
type TLSTCPWrapper struct{}
func (n *TLSTCPWrapper) NewListener(inner net.Listener, config *tls.Config) net.Listener {
return tls.NewListener(inner, config)
}
func (n *TLSTCPWrapper) Dial(network string, addr string, config *tls.Config) (*tls.Conn, error) {
return tls.Dial(network, addr, config)
}
// Interface for the Net Listener to generate stubs with mockgen.
type NetListenerTCP interface {
net.Listener
}
// Interface for the utils package to generate stubs with mockgen.
type UtilsLib interface {
BridgeConns(c1 io.ReadWriteCloser, c1Name string, c2 io.ReadWriteCloser, c2Name string, logger *logger.ReceptorLogger)
}
type UtilsTCPWrapper struct{}
func (u *UtilsTCPWrapper) BridgeConns(c1 io.ReadWriteCloser, c1Name string, c2 io.ReadWriteCloser, c2Name string, logger *logger.ReceptorLogger) {
utils.BridgeConns(c1, c1Name, c2, c2Name, logger)
}
// Interface to mock the Connection object returned from Accept.
type TCPConn interface {
net.Conn
}
// TCPProxyServiceInbound listens on a TCP port and forwards the connection over the Receptor network.
func TCPProxyServiceInbound(s NetcForTCPProxy, host string, port int, tlsServer *tls.Config,
node string, rservice string, tlsClient *tls.Config, netTCP NetLib, tlsTCP TLSLib, utilsTCP UtilsLib,
) error {
tli, err := netTCP.Listen("tcp", net.JoinHostPort(host, strconv.Itoa(port)))
if tlsServer != nil {
tli = tlsTCP.NewListener(tli, tlsServer)
}
if err != nil {
return fmt.Errorf("error listening on TCP: %s", err)
}
go func() {
for {
tc, err := tli.Accept()
if err != nil {
s.GetLogger().Error("error accepting TCP connection: %s\n", err)
return
}
qc, err := s.Dial(node, rservice, tlsClient)
if err != nil {
s.GetLogger().Error("error connecting on Receptor network: %s\n", err)
continue
}
go utilsTCP.BridgeConns(tc, "tcp service", qc, "receptor connection", s.GetLogger())
}
}()
return nil
}
// TCPProxyServiceOutbound listens on the Receptor network and forwards the connection via TCP.
func TCPProxyServiceOutbound(s NetcForTCPProxy, service string, tlsServer *tls.Config,
address string, tlsClient *tls.Config, netTCP NetLib, tlsTCP TLSLib, utilsTCP UtilsLib,
) error {
qli, err := s.ListenAndAdvertise(service, tlsServer, map[string]string{
"type": "TCP Proxy",
"address": address,
})
if err != nil {
return fmt.Errorf("error listening on Receptor network: %s", err)
}
go func() {
for {
qc, err := qli.Accept()
if err != nil {
s.GetLogger().Error("Error accepting connection on Receptor network: %s\n", err)
return
}
var tc net.Conn
if tlsClient == nil {
tc, err = netTCP.Dial("tcp", address)
} else {
tc, err = tlsTCP.Dial("tcp", address, tlsClient)
}
if err != nil {
s.GetLogger().Error("Error connecting via TCP: %s\n", err)
continue
}
go utilsTCP.BridgeConns(qc, "receptor service", tc, "tcp connection", s.GetLogger())
}
}()
return nil
}
// tcpProxyInboundCfg is the cmdline configuration object for a TCP inbound proxy.
type TCPProxyInboundCfg struct {
Port int `required:"true" description:"Local TCP port to bind to"`
BindAddr string `description:"Address to bind TCP listener to" default:"0.0.0.0"`
RemoteNode string `required:"true" description:"Receptor node to connect to"`
RemoteService string `required:"true" description:"Receptor service name to connect to"`
TLSServer string `description:"Name of TLS server config for the TCP listener"`
TLSClient string `description:"Name of TLS client config for the Receptor connection"`
}
// Run runs the action.
func (cfg TCPProxyInboundCfg) Run() error {
netceptor.MainInstance.Logger.Debug("Running TCP inbound proxy service %v\n", cfg)
tlsClientCfg, err := netceptor.MainInstance.GetClientTLSConfig(cfg.TLSClient, cfg.RemoteNode, netceptor.ExpectedHostnameTypeReceptor)
if err != nil {
return err
}
TLSServerConfig, err := netceptor.MainInstance.GetServerTLSConfig(cfg.TLSServer)
if err != nil {
return err
}
return TCPProxyServiceInbound(netceptor.MainInstance, cfg.BindAddr, cfg.Port, TLSServerConfig,
cfg.RemoteNode, cfg.RemoteService, tlsClientCfg, &NetTCPWrapper{}, &TLSTCPWrapper{}, &UtilsTCPWrapper{})
}
// tcpProxyOutboundCfg is the cmdline configuration object for a TCP outbound proxy.
type TCPProxyOutboundCfg struct {
Service string `required:"true" description:"Receptor service name to bind to"`
Address string `required:"true" description:"Address for outbound TCP connection"`
TLSServer string `description:"Name of TLS server config for the Receptor service"`
TLSClient string `description:"Name of TLS client config for the TCP connection"`
}
// Run runs the action.
func (cfg TCPProxyOutboundCfg) Run() error {
netceptor.MainInstance.Logger.Debug("Running TCP inbound proxy service %s\n", cfg)
TLSServerConfig, err := netceptor.MainInstance.GetServerTLSConfig(cfg.TLSServer)
if err != nil {
return err
}
host, _, err := net.SplitHostPort(cfg.Address)
if err != nil {
return err
}
tlsClientCfg, err := netceptor.MainInstance.GetClientTLSConfig(cfg.TLSClient, host, netceptor.ExpectedHostnameTypeDNS)
if err != nil {
return err
}
return TCPProxyServiceOutbound(netceptor.MainInstance, cfg.Service, TLSServerConfig, cfg.Address, tlsClientCfg, &NetTCPWrapper{}, &TLSTCPWrapper{}, &UtilsTCPWrapper{})
}
func init() {
version := viper.GetInt("version")
if version > 1 {
return
}
cmdline.RegisterConfigTypeForApp("receptor-proxies",
"tcp-server", "Listen for TCP and forward via Receptor", TCPProxyInboundCfg{}, cmdline.Section(servicesSection))
cmdline.RegisterConfigTypeForApp("receptor-proxies",
"tcp-client", "Listen on a Receptor service and forward via TCP", TCPProxyOutboundCfg{}, cmdline.Section(servicesSection))
}
|