Skip to content

Commit 335dbdc

Browse files
author
Patrick Downey
committed
Add support allow routes to be added after the tcpproxy is running.
Inspired by inetaf#15
1 parent 3062c12 commit 335dbdc

File tree

5 files changed

+163
-6
lines changed

5 files changed

+163
-6
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
.idea
12
tlsrouter
23
tlsrouter.test

http.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ func (p *Proxy) AddHTTPHostRoute(ipPort, httpHost string, dest Target) {
3131
p.AddHTTPHostMatchRoute(ipPort, equals(httpHost), dest)
3232
}
3333

34+
// RemoveHTTPHostRoute removes a route to the ipPort listener
35+
//
36+
// The ipPort is any net.Listen TCP address. If it hasn't been registered with
37+
// tcpproxy this is a noop
38+
func (p *Proxy) RemoveHTTPHostRoute(ipPort, httpHost string, dest Target) {
39+
p.RemoveHTTPHostMatchRoute(ipPort, equals(httpHost), dest)
40+
}
41+
3442
// AddHTTPHostMatchRoute appends a route to the ipPort listener that
3543
// routes to dest if the incoming HTTP/1.x Host header name is
3644
// accepted by matcher. If it doesn't match, rule processing continues
@@ -41,6 +49,14 @@ func (p *Proxy) AddHTTPHostMatchRoute(ipPort string, match Matcher, dest Target)
4149
p.addRoute(ipPort, httpHostMatch{match, dest})
4250
}
4351

52+
// RemoveHTTPHostMatchRoute removes a route to the ipPort listener
53+
//
54+
// The ipPort is any net.Listen TCP address. If it hasn't been registered with
55+
// tcpproxy this is a noop
56+
func (p *Proxy) RemoveHTTPHostMatchRoute(ipPort string, match Matcher, dest Target) {
57+
p.removeRoute(ipPort, httpHostMatch{match, dest})
58+
}
59+
4460
type httpHostMatch struct {
4561
matcher Matcher
4662
target Target

sni.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ func (p *Proxy) AddSNIRoute(ipPort, sni string, dest Target) {
3838
p.AddSNIMatchRoute(ipPort, equals(sni), dest)
3939
}
4040

41+
// RemoveSNIRoute removes a route from the ipPort listener
42+
//
43+
// The ipPort is any net.Listen TCP address, if it hasn't been registered with
44+
// tcpproxy this is a noop.
45+
func (p *Proxy) RemoveSNIRoute(ipPort, sni string, dest Target) {
46+
p.RemoveSNIMatchRoute(ipPort, equals(sni), dest)
47+
}
48+
4149
// AddSNIMatchRoute appends a route to the ipPort listener that routes
4250
// to dest if the incoming TLS SNI server name is accepted by
4351
// matcher. If it doesn't match, rule processing continues for any
@@ -60,6 +68,21 @@ func (p *Proxy) AddSNIMatchRoute(ipPort string, matcher Matcher, dest Target) {
6068
p.addRoute(ipPort, sniMatch{matcher, dest})
6169
}
6270

71+
// RemoveSNIMatchRoute removes a route from the ipPort listener
72+
//
73+
// The ipPort is any net.Listen TCP address, if it hasn't been registered with
74+
// tcpproxy this is a noop.
75+
func (p *Proxy) RemoveSNIMatchRoute(ipPort string, matcher Matcher, dest Target) {
76+
77+
if p.configExists(ipPort) {
78+
cfg := p.configFor(ipPort)
79+
80+
p.removeRoute(ipPort, &acmeMatch{cfg})
81+
82+
p.removeRoute(ipPort, sniMatch{matcher, dest})
83+
}
84+
}
85+
6386
// AddStopACMESearch prevents ACME probing of subsequent SNI routes.
6487
// Any ACME challenges on ipPort for SNI routes previously added
6588
// before this call will still be proxied to all possible SNI

tcpproxy.go

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import (
6060
"io"
6161
"log"
6262
"net"
63+
"sync"
6364
"time"
6465
)
6566

@@ -69,6 +70,7 @@ import (
6970
// The order that routes are added in matters; each is matched in the order
7071
// registered.
7172
type Proxy struct {
73+
mu sync.Mutex
7274
configs map[string]*config // ip:port => config
7375

7476
lns []net.Listener
@@ -81,6 +83,14 @@ type Proxy struct {
8183
ListenFunc func(net, laddr string) (net.Listener, error)
8284
}
8385

86+
//
87+
//func (p *Proxy) Configs() map[string]*config {
88+
// p.mu.Lock()
89+
// defer p.mu.Unlock()
90+
//
91+
// return p.configs
92+
//}
93+
8494
// Matcher reports whether hostname matches the Matcher's criteria.
8595
type Matcher func(ctx context.Context, hostname string) bool
8696

@@ -93,11 +103,42 @@ func equals(want string) Matcher {
93103

94104
// config contains the proxying state for one listener.
95105
type config struct {
106+
mu sync.Mutex
107+
96108
routes []route
97109
acmeTargets []Target // accumulates targets that should be probed for acme.
98110
stopACME bool // if true, AddSNIRoute doesn't add targets to acmeTargets.
99111
}
100112

113+
func (c *config) AddRoute(r route) {
114+
c.mu.Lock()
115+
defer c.mu.Unlock()
116+
117+
c.routes = append(c.routes, r)
118+
}
119+
120+
func (c *config) Routes() []route {
121+
c.mu.Lock()
122+
defer c.mu.Unlock()
123+
124+
return c.routes
125+
}
126+
127+
func (c *config) RemoveRoute(r route) {
128+
c.mu.Lock()
129+
defer c.mu.Unlock()
130+
131+
var newRoutes []route
132+
133+
for _, i := range c.routes {
134+
if i != r {
135+
newRoutes = append(newRoutes, i)
136+
}
137+
}
138+
139+
c.routes = newRoutes
140+
}
141+
101142
// A route matches a connection to a target.
102143
type route interface {
103144
// match examines the initial bytes of a connection, looking for a
@@ -121,6 +162,9 @@ func (p *Proxy) netListen() func(net, laddr string) (net.Listener, error) {
121162
}
122163

123164
func (p *Proxy) configFor(ipPort string) *config {
165+
p.mu.Lock()
166+
defer p.mu.Unlock()
167+
124168
if p.configs == nil {
125169
p.configs = make(map[string]*config)
126170
}
@@ -130,9 +174,20 @@ func (p *Proxy) configFor(ipPort string) *config {
130174
return p.configs[ipPort]
131175
}
132176

177+
func (p *Proxy) configExists(ipPort string) bool {
178+
return p.configs[ipPort] != nil
179+
}
180+
133181
func (p *Proxy) addRoute(ipPort string, r route) {
134182
cfg := p.configFor(ipPort)
135-
cfg.routes = append(cfg.routes, r)
183+
cfg.AddRoute(r)
184+
}
185+
186+
func (p *Proxy) removeRoute(ipPort string, r route) {
187+
if p.configExists(ipPort) {
188+
cfg := p.configFor(ipPort)
189+
cfg.RemoveRoute(r)
190+
}
136191
}
137192

138193
// AddRoute appends an always-matching route to the ipPort listener,
@@ -146,6 +201,13 @@ func (p *Proxy) AddRoute(ipPort string, dest Target) {
146201
p.addRoute(ipPort, fixedTarget{dest})
147202
}
148203

204+
// RemoveRoute removes the specified target from the ipPort listener
205+
//
206+
// This method won't remove an ipPort listener if there are no routes remaining
207+
func (p *Proxy) RemoveRoute(ipPort string, dest Target) {
208+
p.removeRoute(ipPort, fixedTarget{dest})
209+
}
210+
149211
type fixedTarget struct {
150212
t Target
151213
}
@@ -200,7 +262,7 @@ func (p *Proxy) Start() error {
200262
return err
201263
}
202264
p.lns = append(p.lns, ln)
203-
go p.serveListener(errc, ln, config.routes)
265+
go p.serveListener(errc, ln, config)
204266
}
205267
go p.awaitFirstError(errc)
206268
return nil
@@ -211,22 +273,22 @@ func (p *Proxy) awaitFirstError(errc <-chan error) {
211273
close(p.donec)
212274
}
213275

214-
func (p *Proxy) serveListener(ret chan<- error, ln net.Listener, routes []route) {
276+
func (p *Proxy) serveListener(ret chan<- error, ln net.Listener, cfg *config) {
215277
for {
216278
c, err := ln.Accept()
217279
if err != nil {
218280
ret <- err
219281
return
220282
}
221-
go p.serveConn(c, routes)
283+
go p.serveConn(c, cfg)
222284
}
223285
}
224286

225287
// serveConn runs in its own goroutine and matches c against routes.
226288
// It returns whether it matched purely for testing.
227-
func (p *Proxy) serveConn(c net.Conn, routes []route) bool {
289+
func (p *Proxy) serveConn(c net.Conn, cfg *config) bool {
228290
br := bufio.NewReader(c)
229-
for _, route := range routes {
291+
for _, route := range cfg.Routes() {
230292
if target, hostName := route.match(br); target != nil {
231293
if n := br.Buffered(); n > 0 {
232294
peeked, _ := br.Peek(br.Buffered())

tcpproxy_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,61 @@ func testProxy(t *testing.T, front net.Listener) *Proxy {
175175
}
176176
}
177177

178+
func TestConfigAddRemoveRoute(t *testing.T) {
179+
r1 := fixedTarget{To("foo.com:443")}
180+
r2 := fixedTarget{To("bar.com:443")}
181+
182+
cfg := &config{}
183+
184+
cfg.AddRoute(r1)
185+
cfg.AddRoute(r2)
186+
187+
routes := cfg.Routes()
188+
if len(routes) != 2 {
189+
t.Fatalf("got %d routes, want 1", len(routes))
190+
}
191+
192+
cfg.RemoveRoute(r1)
193+
194+
routes = cfg.Routes()
195+
if len(routes) != 1 {
196+
t.Fatalf("got %d routes, want 1", len(routes))
197+
}
198+
199+
if routes[0] != r2 {
200+
t.Fatalf("got %s, want %s", r2, routes[0])
201+
}
202+
}
203+
204+
func TestProxyAddRemoveRoute(t *testing.T) {
205+
front := newLocalListener(t)
206+
defer front.Close()
207+
208+
r1 := To("foo.com:443")
209+
r1Fixed := fixedTarget{r1}
210+
r2 := To("bar.com:443")
211+
212+
p := testProxy(t, front)
213+
p.AddRoute(testFrontAddr, r1)
214+
p.AddRoute(testFrontAddr, r2)
215+
216+
config := p.configFor(testFrontAddr)
217+
routes := config.Routes()
218+
if len(routes) != 2 {
219+
t.Fatalf("got %d routes, want 2", len(routes))
220+
}
221+
222+
p.RemoveRoute(testFrontAddr, r2)
223+
routes = config.Routes()
224+
if len(routes) != 1 {
225+
t.Fatalf("got %d routes, want 1", len(routes))
226+
}
227+
228+
if routes[0] != r1Fixed {
229+
t.Fatalf("got %s, want %s", r1Fixed, routes[0])
230+
}
231+
}
232+
178233
func TestProxyAlwaysMatch(t *testing.T) {
179234
front := newLocalListener(t)
180235
defer front.Close()

0 commit comments

Comments
 (0)