Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ipn/ipnlocal: Support TCP and Web VIP services #14508

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

KevinLiang10
Copy link
Contributor

@KevinLiang10 KevinLiang10 commented Jan 2, 2025

This PR 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 first 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 https://github.com/tailscale/corp/issues/24604

Signed-off-by: KevinLiang10 37811973+KevinLiang10@users.noreply.github.com

@KevinLiang10 KevinLiang10 force-pushed the kevin/serve_support_TCP_and_Web_VIP_Services branch 2 times, most recently from 7cbfe27 to 36c0813 Compare January 17, 2025 03:10
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>
@KevinLiang10 KevinLiang10 force-pushed the kevin/serve_support_TCP_and_Web_VIP_Services branch from 45fa3ba to 579d75d Compare January 20, 2025 17:03
@KevinLiang10 KevinLiang10 changed the title temp ipn/ipnlocal: Support TCP and Web VIP services Jan 20, 2025
@KevinLiang10 KevinLiang10 marked this pull request as ready for review January 20, 2025 17:25
@KevinLiang10 KevinLiang10 requested a review from a team as a code owner January 20, 2025 17:25
ipn/ipnlocal/serve_test.go Outdated Show resolved Hide resolved
ipn/serve.go Outdated
@@ -662,6 +682,17 @@ func (v ServeConfigView) HasFunnelForTarget(target HostPort) bool {
return false
}

// HasValidServicesConfig reports whether if ServeConfig has at least
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just "whether" not "whether if"

but "whether" wording is normally used only for bool results. This returns an error.

So also the "at least one" wording is weird with an error result.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about
"CheckValidServicesConfig reports whether the ServeConfig has invalid service configurations."

ipn/serve.go Outdated Show resolved Hide resolved
ipn/serve.go Outdated Show resolved Hide resolved
ipn/serve.go Outdated Show resolved Hide resolved
types/netmap/netmap.go Outdated Show resolved Hide resolved
types/netmap/netmap.go Outdated Show resolved Hide resolved
types/netmap/netmap.go Show resolved Hide resolved
ipn/serve.go Outdated Show resolved Hide resolved
// Services maps from service name to a ServiceConfig. Which describes the
// L3, L4, and L7 forwarding information for the service.
// Services maps from service name (in the form "svc:dns_label") to a ServiceConfig.
// Which describes the L3, L4, and L7 forwarding information for the service.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What L3 stuff do we do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't do anything. If Tun is set to true, we just let the packets pass to Tun and user can do their own customization to route then to their wanted destination.

Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com>
Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com>
Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com>
Comment on lines +3437 to +3440
if vipServiceIPMap == nil {
b.logf("can't set intercept function for Service TCP Ports, VIPServiceAddrInfo is nil")
return
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if vipServiceIPMap == nil {
b.logf("can't set intercept function for Service TCP Ports, VIPServiceAddrInfo is nil")
return
}
if len(vipServiceIPMap) == 0 {
// No approved VIP Services
return
}

return
}

svcAddrPorts := map[netip.Addr]func(uint16) bool{}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the convention is usually to make a map so that it has some empty space allocated.

Suggested change
svcAddrPorts := map[netip.Addr]func(uint16) bool{}
svcAddrPorts := make(map[netip.Addr]func(uint16) bool)


svcAddrPorts := map[netip.Addr]func(uint16) bool{}
// Only set the intercept function if the service has been assigned a VIP.
for svcName, ports := range svcPorts {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine, but I feel like it might make more sense to range on vipServiceIPMap since I'd expect that to be the smaller of the two maps.

for svcName, ports := range svcPorts {
if addrs, ok := vipServiceIPMap[svcName]; ok {
for _, addr := range addrs {
svcAddrPorts[addr] = generateInterceptTCPPortFunc(ports)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend it generate the func once outside the loop

@@ -6013,10 +6095,11 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked(prefs ipn.
if wire := b.wantIngressLocked(); b.hostinfo != nil && b.hostinfo.WireIngress != wire {
b.logf("Hostinfo.WireIngress changed to %v", wire)
b.hostinfo.WireIngress = wire
b.goTracker.Go(b.doSetHostinfoFilterServices)
go b.doSetHostinfoFilterServices()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switch this back to using goTracker

func (sc *ServeConfig) CheckValidServicesConfig() error {
for svcName, service := range sc.Services {
if err := service.checkValidConfig(); err != nil {
return fmt.Errorf("invalid service configuration for \"%q\": %w", svcName, err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

%q already puts quotes around the contents, you don't need to add them manually

return nil
}

ipMaps, err := tailcfg.UnmarshalNodeCapJSON[tailcfg.ServiceIPMappings](nm.SelfNode.AsStruct().CapMap, tailcfg.NodeAttrServiceHost)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a way to avoid running this through AsStruct since that makes an expensive deep copy.

// GetVIPServiceIPMap returns a map of service names to the slice of
// VIP addresses that correspond to the service. The service names are
// with the prefix "svc:".
func (nm *NetworkMap) GetVIPServiceIPMap() tailcfg.ServiceIPMappings {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like it would be good for this to cache the unmarshaled details rather than JSON decoding multiple times when a new netmap is received.

Comment on lines +634 to +637
ns.atomicIsVIPServiceIPFunc.Store(func(ip netip.Addr) bool {
_, ok := serviceAddrSet[ip]
return ok
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work?

Suggested change
ns.atomicIsVIPServiceIPFunc.Store(func(ip netip.Addr) bool {
_, ok := serviceAddrSet[ip]
return ok
})
ns.atomicIsVIPServiceIPFunc.Store(serviceAddrSet.Contains)

@@ -990,6 +1011,13 @@ func (ns *Impl) shouldProcessInbound(p *packet.Parsed, t *tstun.Wrapper) bool {
return true
}
}
if ns.lb != nil && p.IPProto == ipproto.TCP && isService {
// An assumption holds for this to work: when tun mode is on for a service,
// it's tcp and web are not set. It's enforced in b.setServeConfigLocked.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// it's tcp and web are not set. It's enforced in b.setServeConfigLocked.
// its tcp and web are not set. This is enforced in b.setServeConfigLocked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants