Skip to content

Commit 1cf7e22

Browse files
committed
chore: Offload handler placeholders for the great eBPF merge
1 parent cc5f9eb commit 1cf7e22

File tree

10 files changed

+345
-57
lines changed

10 files changed

+345
-57
lines changed

config_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ func TestStunnerConfigFileRoundTrip(t *testing.T) {
114114

115115
newConf := &stnrv1.StunnerConfig{}
116116
err = yaml.Unmarshal(file, newConf)
117-
assert.NoError(t, err, "unmarschal config from file")
117+
assert.NoError(t, err, "unmarshal config from file")
118+
assert.NoError(t, newConf.Validate(), "validate")
118119

119120
ok := newConf.DeepEqual(c)
120121
assert.True(t, ok, "config file roundtrip")

handlers.go

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,10 +177,12 @@ const (
177177

178178
// TURN offload handler.
179179
type OffloadHandler interface {
180-
// OffloadHandler manages offload requests.
181-
OffloadHandler(src, dst net.Addr, proto, username, realm string, relayAddr, peer net.Addr, chanNum uint16, event OffloadEventType)
180+
// Start starts the offload handler.
181+
Start() error
182182
// Close closes the offload handler.
183183
Close() error
184+
// Offload manages offload requests.
185+
Offload(OffloadEventType, net.Addr, net.Addr, string, string, string, net.Addr, net.Addr, uint16, string, string)
184186
}
185187

186188
// NewOffloadHandler creates a offload handler that defaults to a stub.
@@ -193,17 +195,18 @@ var offloadHandlerConstructor = newOffloadHandlerStub
193195
// offloadHandlerStub is a stub offload handler that does nothing.
194196
type offloadHandlerStub struct{}
195197

196-
func (o *offloadHandlerStub) OffloadHandler(_, _ net.Addr, _, _, _ string, _, _ net.Addr, _ uint16, _ OffloadEventType) {
198+
func (o *offloadHandlerStub) Offload(_ OffloadEventType, _, _ net.Addr, _, _, _ string, _, _ net.Addr, _ uint16, _, _ string) {
197199
}
198200

201+
func (o *offloadHandlerStub) Start() error { return nil }
199202
func (o *offloadHandlerStub) Close() error { return nil }
200203

201204
func newOffloadHandlerStub(_ *Stunner) OffloadHandler {
202205
return &offloadHandlerStub{}
203206
}
204207

205208
// NewEventHandler creates a set of callbcks for tracking the lifecycle of TURN allocations.
206-
func (s *Stunner) NewEventHandler() turn.EventHandlers {
209+
func (s *Stunner) NewEventHandler(l *object.Listener) turn.EventHandlers {
207210
return turn.EventHandlers{
208211
OnAuth: func(src, dst net.Addr, proto, username, realm string, method string, verdict bool) {
209212
status := "REJECTED"
@@ -240,14 +243,33 @@ func (s *Stunner) NewEventHandler() turn.EventHandlers {
240243
dumpClient(src, dst, proto, username, realm), relayAddr.String(),
241244
peer.String(), chanNum)
242245

243-
s.offloadHandler.OffloadHandler(src, dst, proto, username, realm, relayAddr, peer, chanNum, ChannnelOffloadCreated)
246+
// listener and cluster needed for monitoring
247+
listener := l.Name
248+
cluster := ""
249+
peerAddr, ok := peer.(*net.UDPAddr)
250+
if ok {
251+
clusters := s.clusterManager.Keys()
252+
for _, r := range l.Routes {
253+
if util.Member(clusters, r) {
254+
c := s.GetCluster(r)
255+
if c.Route(peerAddr.IP) {
256+
cluster = c.Name
257+
break
258+
}
259+
}
260+
}
261+
}
262+
263+
s.offloadHandler.Offload(ChannnelOffloadCreated, src, dst, proto, username, realm,
264+
relayAddr, peer, chanNum, listener, cluster)
244265
},
245266
OnChannelDeleted: func(src, dst net.Addr, proto, username, realm string, relayAddr, peer net.Addr, chanNum uint16) {
246267
s.log.Infof("Channel deleted: client=%s, relay-addr=%s, peer=%s, channel-num=%d",
247268
dumpClient(src, dst, proto, username, realm), relayAddr.String(),
248269
peer.String(), chanNum)
249270

250-
s.offloadHandler.OffloadHandler(src, dst, proto, username, realm, relayAddr, peer, chanNum, ChannnelOffloadDeleted)
271+
s.offloadHandler.Offload(ChannnelOffloadDeleted, src, dst, proto, username, realm,
272+
relayAddr, peer, chanNum, "", "")
251273
},
252274
}
253275
}

internal/object/admin.go

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import (
88
"net"
99
"net/http"
1010
"net/url"
11+
"reflect"
1112
"strconv"
13+
"strings"
1214

1315
"github.com/pion/logging"
1416
"github.com/prometheus/client_golang/prometheus/promhttp"
@@ -27,6 +29,8 @@ type Admin struct {
2729
metricsServer, healthCheckServer *http.Server
2830
health *http.ServeMux
2931
quota int
32+
offload stnrv1.OffloadMode
33+
offloadIntfs []string
3034
LicenseManager licensecfg.ConfigManager
3135
licenseConfig *stnrv1.LicenseConfig
3236
log logging.LeveledLogger
@@ -43,6 +47,7 @@ func NewAdmin(conf stnrv1.Config, dryRun bool, rc ReadinessHandler, status Statu
4347
DryRun: dryRun,
4448
health: http.NewServeMux(),
4549
LicenseManager: licensecfg.New(logger.NewLogger("license")),
50+
offload: stnrv1.OffloadEngineNone,
4651
log: logger.NewLogger("admin"),
4752
}
4853
admin.log.Tracef("NewAdmin: %s", req.String())
@@ -56,7 +61,7 @@ func NewAdmin(conf stnrv1.Config, dryRun bool, rc ReadinessHandler, status Statu
5661
})
5762

5863
// readniness checker calls the checker from the factory
59-
admin.health.HandleFunc("/ready", func(w http.ResponseWriter, req *http.Request) {
64+
admin.health.HandleFunc("/ready", func(w http.ResponseWriter, _ *http.Request) {
6065
w.Header().Set("Content-Type", "application/json; charset=utf-8")
6166
if err := rc(); err != nil {
6267
w.WriteHeader(http.StatusServiceUnavailable)
@@ -71,7 +76,7 @@ func NewAdmin(conf stnrv1.Config, dryRun bool, rc ReadinessHandler, status Statu
7176
})
7277

7378
// status handler returns the status
74-
admin.health.HandleFunc("/status", func(w http.ResponseWriter, req *http.Request) {
79+
admin.health.HandleFunc("/status", func(w http.ResponseWriter, _ *http.Request) {
7580
w.Header().Set("Content-Type", "application/json; charset=utf-8")
7681
if js, err := json.Marshal(status()); err != nil {
7782
w.WriteHeader(http.StatusInternalServerError)
@@ -93,7 +98,25 @@ func NewAdmin(conf stnrv1.Config, dryRun bool, rc ReadinessHandler, status Statu
9398
// Inspect examines whether a configuration change requires a reconciliation (returns true if it
9499
// does) or restart (returns ErrRestartRequired).
95100
func (a *Admin) Inspect(old, new, full stnrv1.Config) (bool, error) {
96-
return !old.DeepEqual(new), nil
101+
oldConf, ok := old.(*stnrv1.AdminConfig)
102+
if !ok {
103+
return false, stnrv1.ErrInvalidConf
104+
}
105+
106+
newConf, ok := new.(*stnrv1.AdminConfig)
107+
if !ok {
108+
return false, stnrv1.ErrInvalidConf
109+
}
110+
111+
changed := !oldConf.DeepEqual(newConf)
112+
113+
// restart the admin object if the offload engine or the interfaces change
114+
var restart error
115+
if oldConf.OffloadEngine != newConf.OffloadEngine ||
116+
!reflect.DeepEqual(oldConf.OffloadInterfaces, newConf.OffloadInterfaces) {
117+
restart = ErrRestartRequired
118+
}
119+
return changed, restart
97120
}
98121

99122
// Reconcile updates the authenticator for a new configuration. Requires a valid reconciliation
@@ -127,6 +150,9 @@ func (a *Admin) Reconcile(conf stnrv1.Config) error {
127150

128151
a.quota = req.UserQuota
129152

153+
a.offload, _ = stnrv1.NewOffloadEngine(req.OffloadEngine)
154+
a.offloadIntfs = req.OffloadInterfaces
155+
130156
a.LicenseManager.Reconcile(req.LicenseConfig)
131157
a.licenseConfig = req.LicenseConfig
132158

@@ -157,6 +183,8 @@ func (a *Admin) GetConfig() stnrv1.Config {
157183
MetricsEndpoint: a.MetricsEndpoint,
158184
HealthCheckEndpoint: &h,
159185
UserQuota: a.quota,
186+
OffloadEngine: a.offload.String(),
187+
OffloadInterfaces: a.offloadIntfs,
160188
LicenseConfig: a.licenseConfig,
161189
}
162190
}
@@ -186,12 +214,17 @@ func (a *Admin) Close() error {
186214

187215
// Status returns the status of the object.
188216
func (a *Admin) Status() stnrv1.Status {
217+
offloadIntfs := ""
218+
if a.offload != stnrv1.OffloadEngineNone && len(a.offloadIntfs) > 0 {
219+
offloadIntfs = strings.Join(a.offloadIntfs, ",")
220+
}
189221
s := stnrv1.AdminStatus{
190222
Name: a.Name,
191223
LogLevel: a.LogLevel,
192224
MetricsEndpoint: a.MetricsEndpoint,
193225
HealthCheckEndpoint: a.HealthCheckEndpoint,
194226
UserQuota: fmt.Sprintf("%d", a.quota),
227+
OffloadStatus: fmt.Sprintf("offload=%s%s", a.offload.String(), offloadIntfs),
195228
LicensingInfo: a.LicenseManager.Status(),
196229
}
197230
return &s

pkg/apis/v1/admin.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"net/url"
66
"reflect"
7+
"sort"
78
"strings"
89
)
910

@@ -28,6 +29,12 @@ type AdminConfig struct {
2829
// UserQuota defines the number of permitted TURN allocatoins per username. Affects
2930
// allocation created on any listener. Default is 0, meaning no quota is enforced.
3031
UserQuota int `json:"user_quota,omitempty"`
32+
// OffloadEngine defines the dataplane offload mode, either "None", "XDP", "TC", or
33+
// "Auto". Set to "Auto" to let STUNner find the optimal offload mode. Default is "None".
34+
OffloadEngine string `json:"offload_engine,omitempty"`
35+
// OffloadInterfaces explicitly specifies the interfaces on which to enable the offload
36+
// engine. Empty list means to enable offload on all interfaces (this is the default).
37+
OffloadInterfaces []string `json:"offload_interfaces,omitempty"`
3138
// LicenseConfig describes the licensing info to be used to check subscription status with
3239
// the license server.
3340
LicenseConfig *LicenseConfig `json:"license_config,omitempty"`
@@ -76,6 +83,21 @@ func (req *AdminConfig) Validate() error {
7683
req.UserQuota = 0
7784
}
7885

86+
// Normalize
87+
if req.OffloadEngine == "" {
88+
req.OffloadEngine = OffloadEngineNone.String()
89+
}
90+
t, err := NewOffloadEngine(req.OffloadEngine)
91+
if err != nil {
92+
return err
93+
}
94+
req.OffloadEngine = t.String()
95+
96+
if req.OffloadInterfaces == nil {
97+
req.OffloadInterfaces = []string{}
98+
}
99+
sort.Strings(req.OffloadInterfaces)
100+
79101
return nil
80102
}
81103

@@ -94,6 +116,8 @@ func (req *AdminConfig) DeepEqual(other Config) bool {
94116
func (req *AdminConfig) DeepCopyInto(dst Config) {
95117
ret := dst.(*AdminConfig)
96118
*ret = *req
119+
ret.OffloadInterfaces = make([]string, len(req.OffloadInterfaces))
120+
copy(ret.OffloadInterfaces, req.OffloadInterfaces)
97121
}
98122

99123
// String stringifies the configuration.
@@ -114,6 +138,13 @@ func (req *AdminConfig) String() string {
114138
if req.UserQuota > 0 {
115139
status = append(status, fmt.Sprintf("quota=%d", req.UserQuota))
116140
}
141+
if req.OffloadEngine != "" {
142+
intfs := ""
143+
if req.OffloadEngine != "None" && len(req.OffloadInterfaces) > 0 {
144+
intfs = fmt.Sprintf("<%s>", strings.Join(req.OffloadInterfaces, ","))
145+
}
146+
status = append(status, fmt.Sprintf("offload=%s%s", req.OffloadEngine, intfs))
147+
}
117148
status = append(status, fmt.Sprintf("license_info=%s", LicensingStatus(req.LicenseConfig)))
118149

119150
return fmt.Sprintf("admin:{%s}", strings.Join(status, ","))
@@ -142,6 +173,7 @@ type AdminStatus struct {
142173
MetricsEndpoint string `json:"metrics_endpoint,omitempty"`
143174
HealthCheckEndpoint string `json:"healthcheck_endpoint,omitempty"`
144175
UserQuota string `json:"quota,omitempty"`
176+
OffloadStatus string `json:"offload,omitempty"`
145177
LicensingInfo string `json:"licensing_info,omitempty"`
146178
}
147179

@@ -161,6 +193,9 @@ func (a *AdminStatus) String() string {
161193
if a.LicensingInfo != "" {
162194
status = append(status, fmt.Sprintf("license-info=%s", a.LicensingInfo))
163195
}
196+
if a.OffloadStatus != "" {
197+
status = append(status, fmt.Sprintf("offload=%s", a.OffloadStatus))
198+
}
164199

165200
return fmt.Sprintf("admin:{%s}", strings.Join(status, ","))
166201
}

pkg/apis/v1/util.go

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ func NewClusterType(raw string) (ClusterType, error) {
148148
case clusterTypeStrictDNSStr:
149149
return ClusterTypeStrictDNS, nil
150150
default:
151-
return ClusterType(ClusterTypeUnknown), fmt.Errorf("unknown cluster type: \"%s\"", raw)
151+
return ClusterType(ClusterTypeUnknown),
152+
fmt.Errorf("unknown cluster type: \"%s\"", raw)
152153
}
153154
}
154155

@@ -201,3 +202,53 @@ func (p ClusterProtocol) String() string {
201202
return "<unknown>"
202203
}
203204
}
205+
206+
// OffloadEngine specifies the type of TURN offload mode.
207+
type OffloadMode int
208+
209+
const (
210+
OffloadEngineNone OffloadMode = iota
211+
OffloadEngineXDP
212+
OffloadEngineTC
213+
OffloadEngineAuto
214+
)
215+
216+
const (
217+
offloadEngineNoneStr = "none"
218+
offloadEngineXDPStr = "XDP"
219+
offloadEngineTCStr = "TC"
220+
offloadEngineAutoStr = "Auto"
221+
)
222+
223+
// NewOffloadEngine parses the offload mode.
224+
func NewOffloadEngine(raw string) (OffloadMode, error) {
225+
switch strings.ToLower(raw) {
226+
case strings.ToLower(offloadEngineNoneStr):
227+
return OffloadEngineNone, nil
228+
case strings.ToLower(offloadEngineXDPStr):
229+
return OffloadEngineXDP, nil
230+
case strings.ToLower(offloadEngineTCStr):
231+
return OffloadEngineTC, nil
232+
case strings.ToLower(offloadEngineAutoStr):
233+
return OffloadEngineAuto, nil
234+
default:
235+
return OffloadEngineNone,
236+
fmt.Errorf("unknown offload mode: %q", raw)
237+
}
238+
}
239+
240+
// String returns a string representation of a cluster protocol.
241+
func (p OffloadMode) String() string {
242+
switch p {
243+
case OffloadEngineNone:
244+
return offloadEngineNoneStr
245+
case OffloadEngineXDP:
246+
return offloadEngineXDPStr
247+
case OffloadEngineTC:
248+
return offloadEngineTCStr
249+
case OffloadEngineAuto:
250+
return offloadEngineAutoStr
251+
default:
252+
return "<unknown>"
253+
}
254+
}

0 commit comments

Comments
 (0)