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) set2VethsServer() {
79 vpp.actionFuncName = "Configure2Veths"
80 vpp.config.Variant = "srv"
83 func (vpp *VppInstance) set2VethsClient() {
84 vpp.actionFuncName = "Configure2Veths"
85 vpp.config.Variant = "cln"
88 func (vpp *VppInstance) setVppProxy() {
89 vpp.actionFuncName = "ConfigureVppProxy"
92 func (vpp *VppInstance) setEnvoyProxy() {
93 vpp.actionFuncName = "ConfigureEnvoyProxy"
96 func (vpp *VppInstance) setCliSocket(filePath string) {
97 vpp.config.CliSocketFilePath = filePath
100 func (vpp *VppInstance) getCliSocket() string {
101 return fmt.Sprintf("%s%s", vpp.container.GetContainerWorkDir(), vpp.config.CliSocketFilePath)
104 func (vpp *VppInstance) getRunDir() string {
105 return vpp.container.GetContainerWorkDir() + "/var/run/vpp"
108 func (vpp *VppInstance) getLogDir() string {
109 return vpp.container.GetContainerWorkDir() + "/var/log/vpp"
112 func (vpp *VppInstance) getEtcDir() string {
113 return vpp.container.GetContainerWorkDir() + "/etc/vpp"
116 func (vpp *VppInstance) legacyStart() error {
117 if vpp.actionFuncName == "" {
118 return fmt.Errorf("vpp start failed: action function name must not be blank")
121 serializedConfig, err := serializeVppConfig(vpp.config)
123 return fmt.Errorf("serialize vpp config: %v", err)
125 args := fmt.Sprintf("%s '%s'", vpp.actionFuncName, serializedConfig)
126 _, err = vpp.container.execAction(args)
128 return fmt.Errorf("vpp start failed: %s", err)
133 func (vpp *VppInstance) start() error {
134 if vpp.actionFuncName != "" {
135 return vpp.legacyStart()
139 containerWorkDir := vpp.container.GetContainerWorkDir()
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())
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)
152 if err := vpp.container.execServer("vpp -c " + startupFileName); err != nil {
156 // Connect to VPP and store the connection
157 sockAddress := vpp.container.GetHostWorkDir() + defaultApiSocketFilePath
158 conn, connEv, err := govpp.AsyncConnect(
160 core.DefaultMaxReconnectAttempts,
161 core.DefaultReconnectInterval)
163 fmt.Println("async connect error: ", err)
165 vpp.connection = conn
167 // ... wait for Connected event
169 if e.State != core.Connected {
170 fmt.Println("connecting to VPP failed: ", e.Error)
173 // ... check compatibility of used messages
174 ch, err := conn.NewAPIChannel()
176 fmt.Println("creating channel failed: ", err)
178 if err := ch.CheckCompatiblity(vpe.AllMessages()...); err != nil {
179 fmt.Println("compatibility error: ", err)
181 if err := ch.CheckCompatiblity(interfaces.AllMessages()...); err != nil {
182 fmt.Println("compatibility error: ", err)
189 func (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)
195 return "", fmt.Errorf("vppctl failed: %s", err)
198 return string(output), nil
201 func NewVppInstance(c *Container) *VppInstance {
202 vppConfig := new(VppConfig)
203 vppConfig.CliSocketFilePath = defaultCliSocketFilePath
204 vpp := new(VppInstance)
206 vpp.config = vppConfig
210 func serializeVppConfig(vppConfig *VppConfig) (string, error) {
211 serializedConfig, err := json.Marshal(vppConfig)
213 return "", fmt.Errorf("vpp start failed: serializing configuration failed: %s", err)
215 return string(serializedConfig), nil
218 func deserializeVppConfig(input string) (VppConfig, error) {
219 var vppConfig VppConfig
220 err := json.Unmarshal([]byte(input), &vppConfig)
222 // Since input is not a valid JSON it is going be used as a variant value
223 // for compatibility reasons
224 vppConfig.Variant = input
225 vppConfig.CliSocketFilePath = defaultCliSocketFilePath
227 return vppConfig, nil
230 func (vpp *VppInstance) createAfPacket(
231 veth *NetworkInterfaceVeth,
232 ) (interface_types.InterfaceIndex, error) {
233 createReq := &af_packet.AfPacketCreateV2{
234 UseRandomHwAddr: true,
235 HostIfName: veth.Name(),
237 if veth.hwAddress != (MacAddress{}) {
238 createReq.UseRandomHwAddr = false
239 createReq.HwAddr = veth.hwAddress
241 createReply := &af_packet.AfPacketCreateV2Reply{}
243 if err := vpp.apiChannel.SendRequest(createReq).ReceiveReply(createReply); err != nil {
246 veth.index = createReply.SwIfIndex
249 upReq := &interfaces.SwInterfaceSetFlags{
250 SwIfIndex: veth.index,
251 Flags: interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
253 upReply := &interfaces.SwInterfaceSetFlagsReply{}
255 if err := vpp.apiChannel.SendRequest(upReq).ReceiveReply(upReply); err != nil {
260 if veth.ip4Address == (AddressWithPrefix{}) {
261 ipPrefix, err := vpp.container.suite.NewAddress()
265 veth.ip4Address = ipPrefix
267 addressReq := &interfaces.SwInterfaceAddDelAddress{
269 SwIfIndex: veth.index,
270 Prefix: veth.ip4Address,
272 addressReply := &interfaces.SwInterfaceAddDelAddressReply{}
274 if err := vpp.apiChannel.SendRequest(addressReq).ReceiveReply(addressReply); err != nil {
278 return veth.index, nil
281 func (vpp *VppInstance) addAppNamespace(
283 ifx interface_types.InterfaceIndex,
286 req := &session.AppNamespaceAddDelV2{
289 NamespaceID: namespaceId,
291 reply := &session.AppNamespaceAddDelV2Reply{}
293 if err := vpp.apiChannel.SendRequest(req).ReceiveReply(reply); err != nil {
297 sessionReq := &session.SessionEnableDisable{
300 sessionReply := &session.SessionEnableDisableReply{}
302 if err := vpp.apiChannel.SendRequest(sessionReq).ReceiveReply(sessionReply); err != nil {
309 func (vpp *VppInstance) disconnect() {
310 vpp.connection.Disconnect()
311 vpp.apiChannel.Close()