//go:build vpp // +build vpp package tap import ( "context" "path" "errors" "fmt" "log" "os" "sync" "git.fd.io/govpp.git" "git.fd.io/govpp.git/adapter/socketclient" "git.fd.io/govpp.git/binapi/ethernet_types" interfaces "git.fd.io/govpp.git/binapi/interface" "git.fd.io/govpp.git/binapi/interface_types" "git.fd.io/govpp.git/binapi/l2" "git.fd.io/govpp.git/binapi/memif" "git.fd.io/govpp.git/extras/libmemif" "golang.org/x/sys/unix" "github.com/KusakabeSi/EtherGuard-VPN/mtypes" logger "github.com/sirupsen/logrus" ) const ( ENV_VPP_MEMIF_SOCKET_DIR = "VPP_MEMIF_SOCKET_DIR" ENV_VPP_SOCKET_PATH = "VPP_API_SOCKET_PATH" VPP_SUPPORT = "VPP support enabled" ) var ( //read from env vppMemifSocketDir = "/var/run/eggo-vpp" vppApiSocketPath = socketclient.DefaultSocketName // Path to VPP binary API socket file, default is /run/vpp/api. //internal NumQueues = uint8(1) onConnectWg sync.WaitGroup tunErrorChannel chan error ) type VppTap struct { name string mtu int ifuid uint32 memifSockPath string SwIfIndex interface_types.InterfaceIndex secret string memif *libmemif.Memif RxQueues int RxintCh <-chan uint8 RxintChNext chan uint8 RxintErrCh <-chan error TxQueues int TxCount uint logger *logger.Logger errors chan error // async error handling events chan Event } // New creates and returns a new TUN interface for the application. func CreateVppTAP(iconfig mtypes.InterfaceConf, NodeID mtypes.Vertex, loglevel string) (tapdev Device, err error) { // Setup TUN Config if len(iconfig.Name) >= unix.IFNAMSIZ { return nil, fmt.Errorf("interface name too long: %w", unix.ENAMETOOLONG) } // Set logger log := logger.New() log.Out = os.Stdout log.Level = func() logger.Level { switch loglevel { case "verbose", "debug": return logger.DebugLevel case "error": return logger.ErrorLevel case "silent": return logger.PanicLevel } return logger.ErrorLevel }() libmemif.SetLogger(log) if os.Getenv(ENV_VPP_MEMIF_SOCKET_DIR) != "" { vppMemifSocketDir = os.Getenv(ENV_VPP_MEMIF_SOCKET_DIR) } if os.Getenv(ENV_VPP_SOCKET_PATH) != "" { vppApiSocketPath = os.Getenv(ENV_VPP_SOCKET_PATH) } if err := os.MkdirAll(vppMemifSocketDir, 0755); err != nil { log.Fatalln("ERROR: Failed to create VPP memif socket folder " + vppMemifSocketDir) return nil, err } // connect to VPP conn, err := govpp.Connect(vppApiSocketPath) if err != nil { log.Fatalln("ERROR: Connecting to VPP failed:", err) return nil, err } defer conn.Disconnect() // create a channel ch, err := conn.NewAPIChannel() if err != nil { log.Fatalln("ERROR: creating channel failed:", err) return nil, err } defer ch.Close() if err := ch.CheckCompatiblity(&memif.MemifSocketFilenameAddDel{}, &memif.MemifCreate{}, &memif.MemifDelete{}); err != nil { return nil, err } if err := ch.CheckCompatiblity(&interfaces.SwInterfaceSetFlags{}, &interfaces.SwInterfaceSetMtu{}); err != nil { return nil, err } if err := ch.CheckCompatiblity(&l2.L2fibAddDel{}, &l2.SwInterfaceSetL2Bridge{}); err != nil { return nil, err } memifservice := memif.NewServiceClient(conn) l2service := l2.NewServiceClient(conn) interfacservice := interfaces.NewServiceClient(conn) IfMacAddr, err := GetMacAddr(iconfig.MacAddrPrefix, uint32(NodeID)) if err != nil { log.Fatalln("ERROR: Failed parse mac address:", iconfig.MacAddrPrefix) return nil, err } vppIfMacAddr := ethernet_types.MacAddress(IfMacAddr) tap := &VppTap{ name: iconfig.Name, mtu: int(iconfig.MTU), ifuid: iconfig.VPPIFaceID, SwIfIndex: 0, memifSockPath: path.Join(vppMemifSocketDir, iconfig.Name+".sock"), secret: mtypes.RandomStr(16, iconfig.Name), logger: log, RxintChNext: make(chan uint8, 1<<6), errors: make(chan error, 1<<5), events: make(chan Event, 1<<4), } // create memif socket id 1 filename /tmp/icmp-responder-example _, err = memifservice.MemifSocketFilenameAddDel(context.Background(), &memif.MemifSocketFilenameAddDel{ IsAdd: true, SocketID: iconfig.VPPIFaceID, SocketFilename: tap.memifSockPath, }) if err != nil { return nil, err } // create interface memif id 1 socket-id 1 slave secret secret no-zero-copy memifCreateReply, err := memifservice.MemifCreate(context.Background(), &memif.MemifCreate{ Role: memif.MEMIF_ROLE_API_SLAVE, Mode: memif.MEMIF_MODE_API_ETHERNET, RxQueues: NumQueues, // MEMIF_DEFAULT_RX_QUEUES TxQueues: NumQueues, // MEMIF_DEFAULT_TX_QUEUES ID: tap.ifuid, SocketID: tap.ifuid, RingSize: 1024, // MEMIF_DEFAULT_RING_SIZE BufferSize: 2048, // MEMIF_DEFAULT_BUFFER_SIZE 2048 NoZeroCopy: true, HwAddr: vppIfMacAddr, Secret: tap.secret, }) if err != nil { return nil, err } tap.SwIfIndex = memifCreateReply.SwIfIndex // set int state memif1/1 up _, err = interfacservice.SwInterfaceSetFlags(context.Background(), &interfaces.SwInterfaceSetFlags{ SwIfIndex: tap.SwIfIndex, Flags: interface_types.IF_STATUS_API_FLAG_ADMIN_UP, }) if err != nil { return nil, err } if iconfig.VPPBridgeID != 0 { //set interface l2 bridge memif1/1 4242 _, err = l2service.SwInterfaceSetL2Bridge(context.Background(), &l2.SwInterfaceSetL2Bridge{ RxSwIfIndex: tap.SwIfIndex, BdID: iconfig.VPPBridgeID, PortType: l2.L2_API_PORT_TYPE_NORMAL, Shg: 0, Enable: true, }) if err != nil { return nil, err } } //init libmemif libmemif.Init(tap.name) onConnectWg.Add(1) memifCallbacks := &libmemif.MemifCallbacks{ OnConnect: OnConnect, OnDisconnect: OnDisconnect, } // Prepare memif1 configuration. memifConfig := &libmemif.MemifConfig{ MemifMeta: libmemif.MemifMeta{ IfName: tap.name, ConnID: tap.ifuid, SocketFilename: tap.memifSockPath, Secret: tap.secret, IsMaster: true, Mode: libmemif.IfModeEthernet, }, MemifShmSpecs: libmemif.MemifShmSpecs{ NumRxQueues: NumQueues, NumTxQueues: NumQueues, BufferSize: 2048, Log2RingSize: 10, }, } // Create memif1 interface. memif, err := libmemif.CreateInterface(memifConfig, memifCallbacks) if err != nil { tap.logger.Errorf("libmemif.CreateInterface() error: %v\n", err) return nil, err } onConnectWg.Wait() _, err = interfacservice.SwInterfaceSetMtu(context.Background(), &interfaces.SwInterfaceSetMtu{ SwIfIndex: tap.SwIfIndex, Mtu: []uint32{uint32(tap.mtu)}, }) if err != nil { return nil, err } tap.memif = memif details, err := memif.GetDetails() tap.RxQueues = len(details.RxQueues) tap.RxintCh = memif.GetInterruptChan() tap.RxintErrCh = memif.GetInterruptErrorChan() tap.TxQueues = len(details.TxQueues) tunErrorChannel = tap.errors tap.events <- EventUp return tap, nil } // SetMTU sets the Maximum Tansmission Unit Size for a // Packet on the interface. func (tap *VppTap) Read(buf []byte, offset int) (n int, err error) { select { case err = <-tap.RxintErrCh: tap.logger.Errorf("libmemif.Memif.RxintErr() error: %v\n", err) return 0, err case err = <-tap.errors: if err == nil { err = errors.New("Device closed") } tap.logger.Errorf("tun error: %v\n", err) return 0, err case queueID := <-tap.RxintCh: select { case tap.RxintChNext <- queueID: { // Use non-blocking write to prevent program stuck } default: tap.logger.Debugln("Buffer full") } case queueID := <-tap.RxintChNext: packets, err := tap.memif.RxBurst(queueID, 1) if err != nil { tap.logger.Errorf("libmemif.Memif.RxBurst() error: %v\n", err) return 0, err } if len(packets) == 0 { // No more packets to read until the next interrupt. return 0, nil } for _, packetData := range packets { select { case tap.RxintChNext <- queueID: { // Use non-blocking write to prevent program stuck // repeatedly call RxBurst() until returns an empty slice of packets } default: tap.logger.Debugln("Buffer full") } n = copy(buf[offset:], packetData) } } return } // read a packet from the device (without any additional headers) func (tap *VppTap) Write(buf []byte, offset int) (size int, err error) { queueID := tap.getTxQueueID() buf = buf[offset:] n, err := tap.memif.TxBurst(queueID, []libmemif.RawPacketData{buf}) return len(buf) * int(n), err } // writes a packet to the device (without any additional headers) func (tap *VppTap) Flush() error { return nil } // flush all previous writes to the device func (tap *VppTap) MTU() (int, error) { return tap.mtu, nil } // returns the MTU of the device func (tap *VppTap) Name() (string, error) { return tap.name, nil } // fetches and returns the current name func (tap *VppTap) Events() chan Event { return tap.events } // returns a constant channel of events related to the device func (tap *VppTap) Close() error { // connect to VPP conn, err := govpp.Connect(vppApiSocketPath) if err != nil { log.Fatalln("ERROR: connecting to VPP failed:", err) } defer conn.Disconnect() // create a channel ch, err := conn.NewAPIChannel() if err != nil { log.Fatalln("ERROR: creating channel failed:", err) } defer ch.Close() memifservice := memif.NewServiceClient(conn) // delete interface memif memif1/1 _, err = memifservice.MemifDelete(context.Background(), &memif.MemifDelete{ SwIfIndex: tap.SwIfIndex, }) // delete memif socket id 1 _, err = memifservice.MemifSocketFilenameAddDel(context.Background(), &memif.MemifSocketFilenameAddDel{ IsAdd: false, SocketID: tap.ifuid, SocketFilename: tap.memifSockPath, }) tap.memif.Close() libmemif.Cleanup() tap.events <- EventDown close(tap.errors) close(tap.events) return nil } // stops the device and closes the event channel // OnConnect is called when a memif connection gets established. func OnConnect(memif *libmemif.Memif) (err error) { details, err := memif.GetDetails() if err != nil { fmt.Printf("libmemif.GetDetails() error: %v\n", err) } fmt.Printf("memif %s has been connected: %+v\n", memif.IfName, details) onConnectWg.Done() return nil } // OnDisconnect is called when a memif connection is lost. func OnDisconnect(memif *libmemif.Memif) (err error) { tunErrorChannel <- errors.New(fmt.Sprintf("memif %s has been disconnected", memif.IfName)) return nil } func (tun *VppTap) getTxQueueID() uint8 { if tun.TxQueues == 1 { return 0 } tun.TxCount++ return uint8(tun.TxCount % uint(tun.TxQueues)) }