ipn/ipnlocal: Support TCP and Web VIP services
This commit intend to provide support for TCP and Web VIP services and also allow user to use Tun for VIP services if they want to. The commit includes: 1.Setting TCP intercept function for VIP Services. 2.Update netstack to send packet written from WG to netStack handler for VIP service. 3.Return correct TCP hander for VIP services when netstack acceptTCP. This commit also includes unit tests for if the local backend setServeConfig would set correct TCP intercept function and test if a hander gets returned when getting TCPHandlerForDst. The shouldProcessInbound check is not unit tested since the test result just depends on mocked functions. There should be an integration test to cover shouldProcessInbound and if the returned TCP handler actually does what the serveConfig says. Updates tailscale/corp#24604 Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com>
This commit is contained in:
@@ -296,6 +296,203 @@ func TestServeConfigForeground(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestServeConfigServices tests the side effects of setting the
|
||||
// Services field in a ServeConfig. The Services field is a map
|
||||
// of all services the current service host is serving. Unlike what we
|
||||
// serve for node itself, there is no foreground and no local handlers
|
||||
// for the services. So the only things we need to test are if the
|
||||
// services configured are valid and if they correctly set intercept
|
||||
// functions for netStack.
|
||||
func TestServeConfigServices(t *testing.T) {
|
||||
b := newTestBackend(t)
|
||||
svcIPMap := tailcfg.ServiceIPMappings{
|
||||
"svc:foo": []netip.Addr{
|
||||
netip.MustParseAddr("100.101.101.101"),
|
||||
netip.MustParseAddr("fd7a:115c:a1e0:ab12:4843:cd96:6565:6565"),
|
||||
},
|
||||
"svc:bar": []netip.Addr{
|
||||
netip.MustParseAddr("100.99.99.99"),
|
||||
netip.MustParseAddr("fd7a:115c:a1e0:ab12:4843:cd96:626b:628b"),
|
||||
},
|
||||
}
|
||||
svcIPMapJSON, err := json.Marshal(svcIPMap)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b.netMap = &netmap.NetworkMap{
|
||||
SelfNode: (&tailcfg.Node{
|
||||
Name: "example.ts.net",
|
||||
CapMap: tailcfg.NodeCapMap{
|
||||
tailcfg.NodeAttrServiceHost: []tailcfg.RawMessage{tailcfg.RawMessage(svcIPMapJSON)},
|
||||
},
|
||||
}).View(),
|
||||
UserProfiles: map[tailcfg.UserID]tailcfg.UserProfile{
|
||||
tailcfg.UserID(1): {
|
||||
LoginName: "someone@example.com",
|
||||
DisplayName: "Some One",
|
||||
ProfilePicURL: "https://example.com/photo.jpg",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
conf *ipn.ServeConfig
|
||||
expectedErr error
|
||||
packetDstAddrPort []netip.AddrPort
|
||||
intercepted bool
|
||||
}{
|
||||
{
|
||||
name: "no-services",
|
||||
conf: &ipn.ServeConfig{},
|
||||
packetDstAddrPort: []netip.AddrPort{
|
||||
netip.MustParseAddrPort("100.101.101.101:443"),
|
||||
},
|
||||
intercepted: false,
|
||||
},
|
||||
{
|
||||
name: "one-incorrectly-configured-service",
|
||||
conf: &ipn.ServeConfig{
|
||||
Services: map[string]*ipn.ServiceConfig{
|
||||
"svc:foo": {
|
||||
TCP: map[uint16]*ipn.TCPPortHandler{
|
||||
80: {HTTP: true},
|
||||
},
|
||||
Tun: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErr: ipn.ErrServiceConfigHasBothTCPAndTun,
|
||||
},
|
||||
{
|
||||
// one correctly configured service with packet should be intercepted
|
||||
name: "one-service-intercept-packet",
|
||||
conf: &ipn.ServeConfig{
|
||||
Services: map[string]*ipn.ServiceConfig{
|
||||
"svc:foo": {
|
||||
TCP: map[uint16]*ipn.TCPPortHandler{
|
||||
80: {HTTP: true},
|
||||
81: {HTTPS: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
packetDstAddrPort: []netip.AddrPort{
|
||||
netip.MustParseAddrPort("100.101.101.101:80"),
|
||||
netip.MustParseAddrPort("[fd7a:115c:a1e0:ab12:4843:cd96:6565:6565]:80"),
|
||||
},
|
||||
intercepted: true,
|
||||
},
|
||||
{
|
||||
// one correctly configured service with packet should not be intercepted
|
||||
name: "one-service-not-intercept-packet",
|
||||
conf: &ipn.ServeConfig{
|
||||
Services: map[string]*ipn.ServiceConfig{
|
||||
"svc:foo": {
|
||||
TCP: map[uint16]*ipn.TCPPortHandler{
|
||||
80: {HTTP: true},
|
||||
81: {HTTPS: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
packetDstAddrPort: []netip.AddrPort{
|
||||
netip.MustParseAddrPort("100.99.99.99:80"),
|
||||
netip.MustParseAddrPort("[fd7a:115c:a1e0:ab12:4843:cd96:626b:628b]:80"),
|
||||
netip.MustParseAddrPort("100.101.101.101:82"),
|
||||
netip.MustParseAddrPort("[fd7a:115c:a1e0:ab12:4843:cd96:6565:6565]:82"),
|
||||
},
|
||||
intercepted: false,
|
||||
},
|
||||
{
|
||||
//multiple correctly configured service with packet should be intercepted
|
||||
name: "multiple-service-intercept-packet",
|
||||
conf: &ipn.ServeConfig{
|
||||
Services: map[string]*ipn.ServiceConfig{
|
||||
"svc:foo": {
|
||||
TCP: map[uint16]*ipn.TCPPortHandler{
|
||||
80: {HTTP: true},
|
||||
81: {HTTPS: true},
|
||||
},
|
||||
},
|
||||
"svc:bar": {
|
||||
TCP: map[uint16]*ipn.TCPPortHandler{
|
||||
80: {HTTP: true},
|
||||
81: {HTTPS: true},
|
||||
82: {HTTPS: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
packetDstAddrPort: []netip.AddrPort{
|
||||
netip.MustParseAddrPort("100.99.99.99:80"),
|
||||
netip.MustParseAddrPort("[fd7a:115c:a1e0:ab12:4843:cd96:626b:628b]:80"),
|
||||
netip.MustParseAddrPort("100.101.101.101:81"),
|
||||
netip.MustParseAddrPort("[fd7a:115c:a1e0:ab12:4843:cd96:6565:6565]:81"),
|
||||
},
|
||||
intercepted: true,
|
||||
},
|
||||
{
|
||||
// multiple correctly configured service with packet should not be intercepted
|
||||
name: "multiple-service-not-intercept-packet",
|
||||
conf: &ipn.ServeConfig{
|
||||
Services: map[string]*ipn.ServiceConfig{
|
||||
"svc:foo": {
|
||||
TCP: map[uint16]*ipn.TCPPortHandler{
|
||||
80: {HTTP: true},
|
||||
81: {HTTPS: true},
|
||||
},
|
||||
},
|
||||
"svc:bar": {
|
||||
TCP: map[uint16]*ipn.TCPPortHandler{
|
||||
80: {HTTP: true},
|
||||
81: {HTTPS: true},
|
||||
82: {HTTPS: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
packetDstAddrPort: []netip.AddrPort{
|
||||
// ips in capmap but port is not hosting service
|
||||
netip.MustParseAddrPort("100.99.99.99:77"),
|
||||
netip.MustParseAddrPort("[fd7a:115c:a1e0:ab12:4843:cd96:626b:628b]:77"),
|
||||
netip.MustParseAddrPort("100.101.101.101:85"),
|
||||
netip.MustParseAddrPort("[fd7a:115c:a1e0:ab12:4843:cd96:6565:6565]:85"),
|
||||
// ips not in capmap
|
||||
netip.MustParseAddrPort("100.102.102.102:80"),
|
||||
netip.MustParseAddrPort("[fd7a:115c:a1e0:ab12:4843:cd96:6666:6666]:80"),
|
||||
},
|
||||
intercepted: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := b.SetServeConfig(tt.conf, "")
|
||||
if err != nil && tt.expectedErr != nil {
|
||||
if !errors.Is(err, tt.expectedErr) {
|
||||
t.Fatalf("expected error %v,\n got %v", tt.expectedErr, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, addrPort := range tt.packetDstAddrPort {
|
||||
if tt.intercepted != b.ShouldInterceptVIPServiceTCPPort(addrPort) {
|
||||
if tt.intercepted {
|
||||
t.Fatalf("expected packet to be intercepted")
|
||||
} else {
|
||||
t.Fatalf("expected packet not to be intercepted")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestServeConfigETag(t *testing.T) {
|
||||
b := newTestBackend(t)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user