mirror of
https://github.com/openziti/ziti.git
synced 2026-10-23 12:15:35 +00:00
346 lines
12 KiB
Go
346 lines
12 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 forwarder
|
|
|
|
import (
|
|
"github.com/michaelquigley/pfxlog"
|
|
"github.com/openziti/foundation/v2/errorz"
|
|
"github.com/openziti/foundation/v2/info"
|
|
"github.com/openziti/metrics"
|
|
"github.com/openziti/ziti/common/inspect"
|
|
"github.com/openziti/ziti/common/pb/ctrl_pb"
|
|
"github.com/openziti/ziti/common/trace"
|
|
"github.com/openziti/ziti/router/env"
|
|
"github.com/openziti/ziti/router/xgress"
|
|
"github.com/openziti/ziti/router/xlink"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"time"
|
|
)
|
|
|
|
type Forwarder struct {
|
|
circuits *circuitTable
|
|
destinations *destinationTable
|
|
faulter FaultReceiver
|
|
metricsRegistry metrics.UsageRegistry
|
|
traceController trace.Controller
|
|
Options *Options
|
|
CloseNotify <-chan struct{}
|
|
}
|
|
|
|
type Destination interface {
|
|
SendPayload(payload *xgress.Payload) error
|
|
SendAcknowledgement(acknowledgement *xgress.Acknowledgement) error
|
|
SendControl(control *xgress.Control) error
|
|
InspectCircuit(detail *inspect.CircuitInspectDetail)
|
|
}
|
|
|
|
type XgressDestination interface {
|
|
Destination
|
|
Unrouted()
|
|
Start()
|
|
IsTerminator() bool
|
|
Label() string
|
|
GetTimeOfLastRxFromLink() int64
|
|
}
|
|
|
|
func NewForwarder(metricsRegistry metrics.UsageRegistry, faulter FaultReceiver, options *Options, closeNotify <-chan struct{}) *Forwarder {
|
|
f := &Forwarder{
|
|
circuits: newCircuitTable(),
|
|
destinations: newDestinationTable(),
|
|
faulter: faulter,
|
|
metricsRegistry: metricsRegistry,
|
|
traceController: trace.NewController(closeNotify),
|
|
Options: options,
|
|
CloseNotify: closeNotify,
|
|
}
|
|
return f
|
|
}
|
|
|
|
func (forwarder *Forwarder) StartScanner(ctrls env.NetworkControllers) {
|
|
scanner := newScanner(ctrls, forwarder.Options, forwarder.CloseNotify)
|
|
scanner.setCircuitTable(forwarder.circuits)
|
|
|
|
if scanner.interval > 0 {
|
|
go scanner.run()
|
|
} else {
|
|
logrus.Warnf("scanner disabled")
|
|
}
|
|
}
|
|
|
|
func (forwarder *Forwarder) MetricsRegistry() metrics.UsageRegistry {
|
|
return forwarder.metricsRegistry
|
|
}
|
|
|
|
func (forwarder *Forwarder) TraceController() trace.Controller {
|
|
return forwarder.traceController
|
|
}
|
|
|
|
func (forwarder *Forwarder) RegisterDestination(circuitId string, address xgress.Address, destination Destination) {
|
|
forwarder.destinations.addDestination(address, destination)
|
|
forwarder.destinations.linkDestinationToCircuit(circuitId, address)
|
|
}
|
|
|
|
func (forwarder *Forwarder) UnregisterDestinations(circuitId string) {
|
|
log := pfxlog.Logger().WithField("circuitId", circuitId)
|
|
if addresses, found := forwarder.destinations.getAddressesForCircuit(circuitId); found {
|
|
for _, address := range addresses {
|
|
if destination, found := forwarder.destinations.getDestination(address); found {
|
|
log.Debugf("unregistering destination [@/%v] for circuit", address)
|
|
forwarder.destinations.removeDestination(address)
|
|
go destination.(XgressDestination).Unrouted()
|
|
} else {
|
|
log.Debugf("no destinations found for [@/%v] for circuit", address)
|
|
}
|
|
}
|
|
forwarder.destinations.unlinkCircuit(circuitId)
|
|
} else {
|
|
log.Debug("found no addresses to unregister for circuit")
|
|
}
|
|
}
|
|
|
|
func (forwarder *Forwarder) HasDestination(address xgress.Address) bool {
|
|
_, found := forwarder.destinations.getDestination(address)
|
|
return found
|
|
}
|
|
|
|
func (forwarder *Forwarder) RegisterLink(link xlink.LinkDestination) error {
|
|
forwarder.destinations.addDestination(xgress.Address(link.Id()), link)
|
|
return nil
|
|
}
|
|
|
|
func (forwarder *Forwarder) UnregisterLink(link xlink.LinkDestination) {
|
|
forwarder.destinations.removeDestinationIfMatches(xgress.Address(link.Id()), link)
|
|
}
|
|
|
|
func (forwarder *Forwarder) Route(ctrlId string, route *ctrl_pb.Route) error {
|
|
circuitId := route.CircuitId
|
|
var circuitFt *forwardTable
|
|
if ft, found := forwarder.circuits.getForwardTable(circuitId, true); found {
|
|
circuitFt = ft
|
|
} else {
|
|
circuitFt = newForwardTable(ctrlId)
|
|
}
|
|
for _, forward := range route.Forwards {
|
|
if !forwarder.HasDestination(xgress.Address(forward.DstAddress)) {
|
|
if forward.DstType == ctrl_pb.DestType_Link {
|
|
forwarder.faulter.NotifyInvalidLink(forward.DstAddress)
|
|
return errors.Errorf("invalid link destination %v", forward.DstAddress)
|
|
}
|
|
if forward.DstType == ctrl_pb.DestType_End {
|
|
return errors.Errorf("invalid egress destination %v", forward.DstAddress)
|
|
}
|
|
// It's an ingress destination, which isn't established until after routing has completed
|
|
}
|
|
circuitFt.setForwardAddress(xgress.Address(forward.SrcAddress), xgress.Address(forward.DstAddress))
|
|
}
|
|
forwarder.circuits.setForwardTable(circuitId, circuitFt)
|
|
return nil
|
|
}
|
|
|
|
func (forwarder *Forwarder) Unroute(circuitId string, now bool) {
|
|
if now {
|
|
forwarder.circuits.removeForwardTable(circuitId)
|
|
forwarder.EndCircuit(circuitId)
|
|
} else {
|
|
go forwarder.unrouteTimeout(circuitId, forwarder.Options.XgressCloseCheckInterval)
|
|
}
|
|
}
|
|
|
|
func (forwarder *Forwarder) EndCircuit(circuitId string) {
|
|
forwarder.UnregisterDestinations(circuitId)
|
|
}
|
|
|
|
func (forwarder *Forwarder) ForwardPayload(srcAddr xgress.Address, payload *xgress.Payload) error {
|
|
return forwarder.forwardPayload(srcAddr, payload, true)
|
|
}
|
|
|
|
func (forwarder *Forwarder) RetransmitPayload(srcAddr xgress.Address, payload *xgress.Payload) error {
|
|
return forwarder.forwardPayload(srcAddr, payload, false)
|
|
}
|
|
|
|
func (forwarder *Forwarder) forwardPayload(srcAddr xgress.Address, payload *xgress.Payload, markActive bool) error {
|
|
log := pfxlog.ContextLogger(string(srcAddr))
|
|
|
|
circuitId := payload.GetCircuitId()
|
|
if forwardTable, found := forwarder.circuits.getForwardTable(circuitId, markActive); found {
|
|
if dstAddr, found := forwardTable.getForwardAddress(srcAddr); found {
|
|
if dst, found := forwarder.destinations.getDestination(dstAddr); found {
|
|
if err := dst.SendPayload(payload); err != nil {
|
|
return err
|
|
}
|
|
log.WithFields(payload.GetLoggerFields()).Debugf("=> %s", string(dstAddr))
|
|
return nil
|
|
} else {
|
|
return errors.Errorf("cannot forward payload, no destination for circuit=%v src=%v dst=%v", circuitId, srcAddr, dstAddr)
|
|
}
|
|
} else {
|
|
return errors.Errorf("cannot forward payload, no destination address for circuit=%v src=%v", circuitId, srcAddr)
|
|
}
|
|
} else {
|
|
return errors.Errorf("cannot forward payload, no forward table for circuit=%v src=%v", circuitId, srcAddr)
|
|
}
|
|
}
|
|
|
|
func (forwarder *Forwarder) ForwardAcknowledgement(srcAddr xgress.Address, acknowledgement *xgress.Acknowledgement) error {
|
|
log := pfxlog.ContextLogger(string(srcAddr))
|
|
|
|
circuitId := acknowledgement.CircuitId
|
|
if forwardTable, found := forwarder.circuits.getForwardTable(circuitId, true); found {
|
|
if dstAddr, found := forwardTable.getForwardAddress(srcAddr); found {
|
|
if dst, found := forwarder.destinations.getDestination(dstAddr); found {
|
|
if err := dst.SendAcknowledgement(acknowledgement); err != nil {
|
|
return err
|
|
}
|
|
log.Debugf("=> %s", string(dstAddr))
|
|
return nil
|
|
|
|
} else {
|
|
return errors.Errorf("cannot acknowledge, no destination for circuit=%v src=%v dst=%v", circuitId, srcAddr, dstAddr)
|
|
}
|
|
|
|
} else {
|
|
return errors.Errorf("cannot acknowledge, no destination address for circuit=%v src=%v", circuitId, srcAddr)
|
|
}
|
|
|
|
} else {
|
|
return errors.Errorf("cannot acknowledge, no forward table for circuit=%v src=%v", circuitId, srcAddr)
|
|
}
|
|
}
|
|
|
|
func (forwarder *Forwarder) ForwardControl(srcAddr xgress.Address, control *xgress.Control) error {
|
|
circuitId := control.CircuitId
|
|
log := pfxlog.ContextLogger(string(srcAddr)).WithField("circuitId", circuitId)
|
|
|
|
var err error
|
|
|
|
if forwardTable, found := forwarder.circuits.getForwardTable(circuitId, true); found {
|
|
if dstAddr, found := forwardTable.getForwardAddress(srcAddr); found {
|
|
if dst, found := forwarder.destinations.getDestination(dstAddr); found {
|
|
if control.IsTypeTraceRoute() {
|
|
hops := control.DecrementAndGetHop()
|
|
if hops == 0 {
|
|
resp := control.CreateTraceResponse("forwarder", forwarder.metricsRegistry.SourceId())
|
|
return forwarder.ForwardControl(dstAddr, resp)
|
|
}
|
|
}
|
|
err = dst.SendControl(control)
|
|
log.Debugf("=> %s", string(dstAddr))
|
|
} else {
|
|
err = errors.Errorf("cannot forward control, no destination for circuit=%v src=%v dst=%v", circuitId, srcAddr, dstAddr)
|
|
}
|
|
} else {
|
|
err = errors.Errorf("cannot forward control, no destination address for circuit=%v src=%v", circuitId, srcAddr)
|
|
}
|
|
} else {
|
|
err = errors.Errorf("cannot forward control, no forward table for circuit=%v src=%v", circuitId, srcAddr)
|
|
}
|
|
|
|
if err != nil && control.IsTypeTraceRoute() {
|
|
resp := control.CreateTraceResponse("forwarder", forwarder.metricsRegistry.SourceId())
|
|
resp.Headers.PutStringHeader(xgress.ControlError, err.Error())
|
|
if dst, found := forwarder.destinations.getDestination(srcAddr); found {
|
|
if fwdErr := dst.SendControl(resp); fwdErr != nil {
|
|
log.WithError(errorz.MultipleErrors{err, fwdErr}).Error("error sending trace error response")
|
|
}
|
|
} else {
|
|
log.WithError(err).Error("unable to send trace error response as destination for source not found")
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (forwarder *Forwarder) ReportForwardingFault(circuitId string, ctrlId string) {
|
|
if forwarder.faulter != nil {
|
|
forwarder.faulter.Report(circuitId, ctrlId)
|
|
} else {
|
|
logrus.Error("nil faulter, cannot accept forwarding fault report")
|
|
}
|
|
}
|
|
|
|
func (forwarder *Forwarder) Debug() string {
|
|
return forwarder.circuits.debug() + forwarder.destinations.debug()
|
|
}
|
|
|
|
// unrouteTimeout implements a goroutine to manage route timeout processing. Once a timeout processor has been launched
|
|
// for a circuit, it will be checked repeatedly, looking to see if the circuit has crossed the inactivity threshold.
|
|
// Once it crosses the inactivity threshold, it gets removed.
|
|
func (forwarder *Forwarder) unrouteTimeout(circuitId string, interval time.Duration) {
|
|
log := pfxlog.ContextLogger("c/" + circuitId)
|
|
log.Debug("scheduled")
|
|
defer log.Debug("timeout")
|
|
|
|
ticker := time.NewTicker(interval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
if dest := forwarder.getXgressForCircuit(circuitId); dest != nil {
|
|
elapsedDelta := info.NowInMilliseconds() - dest.GetTimeOfLastRxFromLink()
|
|
if (time.Duration(elapsedDelta) * time.Millisecond) >= interval {
|
|
forwarder.circuits.removeForwardTable(circuitId)
|
|
forwarder.EndCircuit(circuitId)
|
|
return
|
|
}
|
|
} else {
|
|
forwarder.circuits.removeForwardTable(circuitId)
|
|
forwarder.EndCircuit(circuitId)
|
|
return
|
|
}
|
|
case <-forwarder.CloseNotify:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (forwarder *Forwarder) getXgressForCircuit(circuitId string) XgressDestination {
|
|
if addresses, found := forwarder.destinations.getAddressesForCircuit(circuitId); found {
|
|
for _, address := range addresses {
|
|
if destination, found := forwarder.destinations.getDestination(address); found {
|
|
return destination.(XgressDestination)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (forwarder *Forwarder) InspectCircuit(circuitId string, getRelatedGoroutines bool) *inspect.CircuitInspectDetail {
|
|
if ft, found := forwarder.circuits.circuits.Get(circuitId); found {
|
|
result := &inspect.CircuitInspectDetail{
|
|
CircuitId: circuitId,
|
|
Forwards: map[string]string{},
|
|
XgressDetails: map[string]*inspect.XgressDetail{},
|
|
LinkDetails: map[string]*inspect.LinkInspectDetail{},
|
|
}
|
|
result.SetIncludeGoroutines(getRelatedGoroutines)
|
|
|
|
ft.destinations.IterCb(func(key string, dest string) {
|
|
result.Forwards[key] = dest
|
|
})
|
|
|
|
for _, addr := range result.Forwards {
|
|
if dest, _ := forwarder.destinations.getDestination(xgress.Address(addr)); dest != nil {
|
|
dest.InspectCircuit(result)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
return nil
|
|
}
|