blob: 3f9ea871565c77f20d387216d078378a851606c6 [file] [log] [blame]
Maros Ondrejicka11a03e92022-12-01 09:56:37 +01001package main
2
3import (
Maros Ondrejicka11a03e92022-12-01 09:56:37 +01004 "encoding/json"
Filip Tehlarf3ee2b62023-01-09 12:07:09 +01005 "fmt"
Maros Ondrejicka11a03e92022-12-01 09:56:37 +01006 "github.com/edwarnicke/exechelper"
Maros Ondrejickaffa3f602023-01-26 10:07:29 +01007
8 "go.fd.io/govpp"
9 "go.fd.io/govpp/api"
10 "go.fd.io/govpp/binapi/af_packet"
11 interfaces "go.fd.io/govpp/binapi/interface"
12 "go.fd.io/govpp/binapi/interface_types"
13 "go.fd.io/govpp/binapi/session"
14 "go.fd.io/govpp/binapi/vpe"
15 "go.fd.io/govpp/core"
Maros Ondrejicka11a03e92022-12-01 09:56:37 +010016)
17
18const vppConfigTemplate = `unix {
19 nodaemon
20 log %[1]s/var/log/vpp/vpp.log
21 full-coredump
22 cli-listen %[1]s%[2]s
23 runtime-dir %[1]s/var/run
24 gid vpp
25}
26
27api-trace {
28 on
29}
30
31api-segment {
32 gid vpp
33}
34
35socksvr {
36 socket-name %[1]s/var/run/vpp/api.sock
37}
38
39statseg {
40 socket-name %[1]s/var/run/vpp/stats.sock
41}
42
43plugins {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010044 plugin default { disable }
45
Maros Ondrejicka11a03e92022-12-01 09:56:37 +010046 plugin unittest_plugin.so { enable }
Maros Ondrejicka11a03e92022-12-01 09:56:37 +010047 plugin quic_plugin.so { enable }
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010048 plugin af_packet_plugin.so { enable }
49 plugin hs_apps_plugin.so { enable }
50 plugin http_plugin.so { enable }
Maros Ondrejicka11a03e92022-12-01 09:56:37 +010051}
52
53`
54
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010055const (
56 defaultCliSocketFilePath = "/var/run/vpp/cli.sock"
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010057 defaultApiSocketFilePath = "/var/run/vpp/api.sock"
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010058)
59
Maros Ondrejicka11a03e92022-12-01 09:56:37 +010060type VppInstance struct {
Filip Tehlarf3ee2b62023-01-09 12:07:09 +010061 container *Container
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010062 config *VppConfig
Maros Ondrejicka11a03e92022-12-01 09:56:37 +010063 actionFuncName string
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010064 connection *core.Connection
65 apiChannel api.Channel
Maros Ondrejicka11a03e92022-12-01 09:56:37 +010066}
67
68type VppConfig struct {
Filip Tehlarf3ee2b62023-01-09 12:07:09 +010069 Variant string
Maros Ondrejicka11a03e92022-12-01 09:56:37 +010070 CliSocketFilePath string
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010071 additionalConfig Stanza
Maros Ondrejicka11a03e92022-12-01 09:56:37 +010072}
73
74func (vc *VppConfig) getTemplate() string {
75 return fmt.Sprintf(vppConfigTemplate, "%[1]s", vc.CliSocketFilePath)
76}
77
78func (vpp *VppInstance) set2VethsServer() {
79 vpp.actionFuncName = "Configure2Veths"
80 vpp.config.Variant = "srv"
81}
82
83func (vpp *VppInstance) set2VethsClient() {
84 vpp.actionFuncName = "Configure2Veths"
85 vpp.config.Variant = "cln"
86}
87
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010088func (vpp *VppInstance) setVppProxy() {
89 vpp.actionFuncName = "ConfigureVppProxy"
90}
91
92func (vpp *VppInstance) setEnvoyProxy() {
93 vpp.actionFuncName = "ConfigureEnvoyProxy"
94}
95
Maros Ondrejicka11a03e92022-12-01 09:56:37 +010096func (vpp *VppInstance) setCliSocket(filePath string) {
97 vpp.config.CliSocketFilePath = filePath
98}
99
100func (vpp *VppInstance) getCliSocket() string {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100101 return fmt.Sprintf("%s%s", vpp.container.GetContainerWorkDir(), vpp.config.CliSocketFilePath)
Maros Ondrejicka11a03e92022-12-01 09:56:37 +0100102}
103
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100104func (vpp *VppInstance) getRunDir() string {
105 return vpp.container.GetContainerWorkDir() + "/var/run/vpp"
106}
107
108func (vpp *VppInstance) getLogDir() string {
109 return vpp.container.GetContainerWorkDir() + "/var/log/vpp"
110}
111
112func (vpp *VppInstance) getEtcDir() string {
113 return vpp.container.GetContainerWorkDir() + "/etc/vpp"
114}
115
116func (vpp *VppInstance) legacyStart() error {
Maros Ondrejicka11a03e92022-12-01 09:56:37 +0100117 if vpp.actionFuncName == "" {
118 return fmt.Errorf("vpp start failed: action function name must not be blank")
119 }
120
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100121 serializedConfig, err := serializeVppConfig(vpp.config)
Filip Tehlar56160412023-01-25 13:56:38 +0100122 if err != nil {
123 return fmt.Errorf("serialize vpp config: %v", err)
124 }
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100125 args := fmt.Sprintf("%s '%s'", vpp.actionFuncName, serializedConfig)
126 _, err = vpp.container.execAction(args)
Maros Ondrejicka11a03e92022-12-01 09:56:37 +0100127 if err != nil {
128 return fmt.Errorf("vpp start failed: %s", err)
129 }
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100130 return nil
131}
132
133func (vpp *VppInstance) start() error {
134 if vpp.actionFuncName != "" {
135 return vpp.legacyStart()
136 }
137
138 // Create folders
139 containerWorkDir := vpp.container.GetContainerWorkDir()
140
141 vpp.container.exec("mkdir --mode=0700 -p " + vpp.getRunDir())
142 vpp.container.exec("mkdir --mode=0700 -p " + vpp.getLogDir())
143 vpp.container.exec("mkdir --mode=0700 -p " + vpp.getEtcDir())
144
145 // Create startup.conf inside the container
146 configContent := fmt.Sprintf(vppConfigTemplate, containerWorkDir, vpp.config.CliSocketFilePath)
147 configContent += vpp.config.additionalConfig.ToString()
148 startupFileName := vpp.getEtcDir() + "/startup.conf"
149 vpp.container.createFile(startupFileName, configContent)
150
151 // Start VPP
152 if err := vpp.container.execServer("vpp -c " + startupFileName); err != nil {
153 return err
154 }
155
156 // Connect to VPP and store the connection
157 sockAddress := vpp.container.GetHostWorkDir() + defaultApiSocketFilePath
158 conn, connEv, err := govpp.AsyncConnect(
159 sockAddress,
160 core.DefaultMaxReconnectAttempts,
161 core.DefaultReconnectInterval)
162 if err != nil {
163 fmt.Println("async connect error: ", err)
164 }
165 vpp.connection = conn
166
167 // ... wait for Connected event
168 e := <-connEv
169 if e.State != core.Connected {
170 fmt.Println("connecting to VPP failed: ", e.Error)
171 }
172
173 // ... check compatibility of used messages
174 ch, err := conn.NewAPIChannel()
175 if err != nil {
176 fmt.Println("creating channel failed: ", err)
177 }
178 if err := ch.CheckCompatiblity(vpe.AllMessages()...); err != nil {
179 fmt.Println("compatibility error: ", err)
180 }
181 if err := ch.CheckCompatiblity(interfaces.AllMessages()...); err != nil {
182 fmt.Println("compatibility error: ", err)
183 }
184 vpp.apiChannel = ch
Maros Ondrejicka11a03e92022-12-01 09:56:37 +0100185
186 return nil
187}
188
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100189func (vpp *VppInstance) vppctl(command string, arguments ...any) (string, error) {
190 vppCliCommand := fmt.Sprintf(command, arguments...)
191 containerExecCommand := fmt.Sprintf("docker exec --detach=false %[1]s vppctl -s %[2]s %[3]s",
192 vpp.container.name, vpp.getCliSocket(), vppCliCommand)
193 output, err := exechelper.CombinedOutput(containerExecCommand)
Maros Ondrejicka11a03e92022-12-01 09:56:37 +0100194 if err != nil {
195 return "", fmt.Errorf("vppctl failed: %s", err)
196 }
197
198 return string(output), nil
199}
200
201func NewVppInstance(c *Container) *VppInstance {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100202 vppConfig := new(VppConfig)
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100203 vppConfig.CliSocketFilePath = defaultCliSocketFilePath
Maros Ondrejicka11a03e92022-12-01 09:56:37 +0100204 vpp := new(VppInstance)
205 vpp.container = c
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100206 vpp.config = vppConfig
Maros Ondrejicka11a03e92022-12-01 09:56:37 +0100207 return vpp
208}
209
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100210func serializeVppConfig(vppConfig *VppConfig) (string, error) {
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100211 serializedConfig, err := json.Marshal(vppConfig)
212 if err != nil {
213 return "", fmt.Errorf("vpp start failed: serializing configuration failed: %s", err)
214 }
215 return string(serializedConfig), nil
216}
217
218func deserializeVppConfig(input string) (VppConfig, error) {
Maros Ondrejicka11a03e92022-12-01 09:56:37 +0100219 var vppConfig VppConfig
220 err := json.Unmarshal([]byte(input), &vppConfig)
221 if err != nil {
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100222 // Since input is not a valid JSON it is going be used as a variant value
Maros Ondrejicka11a03e92022-12-01 09:56:37 +0100223 // for compatibility reasons
224 vppConfig.Variant = input
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100225 vppConfig.CliSocketFilePath = defaultCliSocketFilePath
Maros Ondrejicka11a03e92022-12-01 09:56:37 +0100226 }
227 return vppConfig, nil
228}
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100229
230func (vpp *VppInstance) createAfPacket(
231 veth *NetworkInterfaceVeth,
232) (interface_types.InterfaceIndex, error) {
233 createReq := &af_packet.AfPacketCreateV2{
234 UseRandomHwAddr: true,
235 HostIfName: veth.Name(),
236 }
237 if veth.hwAddress != (MacAddress{}) {
238 createReq.UseRandomHwAddr = false
239 createReq.HwAddr = veth.hwAddress
240 }
241 createReply := &af_packet.AfPacketCreateV2Reply{}
242
243 if err := vpp.apiChannel.SendRequest(createReq).ReceiveReply(createReply); err != nil {
244 return 0, err
245 }
246 veth.index = createReply.SwIfIndex
247
248 // Set to up
249 upReq := &interfaces.SwInterfaceSetFlags{
250 SwIfIndex: veth.index,
251 Flags: interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
252 }
253 upReply := &interfaces.SwInterfaceSetFlagsReply{}
254
255 if err := vpp.apiChannel.SendRequest(upReq).ReceiveReply(upReply); err != nil {
256 return 0, err
257 }
258
259 // Add address
260 if veth.ip4Address == (AddressWithPrefix{}) {
261 ipPrefix, err := vpp.container.suite.NewAddress()
262 if err != nil {
263 return 0, err
264 }
265 veth.ip4Address = ipPrefix
266 }
267 addressReq := &interfaces.SwInterfaceAddDelAddress{
268 IsAdd: true,
269 SwIfIndex: veth.index,
270 Prefix: veth.ip4Address,
271 }
272 addressReply := &interfaces.SwInterfaceAddDelAddressReply{}
273
274 if err := vpp.apiChannel.SendRequest(addressReq).ReceiveReply(addressReply); err != nil {
275 return 0, err
276 }
277
278 return veth.index, nil
279}
280
281func (vpp *VppInstance) addAppNamespace(
282 secret uint64,
283 ifx interface_types.InterfaceIndex,
284 namespaceId string,
285) error {
286 req := &session.AppNamespaceAddDelV2{
287 Secret: secret,
288 SwIfIndex: ifx,
289 NamespaceID: namespaceId,
290 }
291 reply := &session.AppNamespaceAddDelV2Reply{}
292
293 if err := vpp.apiChannel.SendRequest(req).ReceiveReply(reply); err != nil {
294 return err
295 }
296
297 sessionReq := &session.SessionEnableDisable{
298 IsEnable: true,
299 }
300 sessionReply := &session.SessionEnableDisableReply{}
301
302 if err := vpp.apiChannel.SendRequest(sessionReq).ReceiveReply(sessionReply); err != nil {
303 return err
304 }
305
306 return nil
307}
308
309func (vpp *VppInstance) disconnect() {
310 vpp.connection.Disconnect()
311 vpp.apiChannel.Close()
312}