• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stillya / wg-relay / 24736809271

21 Apr 2026 05:29PM UTC coverage: 49.85% (-0.6%) from 50.494%
24736809271

push

github

web-flow
feat: enable IP forwarding on datapath startup (#26)

0 of 17 new or added lines in 3 files covered. (0.0%)

664 of 1332 relevant lines covered (49.85%)

0.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/pkg/dataplane/proxy/forward.go
1
package proxy
2

3
import (
4
        "context"
5
        "fmt"
6
        "net"
7

8
        log "log/slog"
9

10
        "github.com/cilium/ebpf"
11
        "github.com/cilium/ebpf/link"
12
        "github.com/pkg/errors"
13

14
        wgebpf "github.com/stillya/wg-relay/ebpf"
15
        "github.com/stillya/wg-relay/pkg/bpf"
16
        "github.com/stillya/wg-relay/pkg/dataplane/config"
17
        dplinux "github.com/stillya/wg-relay/pkg/dataplane/linux"
18
        "github.com/stillya/wg-relay/pkg/dataplane/maps"
19
        "github.com/stillya/wg-relay/pkg/utils"
20
)
21

22
// ForwardLoader manages XDP-based forward proxy
23
type ForwardLoader struct {
24
        cfg   config.ProxyConfig
25
        objs  *wgebpf.WgForwardProxyObjects
26
        links []link.Link
27
}
28

29
// NewForwardLoader creates a new forward proxy loader
30
func NewForwardLoader() (*ForwardLoader, error) {
×
31
        return &ForwardLoader{}, nil
×
32
}
×
33

34
// LoadAndAttach loads the eBPF program and attaches it to the configured interfaces.
35
func (fp *ForwardLoader) LoadAndAttach(ctx context.Context, cfg config.ProxyConfig) error {
×
36
        fp.cfg = cfg
×
37

×
NEW
38
        if err := dplinux.EnableIPForwarding(); err != nil {
×
NEW
39
                return errors.Wrap(err, "failed to enable IP forwarding")
×
NEW
40
        }
×
41

42
        if err := fp.loadEBPF(); err != nil {
×
43
                return errors.Wrap(err, "failed to load eBPF objects")
×
44
        }
×
45

46
        if err := fp.attachToInterfaces(); err != nil {
×
47
                fp.Close()
×
48
                return errors.Wrap(err, "failed to attach to interfaces")
×
49
        }
×
50

51
        backendCount := len(cfg.Forward.Backends)
×
52

×
53
        log.Info("Forward proxy loaded and attached",
×
54
                "enabled", cfg.Enabled,
×
55
                "backend_count", backendCount,
×
56
        )
×
57

×
58
        return nil
×
59
}
60

61
func (fp *ForwardLoader) loadEBPF() error {
×
62
        spec, err := wgebpf.LoadWgForwardProxy()
×
63
        if err != nil {
×
64
                return errors.Wrap(err, "failed to load forward proxy spec")
×
65
        }
×
66

67
        if fp.cfg.Instrumentations.Padding != nil && fp.cfg.Instrumentations.Padding.Enabled {
×
68
                mtu, err := detectMinMTU(fp.cfg.Interfaces)
×
69
                if err != nil {
×
70
                        return errors.Wrap(err, "failed to detect interface MTU")
×
71
                }
×
72
                fp.cfg.Instrumentations.Padding.LinkMTU = mtu
×
73
                fp.cfg.Instrumentations.Padding.Randomize = fp.cfg.Instrumentations.Padding.Mode == "randomize"
×
74
                if err := fp.cfg.Instrumentations.Padding.ValidateMTU(); err != nil {
×
75
                        return errors.Wrap(err, "padding MTU validation failed")
×
76
                }
×
77
                log.Info("Detected minimum MTU", "mtu", mtu)
×
78
        }
79

80
        if err := bpf.Configure(spec, &fp.cfg); err != nil {
×
81
                return errors.Wrap(err, "failed to configure static variables")
×
82
        }
×
83

84
        fp.objs = &wgebpf.WgForwardProxyObjects{}
×
85
        opts := &ebpf.CollectionOptions{
×
86
                Programs: ebpf.ProgramOptions{
×
87
                        LogLevel:     2,
×
88
                        LogSizeStart: 16777216,
×
89
                },
×
90
        }
×
91

×
92
        if err := spec.LoadAndAssign(fp.objs, opts); err != nil {
×
93
                return errors.Wrap(err, "failed to load forward proxy eBPF objects")
×
94
        }
×
95

96
        if err := fp.configureBackendMap(); err != nil {
×
97
                return errors.Wrap(err, "failed to configure backend map")
×
98
        }
×
99

100
        log.Info("Forward proxy eBPF program loaded")
×
101
        return nil
×
102
}
103

104
func (fp *ForwardLoader) configureBackendMap() error {
×
105
        backends := fp.cfg.GetBackends()
×
106

×
107
        if len(backends) == 0 {
×
108
                return errors.New("no backends configured")
×
109
        }
×
110

111
        for i, backend := range backends {
×
112
                key := uint32(i) //nolint:gosec // G304: it's fine
×
113
                ip, err := utils.IPToUint32(backend.IP)
×
114
                if err != nil {
×
115
                        return errors.Wrapf(err, "failed to convert IP address %s to uint32", backend.IP)
×
116
                }
×
117
                entry := &wgebpf.WgForwardProxyBackendEntry{
×
118
                        Ip:   ip,
×
119
                        Port: backend.Port,
×
120
                }
×
121
                if err := fp.objs.BackendMap.Put(&key, entry); err != nil {
×
122
                        return errors.Wrapf(err, "failed to add backend[%d] to map", i)
×
123
                }
×
124
        }
125

126
        countKey := uint32(0)
×
127
        count := uint32(len(backends)) //nolint:gosec // G304: it's fine
×
128
        if err := fp.objs.BackendCount.Put(&countKey, &count); err != nil {
×
129
                return errors.Wrap(err, "failed to set backend count")
×
130
        }
×
131

132
        portSet := make(map[uint16]struct{})
×
133
        for _, backend := range backends {
×
134
                port := backend.Port
×
135
                if port == 0 {
×
136
                        port = fp.cfg.WGPort
×
137
                }
×
138
                portSet[port] = struct{}{}
×
139
        }
140
        dummy := uint8(1)
×
141
        for port := range portSet {
×
142
                p := port
×
143
                if err := fp.objs.BackendPortSet.Put(&p, &dummy); err != nil {
×
144
                        return errors.Wrap(err, "failed to populate backend port set")
×
145
                }
×
146
        }
147

148
        backendAddrs := make([]string, len(backends))
×
149
        for i, b := range fp.cfg.Forward.Backends {
×
150
                if b.Port > 0 {
×
151
                        backendAddrs[i] = fmt.Sprintf("%s:%d", b.IP, b.Port)
×
152
                } else {
×
153
                        backendAddrs[i] = b.IP
×
154
                }
×
155
        }
156

157
        log.Info("Backend map configured",
×
158
                "count", len(backends),
×
159
                "backends", backendAddrs,
×
160
        )
×
161

×
162
        return nil
×
163
}
164

165
// attachToInterfaces attaches the XDP program to configured interfaces
166
func (fp *ForwardLoader) attachToInterfaces() error {
×
167
        for _, interfaceName := range fp.cfg.Interfaces {
×
168
                if err := fp.attachToInterface(interfaceName); err != nil {
×
169
                        fp.cleanupLinks()
×
170
                        return errors.Wrapf(err, "failed to attach to interface %s", interfaceName)
×
171
                }
×
172
        }
173
        return nil
×
174
}
175

176
// attachToInterface attaches XDP program to a single interface
177
func (fp *ForwardLoader) attachToInterface(interfaceName string) error {
×
178
        iface, err := net.InterfaceByName(interfaceName)
×
179
        if err != nil {
×
180
                return errors.Wrapf(err, "failed to get interface %s", interfaceName)
×
181
        }
×
182

183
        var flags link.XDPAttachFlags
×
184
        var mode string
×
185
        switch fp.cfg.DriverMode {
×
186
        case "generic":
×
187
                flags = link.XDPGenericMode
×
188
                mode = "generic"
×
189
        case "driver":
×
190
                flags = link.XDPDriverMode
×
191
                mode = "driver"
×
192
        case "offload":
×
193
                flags = link.XDPOffloadMode
×
194
                mode = "offload"
×
195
        default:
×
196
                // Default to driver mode
×
197
                flags = link.XDPDriverMode
×
198
                mode = "driver"
×
199
        }
200

201
        log.Info("Attaching XDP forward proxy", "interface", interfaceName, "index", iface.Index, "mode", mode)
×
202

×
203
        xdpLink, err := link.AttachXDP(link.XDPOptions{
×
204
                Program:   fp.objs.WgForwardProxy,
×
205
                Interface: iface.Index,
×
206
                Flags:     flags,
×
207
        })
×
208
        if err != nil {
×
209
                return errors.Wrapf(err, "failed to attach XDP program to interface %s in %s mode", interfaceName, mode)
×
210
        }
×
211

212
        log.Info("XDP forward proxy attached successfully", "interface", interfaceName, "mode", mode)
×
213
        fp.links = append(fp.links, xdpLink)
×
214
        return nil
×
215
}
216

217
// cleanupLinks cleans up all attached links
218
func (fp *ForwardLoader) cleanupLinks() {
×
219
        for _, l := range fp.links {
×
220
                if l != nil {
×
221
                        err := l.Close()
×
222
                        if err != nil {
×
223
                                log.Error("Failed to close XDP link", "error", err)
×
224
                        } else {
×
225
                                log.Info("XDP link closed successfully")
×
226
                        }
×
227
                }
228
        }
229
        fp.links = nil
×
230
}
231

232
// Close cleans up all resources
233
func (fp *ForwardLoader) Close() error {
×
234
        var errs []error
×
235

×
236
        for i, l := range fp.links {
×
237
                if l != nil {
×
238
                        if err := l.Close(); err != nil {
×
239
                                errs = append(errs, errors.Wrapf(err, "failed to close XDP l %d", i))
×
240
                        }
×
241
                }
242
        }
243

244
        if fp.objs != nil {
×
245
                if err := fp.objs.Close(); err != nil {
×
246
                        errs = append(errs, errors.Wrap(err, "failed to close forward proxy eBPF objects"))
×
247
                }
×
248
        }
249

250
        if len(errs) > 0 {
×
251
                return errs[0]
×
252
        }
×
253

254
        return nil
×
255
}
256

257
// Maps returns all eBPF maps used by the forward proxy
258
func (fp *ForwardLoader) Maps() *maps.Maps {
×
259
        var metricsMap *ebpf.Map
×
260
        if fp.objs != nil {
×
261
                metricsMap = fp.objs.MetricsMap
×
262
        }
×
263

264
        mapsCollection := maps.NewMaps(metricsMap)
×
265

×
266
        if fp.objs != nil {
×
267
                if fp.objs.ConnectionMap != nil {
×
268
                        mapsCollection.AddOtherMap("ConnectionMap", fp.objs.ConnectionMap)
×
269
                }
×
270

271
                if fp.objs.NatReverseMap != nil {
×
272
                        mapsCollection.AddOtherMap("NatReverseMap", fp.objs.NatReverseMap)
×
273
                }
×
274
        }
275

276
        return mapsCollection
×
277
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc