aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md23
-rw-r--r--Makefile2
-rw-r--r--adapter/socketclient/doc.go13
-rw-r--r--adapter/socketclient/socketclient.go437
-rw-r--r--api/binapi.go33
-rw-r--r--codec/msg_codec.go20
-rw-r--r--core/channel.go14
-rw-r--r--core/channel_test.go2
-rw-r--r--core/connection.go18
-rw-r--r--core/log.go21
-rw-r--r--core/request_handler.go20
-rw-r--r--examples/perf-bench/perf-bench.go23
-rw-r--r--examples/simple-client/simple_client.go78
-rw-r--r--examples/union-example/union_example.go101
-rw-r--r--go.mod6
-rw-r--r--go.sum19
-rw-r--r--version/version.go23
17 files changed, 521 insertions, 332 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 145d1b1..86e3edc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,29 @@ This file lists changes for the GoVPP releases.
-
-->
+## 0.4.0 (in development)
+> _NOT RELEASED YET_
+
+### Features
+- optimized [socketclient](adapter/socketclient) adapter and add method to set client name
+- added list of compatible messages to `CompatibilityError`
+
+### Fixes
+- `MsgCodec` will recover panic occurring during a message decoding
+- calling `Unsubscibe` will close the notification channel
+
+### Other
+- improved log messages to provide more relevant info
+
+#### Examples
+- added more code samples of working with unions in [union example](examples/union-example)
+- added profiling mode to [perf bench](examples/perf-bench) example
+- improved [simple client](examples/simple-client) example to work properly even with multiple runs
+
+#### Dependencies
+- updated `github.com/sirupsen/logrus` dep to `v1.6.0`
+- updated `github.com/lunixbochs/struc` dep to `v0.0.0-20200521075829-a4cb8d33dbbe`
+
## 0.3.5
> _18 May 2020_
diff --git a/Makefile b/Makefile
index 4297746..e5bd0bd 100644
--- a/Makefile
+++ b/Makefile
@@ -101,7 +101,7 @@ gen-binapi-docker: install-generator ## Generate binapi code (using Docker)
-v "$(shell pwd):/govpp" -w /govpp \
-u "$(shell id -u):$(shell id -g)" \
"${VPP_IMG}" \
- sh -xc "cd $(BINAPI_DIR) && $(cmds)"
+ sh -exc "cd $(BINAPI_DIR) && $(cmds)"
extras:
@make -C extras
diff --git a/adapter/socketclient/doc.go b/adapter/socketclient/doc.go
index 0f93c56..cbb00a2 100644
--- a/adapter/socketclient/doc.go
+++ b/adapter/socketclient/doc.go
@@ -21,13 +21,18 @@
//
// Requirements
//
-// The socketclient will connect to /run/vpp-api.sock by default. However this
-// is not enabled in VPP configuration by default.
+// The socketclient connects to unix domain socket defined in VPP configuration.
//
-// To enable the socket in VPP, add following section to VPP config.
+// It is enabled by default at /run/vpp/api.sock by the following config section:
//
// socksvr {
-// default
+// socket-name default
+// }
+//
+// If you want to use custom socket path:
+//
+// socksvr {
+// socket-name /run/vpp/api.sock
// }
//
package socketclient
diff --git a/adapter/socketclient/socketclient.go b/adapter/socketclient/socketclient.go
index 366163f..1ee067f 100644
--- a/adapter/socketclient/socketclient.go
+++ b/adapter/socketclient/socketclient.go
@@ -16,7 +16,8 @@ package socketclient
import (
"bufio"
- "bytes"
+ "encoding/binary"
+ "errors"
"fmt"
"io"
"net"
@@ -27,8 +28,7 @@ import (
"time"
"github.com/fsnotify/fsnotify"
- "github.com/lunixbochs/struc"
- logger "github.com/sirupsen/logrus"
+ "github.com/sirupsen/logrus"
"git.fd.io/govpp.git/adapter"
"git.fd.io/govpp.git/codec"
@@ -36,38 +36,34 @@ import (
const (
// DefaultSocketName is default VPP API socket file path.
- DefaultSocketName = adapter.DefaultBinapiSocket
- legacySocketName = "/run/vpp-api.sock"
+ DefaultSocketName = "/run/vpp/api.sock"
+ // DefaultClientName is used for identifying client in socket registration
+ DefaultClientName = "govppsock"
)
var (
+
// DefaultConnectTimeout is default timeout for connecting
DefaultConnectTimeout = time.Second * 3
// DefaultDisconnectTimeout is default timeout for discconnecting
DefaultDisconnectTimeout = time.Millisecond * 100
- // MaxWaitReady defines maximum duration before waiting for socket file
- // times out
+ // MaxWaitReady defines maximum duration of waiting for socket file
MaxWaitReady = time.Second * 10
- // ClientName is used for identifying client in socket registration
- ClientName = "govppsock"
)
var (
- // Debug is global variable that determines debug mode
- Debug = os.Getenv("DEBUG_GOVPP_SOCK") != ""
- // DebugMsgIds is global variable that determines debug mode for msg ids
- DebugMsgIds = os.Getenv("DEBUG_GOVPP_SOCKMSG") != ""
+ debug = strings.Contains(os.Getenv("DEBUG_GOVPP"), "socketclient")
+ debugMsgIds = strings.Contains(os.Getenv("DEBUG_GOVPP"), "msgtable")
- // Log is global logger
- Log = logger.New()
+ logger = logrus.New()
+ log = logger.WithField("logger", "govpp/socketclient")
)
-// init initializes global logger, which logs debug level messages to stdout.
+// init initializes global logger
func init() {
- Log.Out = os.Stdout
- if Debug {
- Log.Level = logger.DebugLevel
- Log.Debug("govpp/socketclient: enabled debug mode")
+ if debug {
+ logger.Level = logrus.DebugLevel
+ log.Debug("govpp: debug level enabled for socketclient")
}
}
@@ -88,12 +84,13 @@ const socketMissing = `
var warnOnce sync.Once
-func (c *vppClient) printMissingSocketMsg() {
+func (c *socketClient) printMissingSocketMsg() {
fmt.Fprintf(os.Stderr, socketMissing, c.sockAddr)
}
-type vppClient struct {
- sockAddr string
+type socketClient struct {
+ sockAddr string
+ clientName string
conn *net.UnixConn
reader *bufio.Reader
@@ -102,50 +99,63 @@ type vppClient struct {
connectTimeout time.Duration
disconnectTimeout time.Duration
- cb adapter.MsgCallback
+ msgCallback adapter.MsgCallback
clientIndex uint32
msgTable map[string]uint16
sockDelMsgId uint16
writeMu sync.Mutex
+ headerPool *sync.Pool
+
quit chan struct{}
wg sync.WaitGroup
}
-func NewVppClient(sockAddr string) *vppClient {
+func NewVppClient(sockAddr string) *socketClient {
if sockAddr == "" {
sockAddr = DefaultSocketName
}
- return &vppClient{
+ return &socketClient{
sockAddr: sockAddr,
+ clientName: DefaultClientName,
connectTimeout: DefaultConnectTimeout,
disconnectTimeout: DefaultDisconnectTimeout,
- cb: func(msgID uint16, data []byte) {
- Log.Warnf("no callback set, dropping message: ID=%v len=%d", msgID, len(data))
+ headerPool: &sync.Pool{New: func() interface{} {
+ return make([]byte, 16)
+ }},
+ msgCallback: func(msgID uint16, data []byte) {
+ log.Debugf("no callback set, dropping message: ID=%v len=%d", msgID, len(data))
},
}
}
+// SetClientName sets a client name used for identification.
+func (c *socketClient) SetClientName(name string) {
+ c.clientName = name
+}
+
// SetConnectTimeout sets timeout used during connecting.
-func (c *vppClient) SetConnectTimeout(t time.Duration) {
+func (c *socketClient) SetConnectTimeout(t time.Duration) {
c.connectTimeout = t
}
// SetDisconnectTimeout sets timeout used during disconnecting.
-func (c *vppClient) SetDisconnectTimeout(t time.Duration) {
+func (c *socketClient) SetDisconnectTimeout(t time.Duration) {
c.disconnectTimeout = t
}
-func (c *vppClient) SetMsgCallback(cb adapter.MsgCallback) {
- Log.Debug("SetMsgCallback")
- c.cb = cb
+func (c *socketClient) SetMsgCallback(cb adapter.MsgCallback) {
+ log.Debug("SetMsgCallback")
+ c.msgCallback = cb
}
-func (c *vppClient) checkLegacySocket() bool {
+const legacySocketName = "/run/vpp-api.sock"
+
+func (c *socketClient) checkLegacySocket() bool {
if c.sockAddr == legacySocketName {
return false
}
- Log.Debugf("checking legacy socket: %s", legacySocketName)
+ log.Debugf("checking legacy socket: %s", legacySocketName)
// check if socket exists
if _, err := os.Stat(c.sockAddr); err == nil {
return false // socket exists
@@ -163,7 +173,7 @@ func (c *vppClient) checkLegacySocket() bool {
}
// WaitReady checks socket file existence and waits for it if necessary
-func (c *vppClient) WaitReady() error {
+func (c *socketClient) WaitReady() error {
// check if socket already exists
if _, err := os.Stat(c.sockAddr); err == nil {
return nil // socket exists, we are ready
@@ -182,7 +192,7 @@ func (c *vppClient) WaitReady() error {
}
defer func() {
if err := watcher.Close(); err != nil {
- Log.Warnf("failed to close file watcher: %v", err)
+ log.Debugf("failed to close file watcher: %v", err)
}
}()
@@ -204,7 +214,7 @@ func (c *vppClient) WaitReady() error {
return e
case ev := <-watcher.Events:
- Log.Debugf("watcher event: %+v", ev)
+ log.Debugf("watcher event: %+v", ev)
if ev.Name == c.sockAddr && (ev.Op&fsnotify.Create) == fsnotify.Create {
// socket created, we are ready
return nil
@@ -213,7 +223,7 @@ func (c *vppClient) WaitReady() error {
}
}
-func (c *vppClient) Connect() error {
+func (c *socketClient) Connect() error {
c.checkLegacySocket()
// check if socket exists
@@ -229,7 +239,7 @@ func (c *vppClient) Connect() error {
}
if err := c.open(); err != nil {
- c.disconnect()
+ _ = c.disconnect()
return err
}
@@ -240,23 +250,23 @@ func (c *vppClient) Connect() error {
return nil
}
-func (c *vppClient) Disconnect() error {
+func (c *socketClient) Disconnect() error {
if c.conn == nil {
return nil
}
- Log.Debugf("Disconnecting..")
+ log.Debugf("Disconnecting..")
close(c.quit)
if err := c.conn.CloseRead(); err != nil {
- Log.Debugf("closing read failed: %v", err)
+ log.Debugf("closing readMsg failed: %v", err)
}
// wait for readerLoop to return
c.wg.Wait()
if err := c.close(); err != nil {
- Log.Debugf("closing failed: %v", err)
+ log.Debugf("closing failed: %v", err)
}
if err := c.disconnect(); err != nil {
@@ -266,38 +276,40 @@ func (c *vppClient) Disconnect() error {
return nil
}
-func (c *vppClient) connect(sockAddr string) error {
+const defaultBufferSize = 4096
+
+func (c *socketClient) connect(sockAddr string) error {
addr := &net.UnixAddr{Name: sockAddr, Net: "unix"}
- Log.Debugf("Connecting to: %v", c.sockAddr)
+ log.Debugf("Connecting to: %v", c.sockAddr)
conn, err := net.DialUnix("unix", nil, addr)
if err != nil {
// we try different type of socket for backwards compatbility with VPP<=19.04
if strings.Contains(err.Error(), "wrong type for socket") {
addr.Net = "unixpacket"
- Log.Debugf("%s, retrying connect with type unixpacket", err)
+ log.Debugf("%s, retrying connect with type unixpacket", err)
conn, err = net.DialUnix("unixpacket", nil, addr)
}
if err != nil {
- Log.Debugf("Connecting to socket %s failed: %s", addr, err)
+ log.Debugf("Connecting to socket %s failed: %s", addr, err)
return err
}
}
c.conn = conn
- Log.Debugf("Connected to socket (local addr: %v)", c.conn.LocalAddr().(*net.UnixAddr))
+ log.Debugf("Connected to socket (local addr: %v)", c.conn.LocalAddr().(*net.UnixAddr))
- c.reader = bufio.NewReader(c.conn)
- c.writer = bufio.NewWriter(c.conn)
+ c.reader = bufio.NewReaderSize(c.conn, defaultBufferSize)
+ c.writer = bufio.NewWriterSize(c.conn, defaultBufferSize)
return nil
}
-func (c *vppClient) disconnect() error {
- Log.Debugf("Closing socket")
+func (c *socketClient) disconnect() error {
+ log.Debugf("Closing socket")
if err := c.conn.Close(); err != nil {
- Log.Debugln("Closing socket failed:", err)
+ log.Debugln("Closing socket failed:", err)
return err
}
return nil
@@ -309,44 +321,40 @@ const (
deleteMsgContext = byte(124)
)
-func (c *vppClient) open() error {
- msgCodec := new(codec.MsgCodec)
+func (c *socketClient) open() error {
+ var msgCodec codec.MsgCodec
- req := &SockclntCreate{Name: ClientName}
+ // Request socket client create
+ req := &SockclntCreate{
+ Name: c.clientName,
+ }
msg, err := msgCodec.EncodeMsg(req, sockCreateMsgId)
if err != nil {
- Log.Debugln("Encode error:", err)
+ log.Debugln("Encode error:", err)
return err
}
// set non-0 context
msg[5] = createMsgContext
- if err := c.write(msg); err != nil {
- Log.Debugln("Write error: ", err)
- return err
- }
-
- readDeadline := time.Now().Add(c.connectTimeout)
- if err := c.conn.SetReadDeadline(readDeadline); err != nil {
+ if err := c.writeMsg(msg); err != nil {
+ log.Debugln("Write error: ", err)
return err
}
- msgReply, err := c.read()
+ msgReply, err := c.readMsgTimeout(nil, c.connectTimeout)
if err != nil {
- Log.Println("Read error:", err)
- return err
- }
- // reset read deadline
- if err := c.conn.SetReadDeadline(time.Time{}); err != nil {
+ log.Println("Read error:", err)
return err
}
reply := new(SockclntCreateReply)
if err := msgCodec.DecodeMsg(msgReply, reply); err != nil {
- Log.Println("Decode error:", err)
+ log.Println("Decoding sockclnt_create_reply failed:", err)
return err
+ } else if reply.Response != 0 {
+ return fmt.Errorf("sockclnt_create_reply: response error (%d)", reply.Response)
}
- Log.Debugf("SockclntCreateReply: Response=%v Index=%v Count=%v",
+ log.Debugf("SockclntCreateReply: Response=%v Index=%v Count=%v",
reply.Response, reply.Index, reply.Count)
c.clientIndex = reply.Index
@@ -358,15 +366,15 @@ func (c *vppClient) open() error {
if strings.HasPrefix(name, "sockclnt_delete_") {
c.sockDelMsgId = x.Index
}
- if DebugMsgIds {
- Log.Debugf(" - %4d: %q", x.Index, name)
+ if debugMsgIds {
+ log.Debugf(" - %4d: %q", x.Index, name)
}
}
return nil
}
-func (c *vppClient) close() error {
+func (c *socketClient) close() error {
msgCodec := new(codec.MsgCodec)
req := &SockclntDelete{
@@ -374,133 +382,148 @@ func (c *vppClient) close() error {
}
msg, err := msgCodec.EncodeMsg(req, c.sockDelMsgId)
if err != nil {
- Log.Debugln("Encode error:", err)
+ log.Debugln("Encode error:", err)
return err
}
// set non-0 context
msg[5] = deleteMsgContext
- Log.Debugf("sending socklntDel (%d byes): % 0X", len(msg), msg)
- if err := c.write(msg); err != nil {
- Log.Debugln("Write error: ", err)
- return err
- }
+ log.Debugf("sending socklntDel (%d bytes): % 0X", len(msg), msg)
- readDeadline := time.Now().Add(c.disconnectTimeout)
- if err := c.conn.SetReadDeadline(readDeadline); err != nil {
+ if err := c.writeMsg(msg); err != nil {
+ log.Debugln("Write error: ", err)
return err
}
- msgReply, err := c.read()
+
+ msgReply, err := c.readMsgTimeout(nil, c.disconnectTimeout)
if err != nil {
- Log.Debugln("Read error:", err)
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
// we accept timeout for reply
return nil
}
- return err
- }
- // reset read deadline
- if err := c.conn.SetReadDeadline(time.Time{}); err != nil {
+ log.Debugln("Read error:", err)
return err
}
reply := new(SockclntDeleteReply)
if err := msgCodec.DecodeMsg(msgReply, reply); err != nil {
- Log.Debugln("Decode error:", err)
+ log.Debugln("Decoding sockclnt_delete_reply failed:", err)
return err
+ } else if reply.Response != 0 {
+ return fmt.Errorf("sockclnt_delete_reply: response error (%d)", reply.Response)
}
- Log.Debugf("SockclntDeleteReply: Response=%v", reply.Response)
-
return nil
}
-func (c *vppClient) GetMsgID(msgName string, msgCrc string) (uint16, error) {
- msg := msgName + "_" + msgCrc
- msgID, ok := c.msgTable[msg]
- if !ok {
- return 0, &adapter.UnknownMsgError{msgName, msgCrc}
+func (c *socketClient) GetMsgID(msgName string, msgCrc string) (uint16, error) {
+ if msgID, ok := c.msgTable[msgName+"_"+msgCrc]; ok {
+ return msgID, nil
+ }
+ return 0, &adapter.UnknownMsgError{
+ MsgName: msgName,
+ MsgCrc: msgCrc,
}
- return msgID, nil
-}
-
-type reqHeader struct {
- // MsgID uint16
- ClientIndex uint32
- Context uint32
}
-func (c *vppClient) SendMsg(context uint32, data []byte) error {
- h := &reqHeader{
- ClientIndex: c.clientIndex,
- Context: context,
- }
- buf := new(bytes.Buffer)
- if err := struc.Pack(buf, h); err != nil {
- return err
+func (c *socketClient) SendMsg(context uint32, data []byte) error {
+ if len(data) < 10 {
+ return fmt.Errorf("invalid message data, length must be at least 10 bytes")
}
- copy(data[2:], buf.Bytes())
+ setMsgRequestHeader(data, c.clientIndex, context)
- Log.Debugf("sendMsg (%d) context=%v client=%d: data: % 02X", len(data), context, c.clientIndex, data)
+ if debug {
+ log.Debugf("sendMsg (%d) context=%v client=%d: % 02X", len(data), context, c.clientIndex, data)
+ }
- if err := c.write(data); err != nil {
- Log.Debugln("write error: ", err)
+ if err := c.writeMsg(data); err != nil {
+ log.Debugln("writeMsg error: ", err)
return err
}
return nil
}
-func (c *vppClient) write(msg []byte) error {
- h := &msgheader{
- DataLen: uint32(len(msg)),
+// setMsgRequestHeader sets client index and context in the message request header
+//
+// Message request has following structure:
+//
+// type msgRequestHeader struct {
+// MsgID uint16
+// ClientIndex uint32
+// Context uint32
+// }
+//
+func setMsgRequestHeader(data []byte, clientIndex, context uint32) {
+ // message ID is already set
+ binary.BigEndian.PutUint32(data[2:6], clientIndex)
+ binary.BigEndian.PutUint32(data[6:10], context)
+}
+
+func (c *socketClient) writeMsg(msg []byte) error {
+ // we lock to prevent mixing multiple message writes
+ c.writeMu.Lock()
+ defer c.writeMu.Unlock()
+
+ header := c.headerPool.Get().([]byte)
+ err := writeMsgHeader(c.writer, header, len(msg))
+ if err != nil {
+ return err
}
- buf := new(bytes.Buffer)
- if err := struc.Pack(buf, h); err != nil {
+ c.headerPool.Put(header)
+
+ if err := writeMsgData(c.writer, msg, c.writer.Size()); err != nil {
return err
}
- header := buf.Bytes()
- // we lock to prevent mixing multiple message sends
- c.writeMu.Lock()
- defer c.writeMu.Unlock()
+ if err := c.writer.Flush(); err != nil {
+ return err
+ }
+
+ log.Debugf(" -- writeMsg done")
+
+ return nil
+}
+
+func writeMsgHeader(w io.Writer, header []byte, dataLen int) error {
+ binary.BigEndian.PutUint32(header[8:12], uint32(dataLen))
- if n, err := c.writer.Write(header); err != nil {
+ n, err := w.Write(header)
+ if err != nil {
return err
- } else {
- Log.Debugf(" - header sent (%d/%d): % 0X", n, len(header), header)
+ }
+ if debug {
+ log.Debugf(" - header sent (%d/%d): % 0X", n, len(header), header)
}
- writerSize := c.writer.Size()
+ return nil
+}
+
+func writeMsgData(w io.Writer, msg []byte, writerSize int) error {
for i := 0; i <= len(msg)/writerSize; i++ {
x := i*writerSize + writerSize
if x > len(msg) {
x = len(msg)
}
- Log.Debugf(" - x=%v i=%v len=%v mod=%v", x, i, len(msg), len(msg)/writerSize)
- if n, err := c.writer.Write(msg[i*writerSize : x]); err != nil {
+ if debug {
+ log.Debugf(" - x=%v i=%v len=%v mod=%v", x, i, len(msg), len(msg)/writerSize)
+ }
+ n, err := w.Write(msg[i*writerSize : x])
+ if err != nil {
return err
- } else {
- Log.Debugf(" - msg sent x=%d (%d/%d): % 0X", x, n, len(msg), msg)
+ }
+ if debug {
+ log.Debugf(" - data sent x=%d (%d/%d): % 0X", x, n, len(msg), msg)
}
}
- if err := c.writer.Flush(); err != nil {
- return err
- }
-
- Log.Debugf(" -- write done")
-
return nil
}
-type msgHeader struct {
- MsgID uint16
- Context uint32
-}
-
-func (c *vppClient) readerLoop() {
+func (c *socketClient) readerLoop() {
defer c.wg.Done()
- defer Log.Debugf("reader quit")
+ defer log.Debugf("reader loop done")
+
+ var buf [8192]byte
for {
select {
@@ -509,72 +532,118 @@ func (c *vppClient) readerLoop() {
default:
}
- msg, err := c.read()
+ msg, err := c.readMsg(buf[:])
if err != nil {
if isClosedError(err) {
return
}
- Log.Debugf("read failed: %v", err)
+ log.Debugf("readMsg error: %v", err)
continue
}
- h := new(msgHeader)
- if err := struc.Unpack(bytes.NewReader(msg), h); err != nil {
- Log.Debugf("unpacking header failed: %v", err)
- continue
+ msgID, context := getMsgReplyHeader(msg)
+ if debug {
+ log.Debugf("recvMsg (%d) msgID=%d context=%v", len(msg), msgID, context)
}
- Log.Debugf("recvMsg (%d) msgID=%d context=%v", len(msg), h.MsgID, h.Context)
- c.cb(h.MsgID, msg)
+ c.msgCallback(msgID, msg)
}
}
-type msgheader struct {
- Q int `struc:"uint64"`
- DataLen uint32 `struc:"uint32"`
- GcMarkTimestamp uint32 `struc:"uint32"`
+// getMsgReplyHeader gets message ID and context from the message reply header
+//
+// Message reply has following structure:
+//
+// type msgReplyHeader struct {
+// MsgID uint16
+// Context uint32
+// }
+//
+func getMsgReplyHeader(msg []byte) (msgID uint16, context uint32) {
+ msgID = binary.BigEndian.Uint16(msg[0:2])
+ context = binary.BigEndian.Uint32(msg[2:6])
+ return
}
-func (c *vppClient) read() ([]byte, error) {
- Log.Debug(" reading next msg..")
+func (c *socketClient) readMsgTimeout(buf []byte, timeout time.Duration) ([]byte, error) {
+ // set read deadline
+ readDeadline := time.Now().Add(timeout)
+ if err := c.conn.SetReadDeadline(readDeadline); err != nil {
+ return nil, err
+ }
- header := make([]byte, 16)
+ // read message
+ msgReply, err := c.readMsg(buf)
+ if err != nil {
+ return nil, err
+ }
- n, err := io.ReadAtLeast(c.reader, header, 16)
+ // reset read deadline
+ if err := c.conn.SetReadDeadline(time.Time{}); err != nil {
+ return nil, err
+ }
+
+ return msgReply, nil
+}
+
+func (c *socketClient) readMsg(buf []byte) ([]byte, error) {
+ log.Debug("reading msg..")
+
+ header := c.headerPool.Get().([]byte)
+ msgLen, err := readMsgHeader(c.reader, header)
if err != nil {
return nil, err
}
+ c.headerPool.Put(header)
+
+ msg, err := readMsgData(c.reader, buf, msgLen)
+
+ log.Debugf(" -- readMsg done (buffered: %d)", c.reader.Buffered())
+
+ return msg, nil
+}
+
+func readMsgHeader(r io.Reader, header []byte) (int, error) {
+ n, err := io.ReadAtLeast(r, header, 16)
+ if err != nil {
+ return 0, err
+ }
if n == 0 {
- Log.Debugln("zero bytes header")
- return nil, nil
+ log.Debugln("zero bytes header")
+ return 0, nil
} else if n != 16 {
- Log.Debugf("invalid header data (%d): % 0X", n, header[:n])
- return nil, fmt.Errorf("invalid header (expected 16 bytes, got %d)", n)
+ log.Debugf("invalid header (%d bytes): % 0X", n, header[:n])
+ return 0, fmt.Errorf("invalid header (expected 16 bytes, got %d)", n)
}
- Log.Debugf(" read header %d bytes: % 0X", n, header)
- h := &msgheader{}
- if err := struc.Unpack(bytes.NewReader(header[:]), h); err != nil {
- return nil, err
- }
- Log.Debugf(" - decoded header: %+v", h)
+ dataLen := binary.BigEndian.Uint32(header[8:12])
- msgLen := int(h.DataLen)
- msg := make([]byte, msgLen)
+ return int(dataLen), nil
+}
+
+func readMsgData(r io.Reader, buf []byte, dataLen int) ([]byte, error) {
+ var msg []byte
+ if buf == nil || len(buf) < dataLen {
+ msg = make([]byte, dataLen)
+ } else {
+ msg = buf[0:dataLen]
+ }
- n, err = c.reader.Read(msg)
+ n, err := r.Read(msg)
if err != nil {
return nil, err
}
- Log.Debugf(" - read msg %d bytes (%d buffered) % 0X", n, c.reader.Buffered(), msg[:n])
+ if debug {
+ log.Debugf(" - read data (%d bytes): % 0X", n, msg[:n])
+ }
- if msgLen > n {
- remain := msgLen - n
- Log.Debugf("continue read for another %d bytes", remain)
+ if dataLen > n {
+ remain := dataLen - n
+ log.Debugf("continue reading remaining %d bytes", remain)
view := msg[n:]
for remain > 0 {
- nbytes, err := c.reader.Read(view)
+ nbytes, err := r.Read(view)
if err != nil {
return nil, err
} else if nbytes == 0 {
@@ -582,19 +651,17 @@ func (c *vppClient) read() ([]byte, error) {
}
remain -= nbytes
- Log.Debugf("another data received: %d bytes (remain: %d)", nbytes, remain)
+ log.Debugf("another data received: %d bytes (remain: %d)", nbytes, remain)
view = view[nbytes:]
}
}
- Log.Debugf(" -- read done (buffered: %d)", c.reader.Buffered())
-
return msg, nil
}
func isClosedError(err error) bool {
- if err == io.EOF {
+ if errors.Is(err, io.EOF) {
return true
}
return strings.HasSuffix(err.Error(), "use of closed network connection")
diff --git a/api/binapi.go b/api/binapi.go
index d933612..96eb3bf 100644
--- a/api/binapi.go
+++ b/api/binapi.go
@@ -91,42 +91,47 @@ type Channel interface {
// It will return an error if any of the given messages are not compatible.
CheckCompatiblity(msgs ...Message) error
- // Close closes the API channel and releases all API channel-related resources in the ChannelProvider.
+ // Close closes the API channel and releases all API channel-related resources
+ // in the ChannelProvider.
Close()
}
// RequestCtx is helper interface which allows to receive reply on request.
type RequestCtx interface {
- // ReceiveReply receives a reply from VPP (blocks until a reply is delivered from VPP, or until an error occurs).
- // The reply will be decoded into the msg argument. Error will be returned if the response cannot be received or decoded.
+ // ReceiveReply receives a reply from VPP (blocks until a reply is delivered
+ // from VPP, or until an error occurs). The reply will be decoded into the msg
+ // argument. Error will be returned if the response cannot be received or decoded.
ReceiveReply(msg Message) error
}
// MultiRequestCtx is helper interface which allows to receive reply on multi-request.
type MultiRequestCtx interface {
- // ReceiveReply receives a reply from VPP (blocks until a reply is delivered from VPP, or until an error occurs).
- // The reply will be decoded into the msg argument. If the last reply has been already consumed, lastReplyReceived is
- // set to true. Do not use the message itself if lastReplyReceived is true - it won't be filled with actual data.
- // Error will be returned if the response cannot be received or decoded.
+ // ReceiveReply receives a reply from VPP (blocks until a reply is delivered
+ // from VPP, or until an error occurs).The reply will be decoded into the msg
+ // argument. If the last reply has been already consumed, lastReplyReceived is
+ // set to true. Do not use the message itself if lastReplyReceived is
+ // true - it won't be filled with actual data.Error will be returned if the
+ // response cannot be received or decoded.
ReceiveReply(msg Message) (lastReplyReceived bool, err error)
}
-// SubscriptionCtx is helper interface which allows to control subscription for notification events.
+// SubscriptionCtx is helper interface which allows to control subscription for
+// notification events.
type SubscriptionCtx interface {
- // Unsubscribe unsubscribes from receiving the notifications tied to the subscription context.
+ // Unsubscribe unsubscribes from receiving the notifications tied to the
+ // subscription context.
Unsubscribe() error
}
// CompatibilityError is the error type usually returned by CheckCompatibility
-// method of Channel. It describes all of the incompatible messages.
+// method of Channel. It contains list of all the compatible/incompatible messages.
type CompatibilityError struct {
- // IncompatibleMessages is the list of all messages
- // that failed compatibility check.
+ CompatibleMessages []string
IncompatibleMessages []string
}
func (c *CompatibilityError) Error() string {
- return fmt.Sprintf("%d incompatible messages: %v", len(c.IncompatibleMessages), c.IncompatibleMessages)
+ return fmt.Sprintf("%d/%d messages incompatible", len(c.IncompatibleMessages), len(c.CompatibleMessages)+len(c.IncompatibleMessages))
}
var (
@@ -155,6 +160,6 @@ func GetRegisteredMessageTypes() map[reflect.Type]string {
return registeredMessageTypes
}
-// GoVppAPIPackageIsVersion1 is referenced from generated binapi files
+// GoVppAPIPackageIsVersionX is referenced from generated binapi files
// to assert that that code is compatible with this version of the GoVPP api package.
const GoVppAPIPackageIsVersion1 = true
diff --git a/codec/msg_codec.go b/codec/msg_codec.go
index 67628a4..3f60cae 100644
--- a/codec/msg_codec.go
+++ b/codec/msg_codec.go
@@ -20,8 +20,9 @@ import (
"fmt"
"reflect"
- "git.fd.io/govpp.git/api"
"github.com/lunixbochs/struc"
+
+ "git.fd.io/govpp.git/api"
)
// MsgCodec provides encoding and decoding functionality of `api.Message` structs into/from
@@ -65,7 +66,7 @@ func (*MsgCodec) EncodeMsg(msg api.Message, msgID uint16) (data []byte, err erro
if err, ok = r.(error); !ok {
err = fmt.Errorf("%v", r)
}
- err = fmt.Errorf("panic occurred: %v", err)
+ err = fmt.Errorf("panic occurred during encoding message %s: %v", msg.GetMessageName(), err)
}
}()
@@ -101,11 +102,22 @@ func (*MsgCodec) EncodeMsg(msg api.Message, msgID uint16) (data []byte, err erro
}
// DecodeMsg decodes binary-encoded data of a message into provided `Message` structure.
-func (*MsgCodec) DecodeMsg(data []byte, msg api.Message) error {
+func (*MsgCodec) DecodeMsg(data []byte, msg api.Message) (err error) {
if msg == nil {
return errors.New("nil message passed in")
}
+ // try to recover panic which might possibly occur
+ defer func() {
+ if r := recover(); r != nil {
+ var ok bool
+ if err, ok = r.(error); !ok {
+ err = fmt.Errorf("%v", r)
+ }
+ err = fmt.Errorf("panic occurred during decoding message %s: %v", msg.GetMessageName(), err)
+ }
+ }()
+
var header interface{}
// check which header is expected
@@ -123,7 +135,7 @@ func (*MsgCodec) DecodeMsg(data []byte, msg api.Message) error {
buf := bytes.NewReader(data)
// decode message header
- if err := struc.Unpack(buf, header); err != nil {
+ if err = struc.Unpack(buf, header); err != nil {
return fmt.Errorf("failed to decode message header: %+v, error: %v", header, err)
}
diff --git a/core/channel.go b/core/channel.go
index 363a267..8479d6a 100644
--- a/core/channel.go
+++ b/core/channel.go
@@ -37,6 +37,8 @@ type MessageCodec interface {
EncodeMsg(msg api.Message, msgID uint16) ([]byte, error)
// DecodeMsg decodes binary-encoded data of a message into provided Message structure.
DecodeMsg(data []byte, msg api.Message) error
+ // DecodeMsgContext decodes context from message data.
+ DecodeMsgContext(data []byte, msg api.Message) (context uint32, err error)
}
// MessageIdentifier provides identification of generated API messages.
@@ -84,7 +86,7 @@ type subscriptionCtx struct {
msgFactory func() api.Message // function that returns a new instance of the specific message that is expected as a notification
}
-// channel is the main communication interface with govpp core. It contains four Go channels, one for sending the requests
+// Channel is the main communication interface with govpp core. It contains four Go channels, one for sending the requests
// to VPP, one for receiving the replies from it and the same set for notifications. The user can access the Go channels
// via methods provided by Channel interface in this package. Do not use the same channel from multiple goroutines
// concurrently, otherwise the responses could mix! Use multiple channels instead.
@@ -150,13 +152,13 @@ func (ch *Channel) CheckCompatiblity(msgs ...api.Message) error {
_, err := ch.msgIdentifier.GetMessageID(msg)
if err != nil {
if uerr, ok := err.(*adapter.UnknownMsgError); ok {
- m := fmt.Sprintf("%s_%s", uerr.MsgName, uerr.MsgCrc)
- comperr.IncompatibleMessages = append(comperr.IncompatibleMessages, m)
+ comperr.IncompatibleMessages = append(comperr.IncompatibleMessages, getMsgID(uerr.MsgName, uerr.MsgCrc))
continue
}
// other errors return immediatelly
return err
}
+ comperr.CompatibleMessages = append(comperr.CompatibleMessages, getMsgNameWithCrc(msg))
}
if len(comperr.IncompatibleMessages) == 0 {
return nil
@@ -234,6 +236,8 @@ func (sub *subscriptionCtx) Unsubscribe() error {
for i, item := range sub.ch.conn.subscriptions[sub.msgID] {
if item == sub {
+ // close notification channel
+ close(sub.ch.conn.subscriptions[sub.msgID][i].notifChan)
// remove i-th item in the slice
sub.ch.conn.subscriptions[sub.msgID] = append(sub.ch.conn.subscriptions[sub.msgID][:i], sub.ch.conn.subscriptions[sub.msgID][i+1:]...)
return nil
@@ -328,9 +332,9 @@ func (ch *Channel) processReply(reply *vppReply, expSeqNum uint16, msg api.Messa
msgNameCrc = getMsgNameWithCrc(replyMsg)
}
- err = fmt.Errorf("received invalid message ID (seqNum=%d), expected %d (%s), but got %d (%s) "+
+ err = fmt.Errorf("received unexpected message (seqNum=%d), expected %s (ID %d), but got %s (ID %d) "+
"(check if multiple goroutines are not sharing single GoVPP channel)",
- reply.seqNum, expMsgID, msg.GetMessageName(), reply.msgID, msgNameCrc)
+ reply.seqNum, msg.GetMessageName(), expMsgID, msgNameCrc, reply.msgID)
return
}
diff --git a/core/channel_test.go b/core/channel_test.go
index b8d07b5..6775519 100644
--- a/core/channel_test.go
+++ b/core/channel_test.go
@@ -466,5 +466,5 @@ func TestInvalidMessageID(t *testing.T) {
// second should fail with error invalid message ID
err = ctx.ch.SendRequest(&ControlPing{}).ReceiveReply(&ControlPingReply{})
Expect(err).Should(HaveOccurred())
- Expect(err.Error()).To(ContainSubstring("invalid message ID"))
+ Expect(err.Error()).To(ContainSubstring("unexpected message"))
}
diff --git a/core/connection.go b/core/connection.go
index 264ec43..917f1cb 100644
--- a/core/connection.go
+++ b/core/connection.go
@@ -95,7 +95,7 @@ type Connection struct {
vppConnected uint32 // non-zero if the adapter is connected to VPP
- codec *codec.MsgCodec // message codec
+ codec MessageCodec // message codec
msgIDs map[string]uint16 // map of message IDs indexed by message name + CRC
msgMap map[uint16]api.Message // map of messages indexed by message ID
@@ -374,7 +374,11 @@ func (c *Connection) healthCheckLoop(connChan chan ConnectionEvent) {
}
func getMsgNameWithCrc(x api.Message) string {
- return x.GetMessageName() + "_" + x.GetCrcString()
+ return getMsgID(x.GetMessageName(), x.GetCrcString())
+}
+
+func getMsgID(name, crc string) string {
+ return name + "_" + crc
}
func getMsgFactory(msg api.Message) func() api.Message {
@@ -425,9 +429,14 @@ func (c *Connection) retrieveMessageIDs() (err error) {
var n int
for name, msg := range msgs {
+ typ := reflect.TypeOf(msg).Elem()
+ path := fmt.Sprintf("%s.%s", typ.PkgPath(), typ.Name())
+
msgID, err := c.GetMessageID(msg)
if err != nil {
- log.Debugf("retrieving msgID for %s failed: %v", name, err)
+ if debugMsgIDs {
+ log.Debugf("retrieving message ID for %s failed: %v", path, err)
+ }
continue
}
n++
@@ -444,7 +453,8 @@ func (c *Connection) retrieveMessageIDs() (err error) {
log.Debugf("message %q (%s) has ID: %d", name, getMsgNameWithCrc(msg), msgID)
}
}
- log.Debugf("retrieved %d/%d msgIDs (took %s)", n, len(msgs), time.Since(t))
+ log.WithField("took", time.Since(t)).
+ Debugf("retrieved IDs for %d messages (registered %d)", n, len(msgs))
return nil
}
diff --git a/core/log.go b/core/log.go
index 5960d6b..dea6cbb 100644
--- a/core/log.go
+++ b/core/log.go
@@ -2,32 +2,35 @@ package core
import (
"os"
+ "strings"
- logger "github.com/sirupsen/logrus"
+ "github.com/sirupsen/logrus"
)
var (
debug = os.Getenv("DEBUG_GOVPP") != ""
- debugMsgIDs = os.Getenv("DEBUG_GOVPP_MSGIDS") != ""
+ debugMsgIDs = strings.Contains(os.Getenv("DEBUG_GOVPP"), "msgid")
- log = logger.New() // global logger
+ log = logrus.New()
)
-// init initializes global logger, which logs debug level messages to stdout.
+// init initializes global logger
func init() {
- log.Out = os.Stdout
+ log.Formatter = &logrus.TextFormatter{
+ EnvironmentOverrideColors: true,
+ }
if debug {
- log.Level = logger.DebugLevel
- log.Debugf("govpp/core: debug mode enabled")
+ log.Level = logrus.DebugLevel
+ log.Debugf("govpp: debug level enabled")
}
}
// SetLogger sets global logger to l.
-func SetLogger(l *logger.Logger) {
+func SetLogger(l *logrus.Logger) {
log = l
}
// SetLogLevel sets global logger level to lvl.
-func SetLogLevel(lvl logger.Level) {
+func SetLogLevel(lvl logrus.Level) {
log.Level = lvl
}
diff --git a/core/request_handler.go b/core/request_handler.go
index ddd5307..e272c6f 100644
--- a/core/request_handler.go
+++ b/core/request_handler.go
@@ -17,10 +17,13 @@ package core
import (
"errors"
"fmt"
+ "reflect"
"sync/atomic"
"time"
logger "github.com/sirupsen/logrus"
+
+ "git.fd.io/govpp.git/api"
)
var ReplyChannelTimeout = time.Millisecond * 100
@@ -93,7 +96,7 @@ func (c *Connection) processRequest(ch *Channel, req *vppRequest) error {
"msg_size": len(data),
"seq_num": req.seqNum,
"msg_crc": req.msg.GetCrcString(),
- }).Debugf("==> govpp send: %s: %+v", req.msg.GetMessageName(), req.msg)
+ }).Debugf("--> govpp SEND: %s %+v", req.msg.GetMessageName(), req.msg)
}
// send the request to VPP
@@ -118,7 +121,7 @@ func (c *Connection) processRequest(ch *Channel, req *vppRequest) error {
"msg_id": c.pingReqID,
"msg_size": len(pingData),
"seq_num": req.seqNum,
- }).Debug("--> sending control ping")
+ }).Debug(" -> sending control ping")
if err := c.vppClient.SendMsg(context, pingData); err != nil {
log.WithFields(logger.Fields{
@@ -156,7 +159,16 @@ func (c *Connection) msgCallback(msgID uint16, data []byte) {
}
chanID, isMulti, seqNum := unpackRequestContext(context)
+
if log.Level == logger.DebugLevel { // for performance reasons - logrus does some processing even if debugs are disabled
+ msg = reflect.New(reflect.TypeOf(msg).Elem()).Interface().(api.Message)
+
+ // decode the message
+ if err = c.codec.DecodeMsg(data, msg); err != nil {
+ err = fmt.Errorf("decoding message failed: %w", err)
+ return
+ }
+
log.WithFields(logger.Fields{
"context": context,
"msg_id": msgID,
@@ -165,7 +177,7 @@ func (c *Connection) msgCallback(msgID uint16, data []byte) {
"is_multi": isMulti,
"seq_num": seqNum,
"msg_crc": msg.GetCrcString(),
- }).Debugf("<== govpp recv: %s", msg.GetMessageName())
+ }).Debugf("<-- govpp RECEIVE: %s %+v", msg.GetMessageName(), msg)
}
if context == 0 || c.isNotificationMessage(msgID) {
@@ -210,7 +222,7 @@ func (c *Connection) msgCallback(msgID uint16, data []byte) {
func sendReply(ch *Channel, reply *vppReply) {
select {
case ch.replyChan <- reply:
- // reply sent successfully
+ // reply sent successfully
case <-time.After(ReplyChannelTimeout):
// receiver still not ready
log.WithFields(logger.Fields{
diff --git a/examples/perf-bench/perf-bench.go b/examples/perf-bench/perf-bench.go
index f48c154..81d183c 100644
--- a/examples/perf-bench/perf-bench.go
+++ b/examples/perf-bench/perf-bench.go
@@ -20,6 +20,7 @@ import (
"flag"
"fmt"
"log"
+ "os"
"time"
"github.com/pkg/profile"
@@ -39,14 +40,14 @@ const (
func main() {
// parse optional flags
- var sync, prof bool
+ var sync bool
var cnt int
- var sock string
+ var sock, prof string
flag.BoolVar(&sync, "sync", false, "run synchronous perf test")
- flag.StringVar(&sock, "socket", socketclient.DefaultSocketName, "Path to VPP API socket")
- flag.String("socket", statsclient.DefaultSocketName, "Path to VPP stats socket")
+ flag.StringVar(&sock, "api-socket", socketclient.DefaultSocketName, "Path to VPP API socket")
+ flag.String("stats-socket", statsclient.DefaultSocketName, "Path to VPP stats socket")
flag.IntVar(&cnt, "count", 0, "count of requests to be sent to VPP")
- flag.BoolVar(&prof, "prof", false, "generate profile data")
+ flag.StringVar(&prof, "prof", "", "enable profiling mode [mem, cpu]")
flag.Parse()
if cnt == 0 {
@@ -58,8 +59,16 @@ func main() {
}
}
- if prof {
- defer profile.Start().Stop()
+ switch prof {
+ case "mem":
+ defer profile.Start(profile.MemProfile, profile.MemProfileRate(1)).Stop()
+ case "cpu":
+ defer profile.Start(profile.CPUProfile).Stop()
+ case "":
+ default:
+ fmt.Printf("invalid profiling mode: %q\n", prof)
+ flag.Usage()
+ os.Exit(1)
}
a := socketclient.NewVppClient(sock)
diff --git a/examples/simple-client/simple_client.go b/examples/simple-client/simple_client.go
index 6d96ca8..fe7c109 100644
--- a/examples/simple-client/simple_client.go
+++ b/examples/simple-client/simple_client.go
@@ -65,20 +65,22 @@ func main() {
}
defer ch.Close()
+ if err := ch.CheckCompatiblity(vpe.AllMessages()...); err != nil {
+ log.Fatal(err)
+ }
+
vppVersion(ch)
if err := ch.CheckCompatiblity(interfaces.AllMessages()...); err != nil {
log.Fatal(err)
}
- createLoopback(ch)
- createLoopback(ch)
+ idx := createLoopback(ch)
interfaceDump(ch)
- addIPAddress(ch)
- ipAddressDump(ch)
-
- interfaceNotifications(ch)
+ addIPAddress(ch, idx)
+ ipAddressDump(ch, idx)
+ interfaceNotifications(ch, idx)
if len(Errors) > 0 {
fmt.Printf("finished with %d errors\n", len(Errors))
@@ -109,11 +111,12 @@ func vppVersion(ch api.Channel) {
fmt.Printf("reply: %+v\n", reply)
fmt.Printf("VPP version: %q\n", cleanString(reply.Version))
- fmt.Println("ok")
+ fmt.Println("OK")
+ fmt.Println()
}
// createLoopback sends request to create loopback interface.
-func createLoopback(ch api.Channel) {
+func createLoopback(ch api.Channel) interfaces.InterfaceIndex {
fmt.Println("Creating loopback interface")
req := &interfaces.CreateLoopback{}
@@ -121,48 +124,54 @@ func createLoopback(ch api.Channel) {
if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
logError(err, "creating loopback interface")
- return
+ return 0
}
fmt.Printf("reply: %+v\n", reply)
- fmt.Printf("loopback interface index: %v\n", reply.SwIfIndex)
+ fmt.Printf("interface index: %v\n", reply.SwIfIndex)
fmt.Println("OK")
+ fmt.Println()
+
+ return reply.SwIfIndex
}
// interfaceDump shows an example of multipart request (multiple replies are expected).
func interfaceDump(ch api.Channel) {
fmt.Println("Dumping interfaces")
+ n := 0
reqCtx := ch.SendMultiRequest(&interfaces.SwInterfaceDump{})
for {
msg := &interfaces.SwInterfaceDetails{}
stop, err := reqCtx.ReceiveReply(msg)
+ if stop {
+ break
+ }
if err != nil {
logError(err, "dumping interfaces")
return
}
- if stop {
- break
- }
- fmt.Printf(" - interface: %+v\n", msg)
+ n++
+ fmt.Printf(" - interface #%d: %+v\n", n, msg)
}
fmt.Println("OK")
+ fmt.Println()
}
// addIPAddress sends request to add IP address to interface.
-func addIPAddress(ch api.Channel) {
- fmt.Println("Adding IP address to interface")
+func addIPAddress(ch api.Channel, index interfaces.InterfaceIndex) {
+ fmt.Printf("Adding IP address to interface to interface index %d\n", index)
req := &interfaces.SwInterfaceAddDelAddress{
- SwIfIndex: 1,
+ SwIfIndex: index,
IsAdd: true,
Prefix: ip_types.AddressWithPrefix{
Address: interfaces.Address{
Af: ip_types.ADDRESS_IP4,
- Un: ip_types.AddressUnionIP4(interfaces.IP4Address{10, 10, 0, 1}),
+ Un: ip_types.AddressUnionIP4(interfaces.IP4Address{10, 10, 0, uint8(index)}),
},
- Len: 24,
+ Len: 32,
},
}
reply := &interfaces.SwInterfaceAddDelAddressReply{}
@@ -174,13 +183,14 @@ func addIPAddress(ch api.Channel) {
fmt.Printf("reply: %+v\n", reply)
fmt.Println("OK")
+ fmt.Println()
}
-func ipAddressDump(ch api.Channel) {
- fmt.Println("Dumping IP addresses")
+func ipAddressDump(ch api.Channel, index interfaces.InterfaceIndex) {
+ fmt.Printf("Dumping IP addresses for interface index %d\n", index)
req := &ip.IPAddressDump{
- SwIfIndex: 1,
+ SwIfIndex: index,
}
reqCtx := ch.SendMultiRequest(req)
@@ -198,13 +208,14 @@ func ipAddressDump(ch api.Channel) {
}
fmt.Println("OK")
+ fmt.Println()
}
// interfaceNotifications shows the usage of notification API. Note that for notifications,
// you are supposed to create your own Go channel with your preferred buffer size. If the channel's
// buffer is full, the notifications will not be delivered into it.
-func interfaceNotifications(ch api.Channel) {
- fmt.Println("Subscribing to notificaiton events")
+func interfaceNotifications(ch api.Channel, index interfaces.InterfaceIndex) {
+ fmt.Printf("Subscribing to notificaiton events for interface index %d\n", index)
notifChan := make(chan api.Message, 100)
@@ -225,27 +236,31 @@ func interfaceNotifications(ch api.Channel) {
return
}
+ // receive notifications
+ go func() {
+ for notif := range notifChan {
+ fmt.Printf("incoming event: %+v\n", notif.(*interfaces.SwInterfaceEvent))
+ }
+ }()
+
// generate some events in VPP
err = ch.SendRequest(&interfaces.SwInterfaceSetFlags{
- SwIfIndex: 1,
+ SwIfIndex: index,
+ Flags: interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
}).ReceiveReply(&interfaces.SwInterfaceSetFlagsReply{})
if err != nil {
logError(err, "setting interface flags")
return
}
err = ch.SendRequest(&interfaces.SwInterfaceSetFlags{
- SwIfIndex: 1,
- Flags: interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
+ SwIfIndex: index,
+ Flags: 0,
}).ReceiveReply(&interfaces.SwInterfaceSetFlagsReply{})
if err != nil {
logError(err, "setting interface flags")
return
}
- // receive one notification
- notif := (<-notifChan).(*interfaces.SwInterfaceEvent)
- fmt.Printf("incoming event: %+v\n", notif)
-
// disable interface events in VPP
err = ch.SendRequest(&interfaces.WantInterfaceEvents{
PID: uint32(os.Getpid()),
@@ -263,6 +278,7 @@ func interfaceNotifications(ch api.Channel) {
return
}
+ fmt.Println("OK")
fmt.Println()
}
diff --git a/examples/union-example/union_example.go b/examples/union-example/union_example.go
index 92c3ec2..9993ee1 100644
--- a/examples/union-example/union_example.go
+++ b/examples/union-example/union_example.go
@@ -16,73 +16,82 @@
package main
import (
- "bytes"
"fmt"
"log"
"net"
+ "reflect"
+ "git.fd.io/govpp.git/codec"
"git.fd.io/govpp.git/examples/binapi/ip"
"git.fd.io/govpp.git/examples/binapi/ip_types"
-
- "github.com/lunixbochs/struc"
)
+func init() {
+ log.SetFlags(0)
+}
+
func main() {
+ constructExample()
+
encodingExample()
- usageExample()
+
+ // convert IP from string form into Address type containing union
+ convertIP("10.10.1.1")
+ convertIP("ff80::1")
}
-func encodingExample() {
- // create union with IPv4 address
- var unionIP4 ip.AddressUnion
- unionIP4.SetIP4(ip.IP4Address{192, 168, 1, 10})
-
- // use it in the Address type
- addr := &ip.Address{
- Af: ip_types.ADDRESS_IP4,
- Un: ip_types.AddressUnionIP4(ip.IP4Address{192, 168, 1, 10}),
- }
- log.Printf("encoding union IPv4: %v", addr.Un.GetIP4())
+func constructExample() {
+ var union ip_types.AddressUnion
- // encode the address with union
- data := encode(addr)
- // decode the address with union
- addr2 := decode(data)
+ // create AddressUnion with AdressUnionXXX constructors
+ union = ip_types.AddressUnionIP4(ip.IP4Address{192, 168, 1, 10})
+ union = ip_types.AddressUnionIP6(ip.IP6Address{0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02})
- log.Printf("decoded union IPv4: %v", addr2.Un.GetIP4())
+ // set AddressUnion with SetXXX methods
+ union.SetIP4(ip.IP4Address{192, 168, 1, 10})
+ union.SetIP6(ip.IP6Address{0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02})
}
-func encode(addr *ip.Address) []byte {
- log.Printf("encoding address: %#v", addr)
- buf := new(bytes.Buffer)
- if err := struc.Pack(buf, addr); err != nil {
- panic(err)
+func encodingExample() {
+ var c codec.MsgCodec
+
+ // encode this message
+ var msg = ip.IPPuntRedirect{
+ Punt: ip.PuntRedirect{
+ Nh: ip_types.Address{
+ Af: ip_types.ADDRESS_IP4,
+ Un: ip_types.AddressUnionIP4(ip.IP4Address{192, 168, 1, 10}),
+ },
+ },
+ IsAdd: true,
}
- return buf.Bytes()
-}
+ log.Printf("encoding message: %+v", msg)
-func decode(data []byte) *ip.Address {
- addr := new(ip.Address)
- buf := bytes.NewReader(data)
- if err := struc.Unpack(buf, addr); err != nil {
- panic(err)
+ b, err := c.EncodeMsg(&msg, 1)
+ if err != nil {
+ log.Fatal(err)
}
- log.Printf("decoded address: %#v", addr)
- return addr
-}
-func usageExample() {
- var convAddr = func(ip string) {
- addr, err := ipToAddress(ip)
- if err != nil {
- log.Printf("converting ip %q failed: %v", ip, err)
- }
- fmt.Printf("% 0X\n", addr)
+ // decode into this message
+ var msg2 ip.IPPuntRedirect
+ if err := c.DecodeMsg(b, &msg2); err != nil {
+ log.Fatal(err)
}
+ log.Printf("decoded message: %+v", msg2)
- convAddr("10.10.10.10")
- convAddr("::1")
- convAddr("")
+ // compare the messages
+ if !reflect.DeepEqual(msg, msg2) {
+ log.Fatal("messages are not equal")
+ }
+}
+
+func convertIP(ip string) {
+ addr, err := ipToAddress(ip)
+ if err != nil {
+ log.Printf("error converting IP: %v", err)
+ return
+ }
+ fmt.Printf("converted IP %q to: %+v\n", ip, addr)
}
func ipToAddress(ipstr string) (addr ip.Address, err error) {
@@ -98,7 +107,7 @@ func ipToAddress(ipstr string) (addr ip.Address, err error) {
} else {
addr.Af = ip_types.ADDRESS_IP4
var ip4addr ip.IP4Address
- copy(ip4addr[:], ip4)
+ copy(ip4addr[:], ip4.To4())
addr.Un.SetIP4(ip4addr)
}
return
diff --git a/go.mod b/go.mod
index cff5c18..09512f5 100644
--- a/go.mod
+++ b/go.mod
@@ -9,13 +9,13 @@ require (
github.com/golang/protobuf v1.3.2 // indirect
github.com/hpcloud/tail v1.0.0 // indirect
github.com/kr/pretty v0.1.0 // indirect
- github.com/lunixbochs/struc v0.0.0-20190916212049-a5c72983bc42
+ github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe
github.com/onsi/ginkgo v1.8.0 // indirect
github.com/onsi/gomega v1.1.0
github.com/pkg/profile v1.2.1
- github.com/sirupsen/logrus v1.0.0
+ github.com/sirupsen/logrus v1.6.0
github.com/stretchr/testify v1.3.0 // indirect
- golang.org/x/sys v0.0.0-20170427041856-9ccfe848b9db // indirect
+ golang.org/x/sys v0.0.0-20200610111108-226ff32320da // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/fsnotify.v1 v1.4.7 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
diff --git a/go.sum b/go.sum
index 78f5650..188240b 100644
--- a/go.sum
+++ b/go.sum
@@ -2,6 +2,8 @@ github.com/bennyscetbun/jsongo v1.1.0 h1:ZDSks3aLP13jhY139lWaUqZaU8G0tELMohzumut
github.com/bennyscetbun/jsongo v1.1.0/go.mod h1:suxbVmjBV8+A2BBAM5EYVh6Uj8j3rqJhzWf3hv7Ff8U=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v0.0.0-20170329110642-4da3e2cfbabc h1:fqUzyjP8DApxXq0dOZJE/NvqQkyjxiTy9ARNyRwBPEw=
github.com/fsnotify/fsnotify v0.0.0-20170329110642-4da3e2cfbabc/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff h1:zk1wwii7uXmI0znwU+lqg+wFL9G5+vm5I+9rv2let60=
@@ -10,13 +12,15 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/lunixbochs/struc v0.0.0-20190916212049-a5c72983bc42 h1:PzBD7QuxXSgSu61TKXxRwVGzWO5d9QZ0HxFFpndZMCg=
-github.com/lunixbochs/struc v0.0.0-20190916212049-a5c72983bc42/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
+github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe h1:ewr1srjRCmcQogPQ/NCx6XCk6LGVmsVCc9Y3vvPZj+Y=
+github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.1.0 h1:e3YP4dN/HYPpGh29X1ZkcxcEICsOls9huyVCRBaxjq8=
@@ -25,13 +29,16 @@ github.com/pkg/profile v1.2.1 h1:F++O52m40owAmADcojzM+9gyjmMOY/T4oYJkgFDH8RE=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/sirupsen/logrus v1.0.0 h1:XM8X4m/9ACaclZMs946FQNEZBZafvToJLTR4007drwo=
-github.com/sirupsen/logrus v1.0.0/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
+github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-golang.org/x/sys v0.0.0-20170427041856-9ccfe848b9db h1:znurcNjtwV7XblDOBERYCP1TUjpwbp8bi3Szx8gbNBE=
-golang.org/x/sys v0.0.0-20170427041856-9ccfe848b9db/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200610111108-226ff32320da h1:bGb80FudwxpeucJUjPYJXuJ8Hk91vNtfvrymzwiei38=
+golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
diff --git a/version/version.go b/version/version.go
index eb8063a..8bde72c 100644
--- a/version/version.go
+++ b/version/version.go
@@ -21,14 +21,17 @@ import (
"time"
)
+// Following variables should normally be updated via `-ldflags "-X ..."`.
+// However, the version string is hard-coded to ensure it is always included
+// even with bare go build/install.
var (
- name = "govpp"
- version = "v0.3.5"
- commitHash = "unknown"
- buildBranch = "HEAD"
- buildStamp = ""
- buildUser = ""
- buildHost = ""
+ name = "govpp"
+ version = "v0.4.0-dev"
+ commit = "unknown"
+ branch = "HEAD"
+ buildStamp = ""
+ buildUser = ""
+ buildHost = ""
buildDate time.Time
)
@@ -41,6 +44,10 @@ func init() {
buildDate = time.Unix(buildstampInt64, 0)
}
+func Version() string {
+ return version
+}
+
func Info() string {
return fmt.Sprintf(`%s %s`, name, version)
}
@@ -54,7 +61,7 @@ func Verbose() string {
Build date: %s
Go runtime: %s (%s/%s)`,
name,
- version, buildBranch, commitHash,
+ version, branch, commit,
buildUser, buildHost, buildDate.Format(time.UnixDate),
runtime.Version(), runtime.GOOS, runtime.GOARCH,
)