Skip to content

Commit de1c7de

Browse files
committed
Add support for arbitrary matching against HTTP and SNI hostnames.
Add{HTTPHost,SNI}Route remain so that the common case of exact matches remains trivial to use. Add{HTTPHost,SNI}MatchRoute allow you to specify your own matching function. Fixes #9
1 parent c6a0996 commit de1c7de

File tree

4 files changed

+52
-17
lines changed

4 files changed

+52
-17
lines changed

http.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,37 @@ package tcpproxy
1717
import (
1818
"bufio"
1919
"bytes"
20+
"context"
2021
"net/http"
2122
)
2223

23-
// AddHTTPHostRoute appends a route to the ipPort listener that says
24-
// if the incoming HTTP/1.x Host header name is httpHost, the
25-
// connection is given to dest. If it doesn't match, rule processing
26-
// continues for any additional routes on ipPort.
24+
// AddHTTPHostRoute appends a route to the ipPort listener that
25+
// routes to dest if the incoming HTTP/1.x Host header name is
26+
// httpHost. If it doesn't match, rule processing continues for any
27+
// additional routes on ipPort.
2728
//
2829
// The ipPort is any valid net.Listen TCP address.
2930
func (p *Proxy) AddHTTPHostRoute(ipPort, httpHost string, dest Target) {
30-
p.addRoute(ipPort, httpHostMatch{httpHost, dest})
31+
p.AddHTTPHostMatchRoute(ipPort, equals(httpHost), dest)
32+
}
33+
34+
// AddHTTPHostMatchRoute appends a route to the ipPort listener that
35+
// routes to dest if the incoming HTTP/1.x Host header name is
36+
// accepted by matcher. If it doesn't match, rule processing continues
37+
// for any additional routes on ipPort.
38+
//
39+
// The ipPort is any valid net.Listen TCP address.
40+
func (p *Proxy) AddHTTPHostMatchRoute(ipPort string, match Matcher, dest Target) {
41+
p.addRoute(ipPort, httpHostMatch{match, dest})
3142
}
3243

3344
type httpHostMatch struct {
34-
host string
35-
target Target
45+
matcher Matcher
46+
target Target
3647
}
3748

3849
func (m httpHostMatch) match(br *bufio.Reader) Target {
39-
if httpHostHeader(br) == m.host {
50+
if m.matcher(context.TODO(), httpHostHeader(br)) {
4051
return m.target
4152
}
4253
return nil

sni.go

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,31 @@ import (
2424
"strings"
2525
)
2626

27-
// AddSNIRoute appends a route to the ipPort listener that says if the
28-
// incoming TLS SNI server name is sni, the connection is given to
29-
// dest. If it doesn't match, rule processing continues for any
30-
// additional routes on ipPort.
27+
// AddSNIRoute appends a route to the ipPort listener that routes to
28+
// dest if the incoming TLS SNI server name is sni. If it doesn't
29+
// match, rule processing continues for any additional routes on
30+
// ipPort.
3131
//
3232
// By default, the proxy will route all ACME tls-sni-01 challenges
3333
// received on ipPort to all SNI dests. You can disable ACME routing
3434
// with AddStopACMESearch.
3535
//
3636
// The ipPort is any valid net.Listen TCP address.
3737
func (p *Proxy) AddSNIRoute(ipPort, sni string, dest Target) {
38+
p.AddSNIMatchRoute(ipPort, equals(sni), dest)
39+
}
40+
41+
// AddSNIMatchRoute appends a route to the ipPort listener that routes
42+
// to dest if the incoming TLS SNI server name is accepted by
43+
// matcher. If it doesn't match, rule processing continues for any
44+
// additional routes on ipPort.
45+
//
46+
// By default, the proxy will route all ACME tls-sni-01 challenges
47+
// received on ipPort to all SNI dests. You can disable ACME routing
48+
// with AddStopACMESearch.
49+
//
50+
// The ipPort is any valid net.Listen TCP address.
51+
func (p *Proxy) AddSNIMatchRoute(ipPort string, matcher Matcher, dest Target) {
3852
cfg := p.configFor(ipPort)
3953
if !cfg.stopACME {
4054
if len(cfg.acmeTargets) == 0 {
@@ -43,7 +57,7 @@ func (p *Proxy) AddSNIRoute(ipPort, sni string, dest Target) {
4357
cfg.acmeTargets = append(cfg.acmeTargets, dest)
4458
}
4559

46-
p.addRoute(ipPort, sniMatch{sni, dest})
60+
p.addRoute(ipPort, sniMatch{matcher, dest})
4761
}
4862

4963
// AddStopACMESearch prevents ACME probing of subsequent SNI routes.
@@ -55,12 +69,12 @@ func (p *Proxy) AddStopACMESearch(ipPort string) {
5569
}
5670

5771
type sniMatch struct {
58-
sni string
59-
target Target
72+
matcher Matcher
73+
target Target
6074
}
6175

6276
func (m sniMatch) match(br *bufio.Reader) Target {
63-
if clientHelloServerName(br) == string(m.sni) {
77+
if m.matcher(context.TODO(), clientHelloServerName(br)) {
6478
return m.target
6579
}
6680
return nil

tcpproxy.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ type Proxy struct {
8181
ListenFunc func(net, laddr string) (net.Listener, error)
8282
}
8383

84+
// Matcher reports whether hostname matches the Matcher's criteria.
85+
type Matcher func(ctx context.Context, hostname string) bool
86+
87+
// equals is a trivial Matcher that implements string equality.
88+
func equals(want string) Matcher {
89+
return func(_ context.Context, got string) bool {
90+
return want == got
91+
}
92+
}
93+
8494
// config contains the proxying state for one listener.
8595
type config struct {
8696
routes []route

tcpproxy_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func TestMatchHTTPHost(t *testing.T) {
7171
}
7272
t.Run(name, func(t *testing.T) {
7373
br := bufio.NewReader(tt.r)
74-
r := httpHostMatch{tt.host, noopTarget{}}
74+
r := httpHostMatch{equals(tt.host), noopTarget{}}
7575
got := r.match(br) != nil
7676
if got != tt.want {
7777
t.Fatalf("match = %v; want %v", got, tt.want)

0 commit comments

Comments
 (0)