5 "github.com/edwarnicke/exechelper"
11 "go.fd.io/govpp/binapi/af_packet"
12 interfaces "go.fd.io/govpp/binapi/interface"
13 "go.fd.io/govpp/binapi/interface_types"
14 "go.fd.io/govpp/binapi/session"
15 "go.fd.io/govpp/binapi/tapv2"
16 "go.fd.io/govpp/binapi/vpe"
20 const vppConfigTemplate = `unix {
22 log %[1]s/var/log/vpp/vpp.log
25 runtime-dir %[1]s/var/run
38 socket-name %[1]s/var/run/vpp/api.sock
42 socket-name %[1]s/var/run/vpp/stats.sock
46 plugin default { disable }
48 plugin unittest_plugin.so { enable }
49 plugin quic_plugin.so { enable }
50 plugin af_packet_plugin.so { enable }
51 plugin hs_apps_plugin.so { enable }
52 plugin http_plugin.so { enable }
58 defaultCliSocketFilePath = "/var/run/vpp/cli.sock"
59 defaultApiSocketFilePath = "/var/run/vpp/api.sock"
62 type VppInstance struct {
66 connection *core.Connection
67 apiChannel api.Channel
70 type VppConfig struct {
72 CliSocketFilePath string
73 additionalConfig Stanza
76 func (vc *VppConfig) getTemplate() string {
77 return fmt.Sprintf(vppConfigTemplate, "%[1]s", vc.CliSocketFilePath)
80 func (vpp *VppInstance) Suite() *HstSuite {
81 return vpp.container.suite
84 func (vpp *VppInstance) setCliSocket(filePath string) {
85 vpp.config.CliSocketFilePath = filePath
88 func (vpp *VppInstance) getCliSocket() string {
89 return fmt.Sprintf("%s%s", vpp.container.GetContainerWorkDir(), vpp.config.CliSocketFilePath)
92 func (vpp *VppInstance) getRunDir() string {
93 return vpp.container.GetContainerWorkDir() + "/var/run/vpp"
96 func (vpp *VppInstance) getLogDir() string {
97 return vpp.container.GetContainerWorkDir() + "/var/log/vpp"
100 func (vpp *VppInstance) getEtcDir() string {
101 return vpp.container.GetContainerWorkDir() + "/etc/vpp"
104 func (vpp *VppInstance) start() error {
106 containerWorkDir := vpp.container.GetContainerWorkDir()
108 vpp.container.exec("mkdir --mode=0700 -p " + vpp.getRunDir())
109 vpp.container.exec("mkdir --mode=0700 -p " + vpp.getLogDir())
110 vpp.container.exec("mkdir --mode=0700 -p " + vpp.getEtcDir())
112 // Create startup.conf inside the container
113 configContent := fmt.Sprintf(vppConfigTemplate, containerWorkDir, vpp.config.CliSocketFilePath)
114 configContent += vpp.config.additionalConfig.ToString()
115 startupFileName := vpp.getEtcDir() + "/startup.conf"
116 vpp.container.createFile(startupFileName, configContent)
119 vpp.container.execServer("vpp -c " + startupFileName)
121 // Connect to VPP and store the connection
122 sockAddress := vpp.container.GetHostWorkDir() + defaultApiSocketFilePath
123 conn, connEv, err := govpp.AsyncConnect(
125 core.DefaultMaxReconnectAttempts,
126 core.DefaultReconnectInterval)
128 fmt.Println("async connect error: ", err)
130 vpp.connection = conn
132 // ... wait for Connected event
134 if e.State != core.Connected {
135 fmt.Println("connecting to VPP failed: ", e.Error)
138 // ... check compatibility of used messages
139 ch, err := conn.NewAPIChannel()
141 fmt.Println("creating channel failed: ", err)
143 if err := ch.CheckCompatiblity(vpe.AllMessages()...); err != nil {
144 fmt.Println("compatibility error: ", err)
146 if err := ch.CheckCompatiblity(interfaces.AllMessages()...); err != nil {
147 fmt.Println("compatibility error: ", err)
154 func (vpp *VppInstance) vppctl(command string, arguments ...any) string {
155 vppCliCommand := fmt.Sprintf(command, arguments...)
156 containerExecCommand := fmt.Sprintf("docker exec --detach=false %[1]s vppctl -s %[2]s %[3]s",
157 vpp.container.name, vpp.getCliSocket(), vppCliCommand)
158 vpp.Suite().log(containerExecCommand)
159 output, err := exechelper.CombinedOutput(containerExecCommand)
160 vpp.Suite().assertNil(err)
162 return string(output)
165 func (vpp *VppInstance) waitForApp(appName string, timeout int) error {
166 for i := 0; i < timeout; i++ {
167 o := vpp.vppctl("show app")
168 if strings.Contains(o, appName) {
171 time.Sleep(1 * time.Second)
173 return fmt.Errorf("Timeout while waiting for app '%s'", appName)
176 func (vpp *VppInstance) createAfPacket(
177 netInterface NetInterface,
178 ) (interface_types.InterfaceIndex, error) {
179 var veth *NetworkInterfaceVeth
180 veth = netInterface.(*NetworkInterfaceVeth)
182 createReq := &af_packet.AfPacketCreateV2{
183 UseRandomHwAddr: true,
184 HostIfName: veth.Name(),
186 if veth.HwAddress() != (MacAddress{}) {
187 createReq.UseRandomHwAddr = false
188 createReq.HwAddr = veth.HwAddress()
190 createReply := &af_packet.AfPacketCreateV2Reply{}
192 if err := vpp.apiChannel.SendRequest(createReq).ReceiveReply(createReply); err != nil {
195 veth.SetIndex(createReply.SwIfIndex)
198 upReq := &interfaces.SwInterfaceSetFlags{
199 SwIfIndex: veth.Index(),
200 Flags: interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
202 upReply := &interfaces.SwInterfaceSetFlagsReply{}
204 if err := vpp.apiChannel.SendRequest(upReq).ReceiveReply(upReply); err != nil {
209 if veth.AddressWithPrefix() == (AddressWithPrefix{}) {
211 var ip4Address string
212 if veth.peerNetworkNamespace != "" {
213 ip4Address, err = veth.addresser.
214 NewIp4AddressWithNamespace(veth.peerNetworkNamespace)
216 ip4Address, err = veth.addresser.
220 veth.SetAddress(ip4Address)
225 addressReq := &interfaces.SwInterfaceAddDelAddress{
227 SwIfIndex: veth.Index(),
228 Prefix: veth.AddressWithPrefix(),
230 addressReply := &interfaces.SwInterfaceAddDelAddressReply{}
232 if err := vpp.apiChannel.SendRequest(addressReq).ReceiveReply(addressReply); err != nil {
236 return veth.Index(), nil
239 func (vpp *VppInstance) addAppNamespace(
241 ifx interface_types.InterfaceIndex,
244 req := &session.AppNamespaceAddDelV2{
247 NamespaceID: namespaceId,
249 reply := &session.AppNamespaceAddDelV2Reply{}
251 if err := vpp.apiChannel.SendRequest(req).ReceiveReply(reply); err != nil {
255 sessionReq := &session.SessionEnableDisable{
258 sessionReply := &session.SessionEnableDisableReply{}
260 if err := vpp.apiChannel.SendRequest(sessionReq).ReceiveReply(sessionReply); err != nil {
267 func (vpp *VppInstance) createTap(
268 hostInterfaceName string,
269 hostIp4Address IP4AddressWithPrefix,
270 vppIp4Address AddressWithPrefix,
272 createTapReq := &tapv2.TapCreateV2{
274 HostIfName: hostInterfaceName,
275 HostIP4PrefixSet: true,
276 HostIP4Prefix: hostIp4Address,
278 createTapReply := &tapv2.TapCreateV2Reply{}
280 // Create tap interface
281 if err := vpp.apiChannel.SendRequest(createTapReq).ReceiveReply(createTapReply); err != nil {
286 addAddressReq := &interfaces.SwInterfaceAddDelAddress{
288 SwIfIndex: createTapReply.SwIfIndex,
289 Prefix: vppIp4Address,
291 addAddressReply := &interfaces.SwInterfaceAddDelAddressReply{}
293 if err := vpp.apiChannel.SendRequest(addAddressReq).ReceiveReply(addAddressReply); err != nil {
297 // Set interface to up
298 upReq := &interfaces.SwInterfaceSetFlags{
299 SwIfIndex: createTapReply.SwIfIndex,
300 Flags: interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
302 upReply := &interfaces.SwInterfaceSetFlagsReply{}
304 if err := vpp.apiChannel.SendRequest(upReq).ReceiveReply(upReply); err != nil {
311 func (vpp *VppInstance) disconnect() {
312 vpp.connection.Disconnect()
313 vpp.apiChannel.Close()