File: tcp_proxy.go

package info (click to toggle)
receptor 1.5.5-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 2,772 kB
  • sloc: python: 1,643; makefile: 305; sh: 174
file content (209 lines) | stat: -rw-r--r-- 7,146 bytes parent folder | download | duplicates (2)
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))
}