6 "github.com/edwarnicke/exechelper"
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"
18 const vppConfigTemplate = `unix {
20 log %[1]s/var/log/vpp/vpp.log
23 runtime-dir %[1]s/var/run
36 socket-name %[1]s/var/run/vpp/api.sock
40 socket-name %[1]s/var/run/vpp/stats.sock
44 plugin default { disable }
46 plugin unittest_plugin.so { enable }
47 plugin quic_plugin.so { enable }
48 plugin af_packet_plugin.so { enable }
49 plugin hs_apps_plugin.so { enable }
50 plugin http_plugin.so { enable }
56 defaultCliSocketFilePath = "/var/run/vpp/cli.sock"
57 defaultApiSocketFilePath = "/var/run/vpp/api.sock"
60 type VppInstance struct {
64 connection *core.Connection
65 apiChannel api.Channel
68 type VppConfig struct {
70 CliSocketFilePath string
71 additionalConfig Stanza
74 func (vc *VppConfig) getTemplate() string {
75 return fmt.Sprintf(vppConfigTemplate, "%[1]s", vc.CliSocketFilePath)
78 func (vpp *VppInstance) Suite() *HstSuite {
79 return vpp.container.suite
82 func (vpp *VppInstance) setVppProxy() {
83 vpp.actionFuncName = "ConfigureVppProxy"
86 func (vpp *VppInstance) setEnvoyProxy() {
87 vpp.actionFuncName = "ConfigureEnvoyProxy"
90 func (vpp *VppInstance) setCliSocket(filePath string) {
91 vpp.config.CliSocketFilePath = filePath
94 func (vpp *VppInstance) getCliSocket() string {
95 return fmt.Sprintf("%s%s", vpp.container.GetContainerWorkDir(), vpp.config.CliSocketFilePath)
98 func (vpp *VppInstance) getRunDir() string {
99 return vpp.container.GetContainerWorkDir() + "/var/run/vpp"
102 func (vpp *VppInstance) getLogDir() string {
103 return vpp.container.GetContainerWorkDir() + "/var/log/vpp"
106 func (vpp *VppInstance) getEtcDir() string {
107 return vpp.container.GetContainerWorkDir() + "/etc/vpp"
110 func (vpp *VppInstance) legacyStart() error {
111 serializedConfig, err := serializeVppConfig(vpp.config)
113 return fmt.Errorf("serialize vpp config: %v", err)
115 args := fmt.Sprintf("%s '%s'", vpp.actionFuncName, serializedConfig)
116 _, err = vpp.container.execAction(args)
118 return fmt.Errorf("vpp start failed: %s", err)
123 func (vpp *VppInstance) start() error {
124 if vpp.actionFuncName != "" {
125 return vpp.legacyStart()
129 containerWorkDir := vpp.container.GetContainerWorkDir()
131 vpp.container.exec("mkdir --mode=0700 -p " + vpp.getRunDir())
132 vpp.container.exec("mkdir --mode=0700 -p " + vpp.getLogDir())
133 vpp.container.exec("mkdir --mode=0700 -p " + vpp.getEtcDir())
135 // Create startup.conf inside the container
136 configContent := fmt.Sprintf(vppConfigTemplate, containerWorkDir, vpp.config.CliSocketFilePath)
137 configContent += vpp.config.additionalConfig.ToString()
138 startupFileName := vpp.getEtcDir() + "/startup.conf"
139 vpp.container.createFile(startupFileName, configContent)
142 vpp.container.execServer("vpp -c " + startupFileName)
144 // Connect to VPP and store the connection
145 sockAddress := vpp.container.GetHostWorkDir() + defaultApiSocketFilePath
146 conn, connEv, err := govpp.AsyncConnect(
148 core.DefaultMaxReconnectAttempts,
149 core.DefaultReconnectInterval)
151 fmt.Println("async connect error: ", err)
153 vpp.connection = conn
155 // ... wait for Connected event
157 if e.State != core.Connected {
158 fmt.Println("connecting to VPP failed: ", e.Error)
161 // ... check compatibility of used messages
162 ch, err := conn.NewAPIChannel()
164 fmt.Println("creating channel failed: ", err)
166 if err := ch.CheckCompatiblity(vpe.AllMessages()...); err != nil {
167 fmt.Println("compatibility error: ", err)
169 if err := ch.CheckCompatiblity(interfaces.AllMessages()...); err != nil {
170 fmt.Println("compatibility error: ", err)
177 func (vpp *VppInstance) vppctl(command string, arguments ...any) string {
178 vppCliCommand := fmt.Sprintf(command, arguments...)
179 containerExecCommand := fmt.Sprintf("docker exec --detach=false %[1]s vppctl -s %[2]s %[3]s",
180 vpp.container.name, vpp.getCliSocket(), vppCliCommand)
181 vpp.Suite().log(containerExecCommand)
182 output, err := exechelper.CombinedOutput(containerExecCommand)
183 vpp.Suite().assertNil(err)
185 return string(output)
188 func NewVppInstance(c *Container) *VppInstance {
189 vppConfig := new(VppConfig)
190 vppConfig.CliSocketFilePath = defaultCliSocketFilePath
191 vpp := new(VppInstance)
193 vpp.config = vppConfig
197 func serializeVppConfig(vppConfig *VppConfig) (string, error) {
198 serializedConfig, err := json.Marshal(vppConfig)
200 return "", fmt.Errorf("vpp start failed: serializing configuration failed: %s", err)
202 return string(serializedConfig), nil
205 func deserializeVppConfig(input string) (VppConfig, error) {
206 var vppConfig VppConfig
207 err := json.Unmarshal([]byte(input), &vppConfig)
209 // Since input is not a valid JSON it is going be used as a variant value
210 // for compatibility reasons
211 vppConfig.Variant = input
212 vppConfig.CliSocketFilePath = defaultCliSocketFilePath
214 return vppConfig, nil
217 func (vpp *VppInstance) createAfPacket(
218 netInterface NetInterface,
219 ) (interface_types.InterfaceIndex, error) {
220 var veth *NetworkInterfaceVeth
221 veth = netInterface.(*NetworkInterfaceVeth)
223 createReq := &af_packet.AfPacketCreateV2{
224 UseRandomHwAddr: true,
225 HostIfName: veth.Name(),
227 if veth.HwAddress() != (MacAddress{}) {
228 createReq.UseRandomHwAddr = false
229 createReq.HwAddr = veth.HwAddress()
231 createReply := &af_packet.AfPacketCreateV2Reply{}
233 if err := vpp.apiChannel.SendRequest(createReq).ReceiveReply(createReply); err != nil {
236 veth.SetIndex(createReply.SwIfIndex)
239 upReq := &interfaces.SwInterfaceSetFlags{
240 SwIfIndex: veth.Index(),
241 Flags: interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
243 upReply := &interfaces.SwInterfaceSetFlagsReply{}
245 if err := vpp.apiChannel.SendRequest(upReq).ReceiveReply(upReply); err != nil {
250 if veth.Ip4AddressWithPrefix() == (AddressWithPrefix{}) {
251 if veth.peerNetworkNamespace != "" {
252 ip4Address, err := veth.addresser.
253 NewIp4AddressWithNamespace(veth.peerNetworkNamespace)
255 veth.SetAddress(ip4Address)
260 ip4Address, err := veth.addresser.
263 veth.SetAddress(ip4Address)
269 addressReq := &interfaces.SwInterfaceAddDelAddress{
271 SwIfIndex: veth.Index(),
272 Prefix: veth.Ip4AddressWithPrefix(),
274 addressReply := &interfaces.SwInterfaceAddDelAddressReply{}
276 if err := vpp.apiChannel.SendRequest(addressReq).ReceiveReply(addressReply); err != nil {
280 return veth.Index(), nil
283 func (vpp *VppInstance) addAppNamespace(
285 ifx interface_types.InterfaceIndex,
288 req := &session.AppNamespaceAddDelV2{
291 NamespaceID: namespaceId,
293 reply := &session.AppNamespaceAddDelV2Reply{}
295 if err := vpp.apiChannel.SendRequest(req).ReceiveReply(reply); err != nil {
299 sessionReq := &session.SessionEnableDisable{
302 sessionReply := &session.SessionEnableDisableReply{}
304 if err := vpp.apiChannel.SendRequest(sessionReq).ReceiveReply(sessionReply); err != nil {
311 func (vpp *VppInstance) disconnect() {
312 vpp.connection.Disconnect()
313 vpp.apiChannel.Close()