Files
zeekay 448c41866f Merge upstream/main: sync with openziti (15 commits)
Upstream changes include:
- Add get/set db version commands (#3579)
- Fix link dial race condition (#3566)
- Update channel version to v4.3.5
- Retire ingress-nginx in favor of Traefik (#3582)
- Appease golangci-lint (#3594)
- Fix flaky certchecker and link tests
- Enable verbose logging for Go SDK apps
- Improve router quickstart crash recovery

Conflicts resolved keeping hanzozt branding while incorporating
all upstream code improvements.
2026-02-22 00:49:18 -08:00

320 lines
10 KiB
Go

/*
Copyright NetFoundry Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package xgress_edge_tunnel
import (
"fmt"
"strings"
"sync/atomic"
"time"
"github.com/michaelquigley/pfxlog"
"github.com/hanzozt/channel/v4"
"github.com/hanzozt/foundation/v2/concurrenz"
"github.com/hanzozt/foundation/v2/stringz"
"github.com/hanzozt/identity"
"github.com/hanzozt/metrics"
"github.com/hanzozt/sdk-golang/xgress"
"github.com/hanzozt/zt/v2/common/pb/edge_ctrl_pb"
"github.com/hanzozt/zt/v2/router/env"
"github.com/hanzozt/zt/v2/router/state"
"github.com/hanzozt/zt/v2/router/xgress_router"
"github.com/pkg/errors"
)
const (
DefaultMode = "tproxy"
DefaultServicePollRate = 15 * time.Second
DefaultDnsResolver = "udp://127.0.0.1:53"
DefaultDnsServiceIpRange = "100.64.0.1/10"
DefaultDnsUpstream = ""
DefaultDnsUnanswerable = "refused"
)
var fabricProviderF concurrenz.AtomicValue[func(env.RouterEnv, *HostedServiceRegistry) TunnelFabricProvider]
// OverrideFabricProviderF allows overriding the default TunnelFabricProvider factory function
// used by the edge tunnel v2 xgress factory. This is primarily useful for wrapping the fabric
// provider to intercept hosting/tunnel calls for router embedders wishing to customize tunnel
// behavior.
//
// The provided function will be called during CreateListener to create a TunnelFabricProvider
// instance. If no override is set, the factory will use NewTunnelFabricProvider as the default.
//
// Parameters:
// - f: A factory function that takes a RouterEnv and HostedServiceRegistry and returns
// a TunnelFabricProvider instance. Pass nil to clear any existing override.
//
// This function is thread-safe and can be called concurrently with CreateListener.
func OverrideFabricProviderF(f func(env.RouterEnv, *HostedServiceRegistry) TunnelFabricProvider) {
fabricProviderF.Store(f)
}
type Factory struct {
id *identity.TokenId
ctrls env.NetworkControllers
stateManager state.Manager
tunneler *tunneler
metricsRegistry metrics.UsageRegistry
env env.RouterEnv
hostedServices *HostedServiceRegistry
dialerInitialized atomic.Bool
}
func (self *Factory) NotifyOfReconnect(channel.Channel) {
}
func (self *Factory) Enabled() bool {
return true
}
func (self *Factory) BindChannel(binding channel.Binding) error {
binding.AddReceiveHandlerF(int32(edge_ctrl_pb.ContentType_CreateTunnelTerminatorResponseV2Type), self.hostedServices.HandleCreateTerminatorResponse)
return nil
}
func (self *Factory) Run(env env.RouterEnv) error {
self.ctrls = env.GetNetworkControllers()
if self.tunneler.listenOptions != nil {
return self.tunneler.Start()
} else {
self.tunneler.initialized.Store(true)
}
return nil
}
func (self *Factory) LoadConfig(map[interface{}]interface{}) error {
return nil
}
func (self *Factory) DefaultRequestTimeout() time.Duration {
return self.env.DefaultRequestTimeout()
}
// NewFactory constructs a new Edge Xgress Tunnel Factory instance
func NewFactory(env env.RouterEnv, stateManager state.Manager) *Factory {
factory := &Factory{
id: env.GetRouterId(),
stateManager: stateManager,
metricsRegistry: env.GetMetricsRegistry(),
env: env,
}
factory.tunneler = newTunneler(factory)
factory.hostedServices = newHostedServicesRegistry(env, stateManager)
factory.tunneler.hostedServices = factory.hostedServices
return factory
}
// CreateListener creates a new Edge Tunnel Xgress listener
func (self *Factory) CreateListener(optionsData xgress.OptionsData) (xgress_router.Listener, error) {
self.env.MarkRouterDataModelRequired()
self.hostedServices.Start()
if fabricProviderFunc := fabricProviderF.Load(); fabricProviderFunc != nil {
self.tunneler.fabricProvider = fabricProviderFunc(self.env, self.hostedServices)
} else {
self.tunneler.fabricProvider = NewTunnelFabricProvider(self.env, self.hostedServices)
}
options := &Options{}
if err := options.load(optionsData); err != nil {
return nil, err
}
self.tunneler.listenOptions = options
self.tunneler.fabricProvider.SetXgressOptions(options.Options)
pfxlog.Logger().Debugf("xgress edge tunnel options: %v", options.ToLoggableString())
return self.tunneler, nil
}
// CreateDialer creates a new Edge Xgress dialer
func (self *Factory) CreateDialer(optionsData xgress.OptionsData) (xgress_router.Dialer, error) {
if self.dialerInitialized.CompareAndSwap(false, true) {
self.env.MarkRouterDataModelRequired()
options := &Options{}
if err := options.load(optionsData); err != nil {
return nil, err
}
self.tunneler.dialOptions = options
}
return self.tunneler, nil
}
type Options struct {
*xgress.Options
mode string
svcPollRate time.Duration
resolver string
dnsSvcIpRange string
dnsUpstream string
dnsUnanswerable string
lanIf string
services []string
udpIdleTimeout time.Duration
udpCheckInterval time.Duration
}
func (options *Options) load(data xgress.OptionsData) error {
options.mode = DefaultMode
options.svcPollRate = DefaultServicePollRate
options.resolver = DefaultDnsResolver
options.dnsSvcIpRange = DefaultDnsServiceIpRange
options.dnsUpstream = DefaultDnsUpstream
options.dnsUnanswerable = DefaultDnsUnanswerable
var err error
options.Options, err = xgress.LoadOptions(data)
if err != nil {
return err
}
if value, found := data["options"]; found {
data = value.(map[interface{}]interface{})
if value, found := data["svcPollRate"]; found {
if strVal, ok := value.(string); ok {
dur, err := time.ParseDuration(strVal)
if err != nil {
return errors.Wrapf(err, "invalid value '%v' for svcPollRate, must be string duration (ex: 1m or 30s)", value)
}
options.svcPollRate = dur
} else {
return errors.Errorf("invalid value '%v' for svcPollRate, must be string duration (ex: 1m or 30s)", value)
}
}
if value, found := data["resolver"]; found {
if strVal, ok := value.(string); ok {
options.resolver = strVal
} else {
return errors.Errorf("invalid value '%v' for resolver, must be string value", value)
}
}
if value, found := data["dnsSvcIpRange"]; found {
if strVal, ok := value.(string); ok {
options.dnsSvcIpRange = strVal
} else {
return errors.Errorf("invalid value '%v' for dnsSvcIpRange, must be string value", value)
}
}
if value, found := data["dnsUpstream"]; found {
if strVal, ok := value.(string); ok {
options.dnsUpstream = strVal
} else {
return errors.Errorf("invalid value '%v' for dnsUpstream, must be string value", value)
}
}
if value, found := data["dnsUnanswerable"]; found {
if strVal, ok := value.(string); ok {
options.dnsUnanswerable = strVal
} else {
return errors.Errorf("invalid value '%v' for dnsUnanswerable, must be string value", value)
}
}
if value, found := data["mode"]; found {
if strVal, ok := value.(string); ok && stringz.Contains([]string{"tproxy", "host", "proxy"}, strVal) ||
strings.HasPrefix(strVal, "tproxy:") {
options.mode = strVal
} else {
return errors.Errorf(`invalid value '%v' for mode, must be one of ["tproxy", "host", "proxy"']`, value)
}
}
if value, found := data["services"]; found {
if slice, ok := value.([]interface{}); ok {
for _, value := range slice {
if strVal, ok := value.(string); ok {
options.services = append(options.services, strVal)
} else {
return errors.Errorf(`invalid value '%v' for services, must be list of strings`, value)
}
}
} else {
return errors.New(`invalid value for services, must be list of strings']`)
}
}
if value, found := data["lanIf"]; found {
if strVal, ok := value.(string); ok {
options.lanIf = strVal
} else {
return errors.Errorf(`invalid value '%v' for lanIf, must be a string value`, value)
}
}
if value, found := data["udpIdleTimeout"]; found {
if strVal, ok := value.(string); ok {
dur, err := time.ParseDuration(strVal)
if err != nil {
return errors.Wrapf(err, "invalid value '%v' for udpIdleTimeout, must be string duration (ex: 1m or 30s)", value)
}
options.udpIdleTimeout = dur
} else {
return errors.Errorf("invalid value '%v' for udpIdleTimeout, must be string duration (ex: 1m or 30s)", value)
}
}
if value, found := data["udpCheckInterval"]; found {
if strVal, ok := value.(string); ok {
dur, err := time.ParseDuration(strVal)
if err != nil {
return errors.Wrapf(err, "invalid value '%v' for udpCheckInterval, must be string duration (ex: 1m or 30s)", value)
}
options.udpCheckInterval = dur
} else {
return errors.Errorf("invalid value '%v' for udpCheckInterval, must be string duration (ex: 1m or 30s)", value)
}
}
}
return nil
}
func (options *Options) ToLoggableString() string {
buf := strings.Builder{}
fmt.Fprintf(&buf, "mtu=%v\n", options.Mtu)
fmt.Fprintf(&buf, "randomDrops=%v\n", options.RandomDrops)
fmt.Fprintf(&buf, "drop1InN=%v\n", options.Drop1InN)
fmt.Fprintf(&buf, "txQueueSize=%v\n", options.TxQueueSize)
fmt.Fprintf(&buf, "txPortalStartSize=%v\n", options.TxPortalStartSize)
fmt.Fprintf(&buf, "txPortalMaxSize=%v\n", options.TxPortalMaxSize)
fmt.Fprintf(&buf, "txPortalMinSize=%v\n", options.TxPortalMinSize)
fmt.Fprintf(&buf, "txPortalIncreaseThresh=%v\n", options.TxPortalIncreaseThresh)
fmt.Fprintf(&buf, "txPortalIncreaseScale=%v\n", options.TxPortalIncreaseScale)
fmt.Fprintf(&buf, "txPortalRetxThresh=%v\n", options.TxPortalRetxThresh)
fmt.Fprintf(&buf, "txPortalRetxScale=%v\n", options.TxPortalRetxScale)
fmt.Fprintf(&buf, "txPortalDupAckThresh=%v\n", options.TxPortalDupAckThresh)
fmt.Fprintf(&buf, "txPortalDupAckScale=%v\n", options.TxPortalDupAckScale)
fmt.Fprintf(&buf, "rxBufferSize=%v\n", options.RxBufferSize)
fmt.Fprintf(&buf, "retxStartMs=%v\n", options.RetxStartMs)
fmt.Fprintf(&buf, "retxScale=%v\n", options.RetxScale)
fmt.Fprintf(&buf, "retxAddMs=%v\n", options.RetxAddMs)
fmt.Fprintf(&buf, "maxCloseWait=%v\n", options.MaxCloseWait)
fmt.Fprintf(&buf, "getCircuitTimeout=%v\n", options.GetCircuitTimeout)
return buf.String()
}