blob: a571deb43c9cb7b4208677a1307364fa1ee1a2dc [file] [log] [blame]
/*
*------------------------------------------------------------------
* Copyright (c) 2020 Cisco and/or its affiliates.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*------------------------------------------------------------------
*/
// Package memif provides the implementation of shared memory interface (memif).
//
// Memif network interfaces communicate using UNIX domain socket. This socket
// must be first created using NewSocket(). Then interfaces can be added
// to this socket using NewInterface(). To start communication on each socket
// socket.StartPolling() must be called. socket.StopPolling() will stop
// the communication. When the interface changes link status Connected and
// Disconencted callbacks set in Arguments for each interface are called
// respectively. Once the interface is connected rx and tx queues can be
// aquired using interface.GetRxQueue() and interface.GetTxQueue().
// Packets can be transmitted by calling queue.ReadPacket() on rx queues and
// queue.WritePacket() on tx queues. If the interface is disconnected
// queue.ReadPacket() and queue.WritePacket() MUST not be called.
//
// Data transmission is backed by shared memory. The driver works in
// promiscuous mode only.
package memif
import (
"container/list"
"fmt"
"os"
"syscall"
)
const (
DefaultSocketFilename = "/run/vpp/memif.sock"
DefaultNumQueuePairs = 1
DefaultLog2RingSize = 10
DefaultPacketBufferSize = 2048
)
const mfd_allow_sealing = 2
const sys_memfd_create = 319
const f_add_seals = 1033
const f_seal_shrink = 0x0002
const efd_nonblock = 04000
// ConnectedFunc is a callback called when an interface is connected
type ConnectedFunc func(i *Interface) error
// DisconnectedFunc is a callback called when an interface is disconnected
type DisconnectedFunc func(i *Interface) error
// MemoryConfig represents shared memory configuration
type MemoryConfig struct {
NumQueuePairs uint16 // number of queue pairs
Log2RingSize uint8 // ring size as log2
PacketBufferSize uint32 // size of single packet buffer
}
// Arguments represent interface configuration
type Arguments struct {
Id uint32 // Interface identifier unique across socket. Used to identify peer interface when connecting
IsMaster bool // Interface role master/slave
Name string
Secret [24]byte // optional parameter, secrets of the interfaces must match if they are to connect
MemoryConfig MemoryConfig
ConnectedFunc ConnectedFunc // callback called when interface changes status to connected
DisconnectedFunc DisconnectedFunc // callback called when interface changes status to disconnected
PrivateData interface{} // private data used by client program
}
// memoryRegion represents a shared memory mapped file
type memoryRegion struct {
data []byte
size uint64
fd int
packetBufferOffset uint32
}
// Queue represents rx or tx queue
type Queue struct {
ring *ring
i *Interface
lastHead uint16
lastTail uint16
interruptFd int
}
// Interface represents memif network interface
type Interface struct {
args Arguments
run MemoryConfig
privateData interface{}
listRef *list.Element
socket *Socket
cc *controlChannel
remoteName string
peerName string
regions []memoryRegion
txQueues []Queue
rxQueues []Queue
}
// IsMaster returns true if the interfaces role is master, else returns false
func (i *Interface) IsMaster() bool {
return i.args.IsMaster
}
// GetRemoteName returns the name of the application on which the peer
// interface exists
func (i *Interface) GetRemoteName() string {
return i.remoteName
}
// GetPeerName returns peer interfaces name
func (i *Interface) GetPeerName() string {
return i.peerName
}
// GetName returens interfaces name
func (i *Interface) GetName() string {
return i.args.Name
}
// GetMemoryConfig returns interfaces active memory config.
// If interface is not connected the config is invalid.
func (i *Interface) GetMemoryConfig() MemoryConfig {
return i.run
}
// GetRxQueue returns an rx queue specified by queue index
func (i *Interface) GetRxQueue(qid int) (*Queue, error) {
if qid >= len(i.rxQueues) {
return nil, fmt.Errorf("Invalid Queue index")
}
return &i.rxQueues[qid], nil
}
// GetRxQueue returns a tx queue specified by queue index
func (i *Interface) GetTxQueue(qid int) (*Queue, error) {
if qid >= len(i.txQueues) {
return nil, fmt.Errorf("Invalid Queue index")
}
return &i.txQueues[qid], nil
}
// GetEventFd returns queues interrupt event fd
func (q *Queue) GetEventFd() (int, error) {
return q.interruptFd, nil
}
// GetFilename returns sockets filename
func (socket *Socket) GetFilename() string {
return socket.filename
}
// close closes the queue
func (q *Queue) close() {
syscall.Close(q.interruptFd)
}
// IsConnecting returns true if the interface is connecting
func (i *Interface) IsConnecting() bool {
if i.cc != nil {
return true
}
return false
}
// IsConnected returns true if the interface is connected
func (i *Interface) IsConnected() bool {
if i.cc != nil && i.cc.isConnected {
return true
}
return false
}
// Disconnect disconnects the interface
func (i *Interface) Disconnect() (err error) {
if i.cc != nil {
// close control and disconenct interface
return i.cc.close(true, "Interface disconnected")
}
return nil
}
// disconnect finalizes interface disconnection
func (i *Interface) disconnect() (err error) {
if i.cc == nil { // disconnected
return nil
}
err = i.args.DisconnectedFunc(i)
if err != nil {
return fmt.Errorf("DisconnectedFunc: ", err)
}
for _, q := range i.txQueues {
q.close()
}
i.txQueues = []Queue{}
for _, q := range i.rxQueues {
q.close()
}
i.rxQueues = []Queue{}
// unmap regions
for _, r := range i.regions {
err = syscall.Munmap(r.data)
if err != nil {
return err
}
err = syscall.Close(r.fd)
if err != nil {
return err
}
}
i.regions = nil
i.cc = nil
i.peerName = ""
i.remoteName = ""
return nil
}
// Delete deletes the interface
func (i *Interface) Delete() (err error) {
i.Disconnect()
// remove referance on socket
i.socket.interfaceList.Remove(i.listRef)
i = nil
return nil
}
// GetSocket returns the socket the interface belongs to
func (i *Interface) GetSocket() *Socket {
return i.socket
}
// GetPrivateDate returns interfaces private data
func (i *Interface) GetPrivateData() interface{} {
return i.args.PrivateData
}
// GetId returns interfaces id
func (i *Interface) GetId() uint32 {
return i.args.Id
}
// RoleToString returns 'Master' if isMaster os true, else returns 'Slave'
func RoleToString(isMaster bool) string {
if isMaster {
return "Master"
}
return "Slave"
}
// RequestConnection is used by slave interface to connect to a socket and
// create a control channel
func (i *Interface) RequestConnection() error {
if i.IsMaster() {
return fmt.Errorf("Only slave can request connection")
}
// create socket
fd, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_SEQPACKET, 0)
if err != nil {
return fmt.Errorf("Failed to create UNIX domain socket: %v", err)
}
usa := &syscall.SockaddrUnix{Name: i.socket.filename}
// Connect to listener socket
err = syscall.Connect(fd, usa)
if err != nil {
return fmt.Errorf("Failed to connect socket %s : %v", i.socket.filename, err)
}
// Create control channel
i.cc, err = i.socket.addControlChannel(fd, i)
if err != nil {
return fmt.Errorf("Failed to create control channel: %v", err)
}
return nil
}
// NewInterface returns a new memif network interface. When creating an interface
// it's id must be unique across socket with the exception of loopback interface
// in which case the id is the same but role differs
func (socket *Socket) NewInterface(args *Arguments) (*Interface, error) {
var err error
// make sure the ID is unique on this socket
for elt := socket.interfaceList.Front(); elt != nil; elt = elt.Next() {
i, ok := elt.Value.(*Interface)
if ok {
if i.args.Id == args.Id && i.args.IsMaster == args.IsMaster {
return nil, fmt.Errorf("Interface with id %u role %s already exists on this socket", args.Id, RoleToString(args.IsMaster))
}
}
}
// copy interface configuration
i := Interface{
args: *args,
}
// set default values
if i.args.MemoryConfig.NumQueuePairs == 0 {
i.args.MemoryConfig.NumQueuePairs = DefaultNumQueuePairs
}
if i.args.MemoryConfig.Log2RingSize == 0 {
i.args.MemoryConfig.Log2RingSize = DefaultLog2RingSize
}
if i.args.MemoryConfig.PacketBufferSize == 0 {
i.args.MemoryConfig.PacketBufferSize = DefaultPacketBufferSize
}
i.socket = socket
// append interface to the list
i.listRef = socket.interfaceList.PushBack(&i)
if i.args.IsMaster {
if socket.listener == nil {
err = socket.addListener()
if err != nil {
return nil, fmt.Errorf("Failed to create listener channel: %s", err)
}
}
}
return &i, nil
}
// eventFd returns an eventfd (SYS_EVENTFD2)
func eventFd() (efd int, err error) {
u_efd, _, errno := syscall.Syscall(syscall.SYS_EVENTFD2, uintptr(0), uintptr(efd_nonblock), 0)
if errno != 0 {
return -1, os.NewSyscallError("eventfd", errno)
}
return int(u_efd), nil
}
// addRegions creates and adds a new memory region to the interface (slave only)
func (i *Interface) addRegion(hasPacketBuffers bool, hasRings bool) (err error) {
var r memoryRegion
if hasRings {
r.packetBufferOffset = uint32((i.run.NumQueuePairs + i.run.NumQueuePairs) * (ringSize + descSize*(1<<i.run.Log2RingSize)))
} else {
r.packetBufferOffset = 0
}
if hasPacketBuffers {
r.size = uint64(r.packetBufferOffset + i.run.PacketBufferSize*uint32(1<<i.run.Log2RingSize)*uint32(i.run.NumQueuePairs+i.run.NumQueuePairs))
} else {
r.size = uint64(r.packetBufferOffset)
}
r.fd, err = memfdCreate()
if err != nil {
return err
}
_, _, errno := syscall.Syscall(syscall.SYS_FCNTL, uintptr(r.fd), uintptr(f_add_seals), uintptr(f_seal_shrink))
if errno != 0 {
syscall.Close(r.fd)
return fmt.Errorf("memfdCreate: %s", os.NewSyscallError("fcntl", errno))
}
err = syscall.Ftruncate(r.fd, int64(r.size))
if err != nil {
syscall.Close(r.fd)
r.fd = -1
return fmt.Errorf("memfdCreate: %s", err)
}
r.data, err = syscall.Mmap(r.fd, 0, int(r.size), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
if err != nil {
return fmt.Errorf("addRegion: %s", err)
}
i.regions = append(i.regions, r)
return nil
}
// initializeRegions initializes interfaces regions (slave only)
func (i *Interface) initializeRegions() (err error) {
err = i.addRegion(true, true)
if err != nil {
return fmt.Errorf("initializeRegions: %s", err)
}
return nil
}
// initializeQueues initializes interfaces queues (slave only)
func (i *Interface) initializeQueues() (err error) {
var q *Queue
var desc descBuf
var slot int
desc = newDescBuf()
desc.setFlags(0)
desc.setRegion(0)
desc.setLength(int(i.run.PacketBufferSize))
for qid := 0; qid < int(i.run.NumQueuePairs); qid++ {
/* TX */
q = &Queue{
ring: i.newRing(0, ringTypeS2M, qid),
lastHead: 0,
lastTail: 0,
i: i,
}
q.ring.setCookie(cookie)
q.ring.setFlags(1)
q.interruptFd, err = eventFd()
if err != nil {
return err
}
q.putRing()
i.txQueues = append(i.txQueues, *q)
for j := 0; j < q.ring.size; j++ {
slot = qid*q.ring.size + j
desc.setOffset(int(i.regions[0].packetBufferOffset + uint32(slot)*i.run.PacketBufferSize))
q.putDescBuf(slot, desc)
}
}
for qid := 0; qid < int(i.run.NumQueuePairs); qid++ {
/* RX */
q = &Queue{
ring: i.newRing(0, ringTypeM2S, qid),
lastHead: 0,
lastTail: 0,
i: i,
}
q.ring.setCookie(cookie)
q.ring.setFlags(1)
q.interruptFd, err = eventFd()
if err != nil {
return err
}
q.putRing()
i.rxQueues = append(i.rxQueues, *q)
for j := 0; j < q.ring.size; j++ {
slot = qid*q.ring.size + j
desc.setOffset(int(i.regions[0].packetBufferOffset + uint32(slot)*i.run.PacketBufferSize))
q.putDescBuf(slot, desc)
}
}
return nil
}
// connect finalizes interface connection
func (i *Interface) connect() (err error) {
for rid, _ := range i.regions {
r := &i.regions[rid]
if r.data == nil {
r.data, err = syscall.Mmap(r.fd, 0, int(r.size), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
if err != nil {
return fmt.Errorf("Mmap: %s", err)
}
}
}
for _, q := range i.txQueues {
q.updateRing()
if q.ring.getCookie() != cookie {
return fmt.Errorf("Wrong cookie")
}
q.lastHead = 0
q.lastTail = 0
}
for _, q := range i.rxQueues {
q.updateRing()
if q.ring.getCookie() != cookie {
return fmt.Errorf("Wrong cookie")
}
q.lastHead = 0
q.lastTail = 0
}
return i.args.ConnectedFunc(i)
}