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

283 lines
9.5 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
import (
"fmt"
"strings"
"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/foundation/v2/versions"
"github.com/hanzozt/metrics"
"github.com/hanzozt/sdk-golang/xgress"
"github.com/hanzozt/sdk-golang/zt/edge"
"github.com/hanzozt/transport/v2"
"github.com/hanzozt/zt/v2/common/inspect"
"github.com/hanzozt/zt/v2/common/pb/edge_ctrl_pb"
"github.com/hanzozt/zt/v2/router/env"
"github.com/hanzozt/zt/v2/router/internal/apiproxy"
"github.com/hanzozt/zt/v2/router/state"
"github.com/hanzozt/zt/v2/router/xgress_router"
"github.com/pkg/errors"
)
type reconnectionHandler interface {
NotifyOfReconnect(ch channel.Channel)
}
type Factory struct {
ctrls env.NetworkControllers
enabled bool
routerConfig *env.Config
edgeRouterConfig *env.EdgeConfig
hostedServices *hostedServiceRegistry
stateManager state.Manager
versionProvider versions.VersionProvider
metricsRegistry metrics.Registry
env env.RouterEnv
reconnectionHandlers concurrenz.CopyOnWriteSlice[reconnectionHandler]
connectionTracker *connectionTracker
}
func (factory *Factory) Inspect(key string, timeout time.Duration) any {
if key == inspect.RouterIdentityConnectionStatusesKey {
return factory.connectionTracker.Inspect(key, timeout)
}
return nil
}
func (factory *Factory) GetNetworkControllers() env.NetworkControllers {
return factory.ctrls
}
func (factory *Factory) Enabled() bool {
return factory.enabled
}
func (factory *Factory) BindChannel(binding channel.Binding) error {
binding.AddTypedReceiveHandler(&channel.AsyncFunctionReceiveAdapter{
Type: int32(edge_ctrl_pb.ContentType_CreateTerminatorV2ResponseType),
Handler: factory.hostedServices.HandleCreateTerminatorResponse,
})
return nil
}
func (factory *Factory) NotifyOfReconnect(ch channel.Channel) {
pfxlog.Logger().Info("control channel reconnected, re-establishing hosted services")
factory.hostedServices.HandleReconnect()
go factory.stateManager.ValidateSessions(ch, factory.edgeRouterConfig.SessionValidateChunkSize, factory.edgeRouterConfig.SessionValidateMinInterval, factory.edgeRouterConfig.SessionValidateMaxInterval)
for _, handler := range factory.reconnectionHandlers.Value() {
go handler.NotifyOfReconnect(ch)
}
}
func (factory *Factory) addReconnectionHandler(h reconnectionHandler) {
factory.reconnectionHandlers.Append(h)
}
func (factory *Factory) Run(env env.RouterEnv) error {
factory.stateManager.StartHeartbeat(env, factory.edgeRouterConfig.HeartbeatIntervalSeconds, env.GetCloseNotify())
return nil
}
func (factory *Factory) LoadConfig(configMap map[interface{}]interface{}) error {
factory.enabled = factory.routerConfig.Edge.Enabled
if !factory.enabled {
return nil
}
edgeConfig := factory.routerConfig.Edge
if edgeConfig.Tcfg == nil {
edgeConfig.Tcfg = make(transport.Configuration)
}
if !stringz.Contains(edgeConfig.Tcfg.Protocols(), "zt-edge") {
edgeConfig.Tcfg[transport.KeyProtocol] = append(edgeConfig.Tcfg.Protocols(), "zt-edge")
}
if !stringz.Contains(edgeConfig.Tcfg.Protocols(), "") {
edgeConfig.Tcfg[transport.KeyProtocol] = append(edgeConfig.Tcfg.Protocols(), "")
}
factory.edgeRouterConfig = factory.routerConfig.Edge
factory.env.MarkRouterDataModelRequired()
go apiproxy.Start(edgeConfig)
return nil
}
// NewFactory constructs a new Edge Xgress Factory instance
func NewFactory(routerConfig *env.Config, env env.RouterEnv, stateManager state.Manager) *Factory {
factory := &Factory{
ctrls: env.GetNetworkControllers(),
hostedServices: newHostedServicesRegistry(env, stateManager),
stateManager: stateManager,
versionProvider: env.GetVersionInfo(),
routerConfig: routerConfig,
metricsRegistry: env.GetMetricsRegistry(),
env: env,
connectionTracker: newConnectionTracker(env, stateManager),
}
factory.stateManager.SetConnectionTracker(factory.connectionTracker)
factory.addReconnectionHandler(factory.connectionTracker)
return factory
}
// CreateListener creates a new Edge Xgress listener
func (factory *Factory) CreateListener(optionsData xgress.OptionsData) (xgress_router.Listener, error) {
if !factory.enabled {
return nil, errors.New("edge listener enabled but required configuration section [edge] is missing")
}
options := &Options{}
if err := options.load(optionsData); err != nil {
return nil, err
}
pfxlog.Logger().Infof("xgress edge listener options: %v", options.ToLoggableString())
versionInfo := factory.versionProvider.AsVersionInfo()
versionHeader, err := factory.versionProvider.EncoderDecoder().Encode(versionInfo)
if err != nil {
return nil, fmt.Errorf("could not generate version header: %v", err)
}
headers := map[int32][]byte{
channel.HelloVersionHeader: versionHeader,
edge.SupportsBindSuccessHeader: {1},
edge.SupportsPostureChecksHeader: {1},
}
return newListener(factory.env.GetRouterId(), factory, options, headers), nil
}
// CreateDialer creates a new Edge Xgress dialer
func (factory *Factory) CreateDialer(optionsData xgress.OptionsData) (xgress_router.Dialer, error) {
if !factory.enabled {
return nil, errors.New("edge listener enabled but required configuration section [edge] is missing")
}
options := &Options{}
if err := options.load(optionsData); err != nil {
return nil, err
}
// CreateDialer is called for every egress route and for inspect and validations
// can't Log this every time.
// pfxlog.Logger().Infof("xgress edge dialer options: %v", options.ToLoggableString())
return newDialer(factory, options), nil
}
type Options struct {
xgress.Options
channelOptions *channel.Options
lookupApiSessionTimeout time.Duration
lookupSessionTimeout time.Duration
}
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)
fmt.Fprintf(&buf, "lookupApiSessionTimeout=%v\n", options.lookupApiSessionTimeout)
fmt.Fprintf(&buf, "lookupSessionTimeout=%v\n", options.lookupSessionTimeout)
fmt.Fprintf(&buf, "channel.outQueueSize=%v\n", options.channelOptions.OutQueueSize)
fmt.Fprintf(&buf, "channel.connectTimeout=%v\n", options.channelOptions.ConnectTimeout)
fmt.Fprintf(&buf, "channel.maxOutstandingConnects=%v\n", options.channelOptions.MaxOutstandingConnects)
fmt.Fprintf(&buf, "channel.maxQueuedConnects=%v\n", options.channelOptions.MaxQueuedConnects)
return buf.String()
}
func (options *Options) load(data xgress.OptionsData) error {
o, err := xgress.LoadOptions(data)
if err != nil {
return errors.Wrap(err, "error loading options")
}
options.Options = *o
options.lookupSessionTimeout = 5 * time.Second
options.lookupApiSessionTimeout = 5 * time.Second
if value, found := data["options"]; found {
data = value.(map[interface{}]interface{})
var err error
options.channelOptions, err = channel.LoadOptions(data)
if err != nil {
return err
}
if err := options.channelOptions.Validate(); err != nil {
return fmt.Errorf("error loading options for [edge/options]: %v", err)
}
if value, found := data["lookupSessionTimeout"]; found {
timeout, err := time.ParseDuration(value.(string))
if err != nil {
return errors.Wrap(err, "invalid 'lookupSessionTimeout' value")
}
options.lookupSessionTimeout = timeout
}
if value, found := data["lookupApiSessionTimeout"]; found {
timeout, err := time.ParseDuration(value.(string))
if err != nil {
return errors.Wrap(err, "invalid 'lookupApiSessionTimeout' value")
}
options.lookupApiSessionTimeout = timeout
}
} else {
options.channelOptions = channel.DefaultOptions()
}
if options.channelOptions.OutQueueSize == channel.DefaultOutQueueSize {
options.channelOptions.OutQueueSize = 64
}
return nil
}