├── .gitignore ├── .golangci.yml ├── acl └── cgo_shared.go ├── eal ├── cgo_shared.go ├── doc.go ├── parse.go ├── init_once.go └── eal_test.go ├── hash ├── cgo_shared.go ├── crc.go └── crc_test.go ├── lpm ├── cgo_shared.go ├── common.go ├── lpm6.go └── lpm.go ├── mbuf ├── cgo_shared.go └── mbuf_test.go ├── ring ├── cgo_shared.go ├── common.go ├── ring.h ├── ring_telemetry.go ├── ring_telemetry.h ├── enqueue.go └── dequeue.go ├── common ├── cgo_shared.go ├── objects_test.go ├── allocator_std.go ├── parse_test.go ├── allocator_rte.go ├── assert.go ├── endian.go ├── pointers.go ├── allocator_test.go ├── common.go ├── objects.go ├── set_test.go ├── pointers_test.go ├── parse.go └── set.go ├── ethdev ├── cgo_shared.go ├── flow │ ├── cgo_shared.go │ ├── flow_test.go │ ├── action_queue.go │ ├── action.go │ ├── utils.go │ ├── item_udp.go │ ├── sample_flow.h │ ├── item_vlan.go │ ├── item_ipv4.go │ ├── action_rss.go │ ├── errors.go │ ├── item.go │ ├── item_eth.go │ └── attr.go ├── lsc_telemetry.c ├── lsc_telemetry.go ├── lsc_telemetry.h ├── stats_test.go └── rxtx.go ├── mempool ├── cgo_shared.go ├── cache.go └── mbuf.go ├── memzone ├── cgo_shared.go └── memzone_test.go ├── port ├── cgo_shared.go ├── stats.go ├── ethdev_test.go ├── source_sink_test.go ├── fd_test.go ├── fd.go ├── ethdev.go ├── ring.go ├── port_test.go ├── source_sink.go └── port.go ├── table ├── stats.go ├── acl_test.go ├── stub.go ├── table_test.go ├── lpm_test.go ├── acl_params_test.go ├── array.go ├── table.go ├── acl_params.go ├── acl.go ├── hash.go └── lpm.go ├── app ├── helloworld │ └── main.go ├── ring │ └── main.go ├── test-sniffer │ ├── main.go │ ├── mempool.go │ ├── queue_count.go │ ├── port.go │ ├── init.go │ ├── lcores.go │ └── util.go └── dpdk-exporter │ └── main.go ├── lcore ├── lcore_test.go ├── lcore_linux.go ├── lcore.go ├── thread.go └── thread_test.go ├── .github └── workflows │ ├── linter.yml │ └── unit.yml ├── util ├── errors.go ├── lcores_test.go ├── make_hash_test.go ├── mbuf_array.h ├── make_hash.go ├── lcores.go └── rx_buffer.go ├── go.mod ├── README.md ├── pipeline ├── pipeline_loop.h ├── pipeline_loop_test.go ├── stats.go ├── pipeline_loop.go ├── pipeline_test.go └── port.go ├── LICENSE └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable: 3 | - revive 4 | - misspell 5 | -------------------------------------------------------------------------------- /acl/cgo_shared.go: -------------------------------------------------------------------------------- 1 | package acl 2 | 3 | /* 4 | #cgo pkg-config: libdpdk 5 | */ 6 | import "C" 7 | -------------------------------------------------------------------------------- /eal/cgo_shared.go: -------------------------------------------------------------------------------- 1 | package eal 2 | 3 | /* 4 | #cgo pkg-config: libdpdk 5 | */ 6 | import "C" 7 | -------------------------------------------------------------------------------- /hash/cgo_shared.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | /* 4 | #cgo pkg-config: libdpdk 5 | */ 6 | import "C" 7 | -------------------------------------------------------------------------------- /lpm/cgo_shared.go: -------------------------------------------------------------------------------- 1 | package lpm 2 | 3 | /* 4 | #cgo pkg-config: libdpdk 5 | */ 6 | import "C" 7 | -------------------------------------------------------------------------------- /mbuf/cgo_shared.go: -------------------------------------------------------------------------------- 1 | package mbuf 2 | 3 | /* 4 | #cgo pkg-config: libdpdk 5 | */ 6 | import "C" 7 | -------------------------------------------------------------------------------- /ring/cgo_shared.go: -------------------------------------------------------------------------------- 1 | package ring 2 | 3 | /* 4 | #cgo pkg-config: libdpdk 5 | */ 6 | import "C" 7 | -------------------------------------------------------------------------------- /common/cgo_shared.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | /* 4 | #cgo pkg-config: libdpdk 5 | */ 6 | import "C" 7 | -------------------------------------------------------------------------------- /ethdev/cgo_shared.go: -------------------------------------------------------------------------------- 1 | package ethdev 2 | 3 | /* 4 | #cgo pkg-config: libdpdk 5 | */ 6 | import "C" 7 | -------------------------------------------------------------------------------- /ethdev/flow/cgo_shared.go: -------------------------------------------------------------------------------- 1 | package flow 2 | 3 | /* 4 | #cgo pkg-config: libdpdk 5 | */ 6 | import "C" 7 | -------------------------------------------------------------------------------- /ethdev/lsc_telemetry.c: -------------------------------------------------------------------------------- 1 | #include "lsc_telemetry.h" 2 | 3 | struct lsc_counters global_lsc_counters = {}; 4 | -------------------------------------------------------------------------------- /mempool/cgo_shared.go: -------------------------------------------------------------------------------- 1 | package mempool 2 | 3 | /* 4 | #cgo pkg-config: libdpdk 5 | */ 6 | import "C" 7 | -------------------------------------------------------------------------------- /memzone/cgo_shared.go: -------------------------------------------------------------------------------- 1 | package memzone 2 | 3 | /* 4 | #cgo pkg-config: libdpdk 5 | */ 6 | import "C" 7 | -------------------------------------------------------------------------------- /port/cgo_shared.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package port wraps RTE Port. 3 | 4 | This tool is part of the DPDK Packet Framework tool suite and provides 5 | a standard interface to implement different types of packet ports. 6 | */ 7 | package port 8 | 9 | /* 10 | #cgo pkg-config: libdpdk 11 | */ 12 | import "C" 13 | -------------------------------------------------------------------------------- /ethdev/flow/flow_test.go: -------------------------------------------------------------------------------- 1 | package flow 2 | 3 | import "testing" 4 | 5 | func assert(t testing.TB, expected bool, args ...interface{}) { 6 | if !expected { 7 | t.Helper() 8 | t.Fatal(args...) 9 | } 10 | } 11 | 12 | func TestCPattern(t *testing.T) { 13 | pattern := []Item{ 14 | {Spec: &ItemIPv4{}, Mask: &ItemIPv4{}}, 15 | } 16 | 17 | pat := cPattern(pattern) 18 | assert(t, pat != nil) 19 | 20 | } 21 | -------------------------------------------------------------------------------- /table/stats.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | import ( 8 | "unsafe" 9 | ) 10 | 11 | // Stats is a table statistics. 12 | type Stats struct { 13 | PacketsIn uint64 14 | PacketsLookupMiss uint64 15 | } 16 | 17 | var _ uintptr = unsafe.Sizeof(Stats{}) - unsafe.Sizeof(C.struct_rte_table_stats{}) 18 | var _ uintptr = unsafe.Sizeof(C.struct_rte_table_stats{}) - unsafe.Sizeof(Stats{}) 19 | -------------------------------------------------------------------------------- /app/helloworld/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/yerden/go-dpdk/eal" 8 | ) 9 | 10 | func main() { 11 | if _, err := eal.Init(os.Args); err != nil { 12 | log.Fatalln("EAL init failed:", err) 13 | } 14 | defer eal.Cleanup() 15 | defer eal.StopLcores() 16 | 17 | for _, id := range eal.Lcores() { 18 | eal.ExecOnLcore(id, func(ctx *eal.LcoreCtx) { 19 | log.Println("hello from core", eal.LcoreID()) 20 | }) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /table/acl_test.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/yerden/go-dpdk/common" 7 | ) 8 | 9 | func TestACLParams(t *testing.T) { 10 | assert := common.Assert(t, true) 11 | 12 | p := &ACLParams{ 13 | Name: "hello", 14 | Rules: 16, 15 | FieldFormat: []ACLFieldDef{ 16 | {Type: 1}, // some shit 17 | }, 18 | } 19 | 20 | cptr, dtor := p.Transform(alloc) 21 | assert(cptr != nil) 22 | assert(dtor != nil) 23 | defer dtor(cptr) 24 | } 25 | -------------------------------------------------------------------------------- /ring/common.go: -------------------------------------------------------------------------------- 1 | package ring 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | #include "ring.h" 8 | */ 9 | import "C" 10 | 11 | import ( 12 | "unsafe" 13 | ) 14 | 15 | func args(r *Ring, obj []unsafe.Pointer) (*C.struct_rte_ring, 16 | C.uintptr_t, C.uint) { 17 | return (*C.struct_rte_ring)(r), C.uintptr_t(uintptr(unsafe.Pointer(&obj[0]))), C.uint(len(obj)) 18 | } 19 | 20 | func ret(out C.struct_compound_int) (rc, n uint32) { 21 | return uint32(out.rc), uint32(out.n) 22 | } 23 | -------------------------------------------------------------------------------- /lpm/common.go: -------------------------------------------------------------------------------- 1 | package lpm 2 | 3 | import ( 4 | "encoding/binary" 5 | "net/netip" 6 | ) 7 | 8 | func cvtIPv4(addr netip.Addr) (ip uint32) { 9 | a4 := addr.As4() 10 | return binary.BigEndian.Uint32(a4[:]) 11 | } 12 | 13 | func cvtIPv4Net(prefix netip.Prefix) (ip uint32, bits uint8) { 14 | return cvtIPv4(prefix.Masked().Addr()), uint8(prefix.Bits()) 15 | } 16 | 17 | func cvtIPv6Net(prefix netip.Prefix) (ip [16]byte, bits uint8) { 18 | addr := prefix.Masked().Addr() 19 | if !addr.Is6() || addr.Is4In6() { 20 | panic("not an IPv6 address") 21 | } 22 | return addr.As16(), uint8(prefix.Bits()) 23 | } 24 | -------------------------------------------------------------------------------- /table/stub.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | 8 | import ( 9 | "unsafe" 10 | 11 | "github.com/yerden/go-dpdk/common" 12 | ) 13 | 14 | // StubParams is the empty parameters for Stub table. 15 | type StubParams struct{} 16 | 17 | // Transform implements common.Transformer interface. 18 | func (p *StubParams) Transform(alloc common.Allocator) (unsafe.Pointer, func(unsafe.Pointer)) { 19 | return nil, alloc.Free 20 | } 21 | 22 | // Ops implements Params interface. 23 | func (p *StubParams) Ops() *Ops { 24 | return (*Ops)(&C.rte_table_stub_ops) 25 | } 26 | -------------------------------------------------------------------------------- /eal/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package eal wraps EAL initialization and provides some additional functionality 3 | on top of that. Every CPU's logical core which is setup by EAL runs its own 4 | function which essentially receives functions to execute via Go channel. So you 5 | may run arbitrary Go code in the context of EAL thread. 6 | 7 | EAL may be initialized via command line string, parsed command line string or a 8 | set of Options. 9 | 10 | Please note that some functions may be called only in EAL thread because of TLS 11 | (Thread Local Storage) dependency. 12 | 13 | API is a subject to change. Be aware. 14 | */ 15 | package eal 16 | -------------------------------------------------------------------------------- /common/objects_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func testRegistry(t *testing.T, r Registry) { 8 | t.Helper() 9 | assert := Assert(t, true) 10 | 11 | x := int64(1) 12 | xObj := r.Create(x) 13 | 14 | y, ok := r.Read(xObj).(int64) 15 | assert(ok) 16 | assert(x == y) 17 | 18 | y = int64(2) 19 | r.Update(xObj, y) 20 | 21 | x, ok = r.Read(xObj).(int64) 22 | assert(ok) 23 | assert(x == y) 24 | 25 | r.Delete(xObj) 26 | } 27 | 28 | func TestRegistryArray(t *testing.T) { 29 | testRegistry(t, NewRegistryArray()) 30 | } 31 | 32 | func TestRegistryMap(t *testing.T) { 33 | testRegistry(t, NewRegistryMap()) 34 | } 35 | -------------------------------------------------------------------------------- /lcore/lcore_test.go: -------------------------------------------------------------------------------- 1 | package lcore 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestReadCpuHexMap(t *testing.T) { 9 | s := "4f" 10 | b := bytes.NewBufferString(s) 11 | 12 | cores, err := readCPUHexMap(b) 13 | a := []int{0, 1, 2, 3, 6} 14 | 15 | if err != nil { 16 | t.FailNow() 17 | } 18 | for i := range a { 19 | if a[i] != cores[i] { 20 | t.FailNow() 21 | } 22 | } 23 | if len(a) != len(cores) { 24 | t.FailNow() 25 | } 26 | } 27 | 28 | func TestReadCpuHexMapErr(t *testing.T) { 29 | s := "4z" 30 | b := bytes.NewBufferString(s) 31 | 32 | _, err := readCPUHexMap(b) 33 | 34 | if err != errInvalidMap { 35 | t.FailNow() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /common/allocator_std.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | /* 4 | #include 5 | #include 6 | */ 7 | import "C" 8 | 9 | import "unsafe" 10 | 11 | // StdAlloc wraps system malloc/free memory allocation. 12 | type StdAlloc struct{} 13 | 14 | var _ Allocator = (*StdAlloc)(nil) 15 | 16 | // Malloc implements Allocator. 17 | func (mem *StdAlloc) Malloc(size uintptr) unsafe.Pointer { 18 | return C.malloc(C.size_t(size)) 19 | } 20 | 21 | // Free implements Allocator. 22 | func (mem *StdAlloc) Free(p unsafe.Pointer) { 23 | C.free(p) 24 | } 25 | 26 | // Realloc implements Allocator. 27 | func (mem *StdAlloc) Realloc(p unsafe.Pointer, size uintptr) unsafe.Pointer { 28 | return C.realloc(p, C.size_t(size)) 29 | } 30 | -------------------------------------------------------------------------------- /eal/parse.go: -------------------------------------------------------------------------------- 1 | package eal 2 | 3 | import ( 4 | "bufio" 5 | "strings" 6 | 7 | "github.com/yerden/go-dpdk/common" 8 | ) 9 | 10 | func parseCmd(input string) ([]string, error) { 11 | s := bufio.NewScanner(strings.NewReader(input)) 12 | s.Split(common.SplitFunc(common.DefaultSplitter)) 13 | 14 | var argv []string 15 | for s.Scan() { 16 | argv = append(argv, s.Text()) 17 | } 18 | return argv, s.Err() 19 | } 20 | 21 | // InitCmd initializes EAL as in rte_eal_init. Options are specified 22 | // in a unparsed command line string. This string is parsed and 23 | // Init is then called upon. 24 | func InitCmd(input string) (int, error) { 25 | argv, err := parseCmd(input) 26 | if err != nil { 27 | return 0, err 28 | } 29 | return Init(argv) 30 | } 31 | -------------------------------------------------------------------------------- /port/stats.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | import ( 8 | "unsafe" 9 | ) 10 | 11 | // InStats is an input port statistics. 12 | type InStats struct { 13 | PacketsIn uint64 14 | PacketsDrop uint64 15 | } 16 | 17 | var _ uintptr = unsafe.Sizeof(InStats{}) - unsafe.Sizeof(C.struct_rte_port_in_stats{}) 18 | var _ uintptr = unsafe.Sizeof(C.struct_rte_port_in_stats{}) - unsafe.Sizeof(InStats{}) 19 | 20 | // OutStats is an output port statistics. 21 | type OutStats struct { 22 | PacketsIn uint64 23 | PacketsDrop uint64 24 | } 25 | 26 | var _ uintptr = unsafe.Sizeof(OutStats{}) - unsafe.Sizeof(C.struct_rte_port_out_stats{}) 27 | var _ uintptr = unsafe.Sizeof(C.struct_rte_port_out_stats{}) - unsafe.Sizeof(OutStats{}) 28 | -------------------------------------------------------------------------------- /table/table_test.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/yerden/go-dpdk/common" 7 | "github.com/yerden/go-dpdk/eal" 8 | ) 9 | 10 | func TestTableHash(t *testing.T) { 11 | assert := common.Assert(t, true) 12 | 13 | // Initialize EAL on all cores 14 | eal.InitOnceSafe("test", 4) 15 | 16 | err := eal.ExecOnMain(func(*eal.LcoreCtx) { 17 | hash := &HashParams{ 18 | TableOps: HashCuckooOps, 19 | Name: "hello", 20 | KeySize: 32, 21 | KeysNum: 1024, 22 | BucketsNum: 1024, 23 | } 24 | 25 | hash.CuckooHash.Func = Crc32Hash 26 | 27 | table1 := Create(0, hash, 20) 28 | assert(table1 != nil) 29 | 30 | err := table1.Free(hash.TableOps) 31 | assert(err == nil) 32 | }) 33 | assert(err == nil, err) 34 | } 35 | -------------------------------------------------------------------------------- /ethdev/flow/action_queue.go: -------------------------------------------------------------------------------- 1 | package flow 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | */ 8 | import "C" 9 | import ( 10 | "runtime" 11 | ) 12 | 13 | var _ Action = (*ActionQueue)(nil) 14 | 15 | // ActionQueue implements Action which assigns packets to a given 16 | // queue index. 17 | type ActionQueue struct { 18 | cPointer 19 | Index uint16 20 | } 21 | 22 | // Reload implements Action interface. 23 | func (action *ActionQueue) Reload() { 24 | cptr := (*C.struct_rte_flow_action_queue)(action.createOrRet(C.sizeof_struct_rte_flow_action_queue)) 25 | 26 | cptr.index = C.uint16_t(action.Index) 27 | runtime.SetFinalizer(action, (*ActionQueue).free) 28 | } 29 | 30 | // Type implements Action interface. 31 | func (action *ActionQueue) Type() ActionType { 32 | return ActionTypeQueue 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | name: linter 2 | 3 | on: 4 | pull_request: 5 | types: [opened, reopened, synchronize, ready_for_review] 6 | 7 | env: 8 | TERM: "xterm" 9 | FORCE_COLOR: "1" 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | container: nedrey/dpdk-rockylinux8:v21.11-go1.19-snf 15 | steps: 16 | - 17 | name: Checkout 18 | uses: actions/checkout@v3 19 | - 20 | name: Linter Mandatory 21 | shell: 'script -q -e -c "bash {0}"' 22 | run: | 23 | # don't check unit tests and sample apps 24 | golangci-lint run --timeout 5m --skip-files=".*_test.go" --skip-dirs="^app" 25 | - 26 | name: Linter 27 | shell: 'script -q -e -c "bash {0}"' 28 | run: | 29 | golangci-lint run --timeout 5m --issues-exit-code 0 30 | 31 | -------------------------------------------------------------------------------- /table/lpm_test.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/yerden/go-dpdk/common" 7 | "github.com/yerden/go-dpdk/eal" 8 | ) 9 | 10 | func TestLPMParams(t *testing.T) { 11 | assert := common.Assert(t, true) 12 | 13 | eal.InitOnceSafe("test", 4) 14 | 15 | params := &LPMParams{ 16 | Name: "hello", 17 | NumTBL8: 16, 18 | Rules: 1024, 19 | Offset: 0, 20 | EntryUniqueSize: 2, 21 | } 22 | 23 | { 24 | params.TableOps = LPMOps 25 | ops := params.Ops() 26 | tbl := Create(-1, params, 2) 27 | assert(tbl != nil) 28 | err := tbl.Free(ops) 29 | assert(err == nil) 30 | } 31 | 32 | { 33 | params.TableOps = LPM6Ops 34 | ops := params.Ops() 35 | tbl := Create(-1, params, 2) 36 | assert(tbl != nil) 37 | err := tbl.Free(ops) 38 | assert(err == nil) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /util/errors.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | // ErrWithMessage wraps Err and buffer to store annotation. 9 | type ErrWithMessage struct { 10 | Err error 11 | B bytes.Buffer 12 | } 13 | 14 | func (e *ErrWithMessage) Error() string { 15 | return fmt.Sprintf("%v: %v", e.B.String(), e.Err) 16 | } 17 | 18 | func (e *ErrWithMessage) Unwrap() error { 19 | return e.Err 20 | } 21 | 22 | // ErrWrapf annotates specified err with formatted string. 23 | func ErrWrapf(err error, format string, args ...interface{}) error { 24 | if err == nil { 25 | return nil 26 | } 27 | e := &ErrWithMessage{Err: err} 28 | fmt.Fprintf(&e.B, format, args...) 29 | return e 30 | } 31 | 32 | // ErrWrap annotates specified err with message string. 33 | func ErrWrap(err error, msg string) error { 34 | return ErrWrapf(err, msg) 35 | } 36 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/yerden/go-dpdk 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/prometheus/client_golang v1.17.0 7 | github.com/stretchr/testify v1.9.0 8 | golang.org/x/sys v0.18.0 9 | ) 10 | 11 | require ( 12 | github.com/beorn7/perks v1.0.1 // indirect 13 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/golang/protobuf v1.5.3 // indirect 16 | github.com/kr/text v0.2.0 // indirect 17 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 18 | github.com/pmezard/go-difflib v1.0.0 // indirect 19 | github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect 20 | github.com/prometheus/common v0.44.0 // indirect 21 | github.com/prometheus/procfs v0.11.1 // indirect 22 | google.golang.org/protobuf v1.31.0 // indirect 23 | gopkg.in/yaml.v3 v3.0.1 // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /table/acl_params_test.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/yerden/go-dpdk/common" 7 | ) 8 | 9 | func TestACLField(t *testing.T) { 10 | assert := common.Assert(t, true) 11 | 12 | { 13 | f1 := NewACLField8(0xAA, 0xFF) 14 | f2 := NewACLField8(0xAA, 0xFF) 15 | assert(f1 == f2) 16 | } 17 | 18 | { 19 | f1 := NewACLField16(0xAABB, 0xFFF0) 20 | f2 := NewACLField16(0xAABB, 0xFFF0) 21 | assert(f1 == f2) 22 | } 23 | 24 | { 25 | f1 := NewACLField32(0xAABBFFF0, 0xFFFFFF00) 26 | f2 := NewACLField32(0xAABBFFF0, 0xFFFFFF00) 27 | assert(f1 == f2) 28 | } 29 | 30 | { 31 | f1 := NewACLField64(0xAABBFFF0FFFFFF00, 0xFFFFFF0FFFFFF00) 32 | f2 := NewACLField64(0xAABBFFF0FFFFFF00, 0xFFFFFF0FFFFFF00) 33 | assert(f1 == f2) 34 | } 35 | 36 | newRule := ACLRuleAdd{} 37 | newRule.Priority = 2 38 | newRule.FieldValue[0] = NewACLField8(0xAA, 0xFF) 39 | } 40 | -------------------------------------------------------------------------------- /common/parse_test.go: -------------------------------------------------------------------------------- 1 | package common_test 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "testing" 7 | 8 | "github.com/yerden/go-dpdk/common" 9 | ) 10 | 11 | func TestParseSplitDoubleQuotes(t *testing.T) { 12 | assert := common.Assert(t, true) 13 | 14 | input := "-c f -n 4 --socket-mem=\"1024, 1024\" --no-pci -d /path/to/so" 15 | b := bytes.NewBufferString(input) 16 | s := bufio.NewScanner(b) 17 | s.Split(common.SplitFunc(common.DefaultSplitter)) 18 | var argv []string 19 | 20 | for s.Scan() { 21 | argv = append(argv, s.Text()) 22 | } 23 | 24 | assert(len(argv) == 8, argv, len(argv)) 25 | assert(argv[0] == "-c", argv) 26 | assert(argv[1] == "f", argv) 27 | assert(argv[2] == "-n", argv) 28 | assert(argv[3] == "4", argv) 29 | assert(argv[4] == "--socket-mem=\"1024, 1024\"", argv) 30 | assert(argv[5] == "--no-pci", argv) 31 | assert(argv[6] == "-d", argv) 32 | assert(argv[7] == "/path/to/so", argv) 33 | } 34 | -------------------------------------------------------------------------------- /ethdev/lsc_telemetry.go: -------------------------------------------------------------------------------- 1 | package ethdev 2 | 3 | /* 4 | #include "lsc_telemetry.h" 5 | */ 6 | import "C" 7 | import ( 8 | "unsafe" 9 | 10 | "github.com/yerden/go-dpdk/common" 11 | ) 12 | 13 | // RegisterCallbackLSC installs a callback for RTE_ETH_EVENT_INTR_LSC event 14 | // which counts all occurred events. 15 | func (p Port) RegisterCallbackLSC() error { 16 | return common.IntErr(int64(C.lsc_counters_callback_register(C.ushort(p)))) 17 | } 18 | 19 | // UnregisterCallbackLSC removes callback for RTE_ETH_EVENT_INTR_LSC event 20 | // which counts all occurred events. 21 | func (p Port) UnregisterCallbackLSC() error { 22 | return common.IntErr(int64(C.lsc_counters_callback_unregister(C.ushort(p)))) 23 | } 24 | 25 | // RegisterTelemetryLSC registers telemetry handlers for accessing LSC counters. 26 | func RegisterTelemetryLSC(name string) { 27 | cname := C.CString(name) 28 | C.lsc_register_telemetry_cmd(cname) 29 | C.free(unsafe.Pointer(cname)) 30 | } 31 | -------------------------------------------------------------------------------- /util/lcores_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "testing" 4 | 5 | func assert(t testing.TB, expected bool, args ...interface{}) { 6 | if !expected { 7 | t.Helper() 8 | t.Fatal(args...) 9 | } 10 | } 11 | 12 | func TestLcoresBasic(t *testing.T) { 13 | list := LcoresList{2, 4, 5, 6, 7, 8, 1} 14 | sorted := LcoresList{1, 2, 4, 5, 6, 7, 8} 15 | 16 | assert(t, sorted.Equal(list)) 17 | assert(t, list.Equal(sorted)) 18 | 19 | list.Sort() 20 | } 21 | 22 | var testCases = []struct { 23 | LcoresList 24 | String string 25 | }{ 26 | {LcoresList{2, 4, 5, 6, 7, 8, 1}, "1-2,4-8"}, 27 | {LcoresList{2, 4, 5, 6}, "2,4-6"}, 28 | {LcoresList{4, 5, 2, 4}, "2,4-5"}, 29 | {LcoresList{4, 5, 6, 7, 7}, "4-7"}, 30 | {LcoresList{4, 5, 7, 8, 7}, "4-5,7-8"}, 31 | {LcoresList{4, 5, 6, 7, 9}, "4-7,9"}, 32 | {LcoresList{4, 6, 7, 8, 19}, "4,6-8,19"}, 33 | } 34 | 35 | func TestLcoresString(t *testing.T) { 36 | for i := range testCases { 37 | list := testCases[i].LcoresList 38 | s := testCases[i].String 39 | assert(t, list.String() == s, list.String(), s) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /common/allocator_rte.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | /* 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | */ 10 | import "C" 11 | 12 | import "unsafe" 13 | 14 | // RteAlloc implements allocator based on DPDK rte_malloc.h. 15 | type RteAlloc struct { 16 | // Requested alignment. 17 | Align uint 18 | 19 | // Requested NUMA node. Set to SocketIDAny if meaningless. 20 | Socket int 21 | } 22 | 23 | var _ Allocator = (*RteAlloc)(nil) 24 | 25 | // Malloc implements Allocator. 26 | func (mem *RteAlloc) Malloc(size uintptr) unsafe.Pointer { 27 | return C.rte_malloc_socket(nil, C.size_t(size), C.uint(mem.Align), C.int(mem.Socket)) 28 | } 29 | 30 | // Free implements Allocator. 31 | func (mem *RteAlloc) Free(p unsafe.Pointer) { 32 | C.rte_free(p) 33 | } 34 | 35 | // Realloc implements Allocator. 36 | // 37 | // Note: rte_realloc() may not reside on the same NUMA node. 38 | func (mem *RteAlloc) Realloc(p unsafe.Pointer, size uintptr) unsafe.Pointer { 39 | return C.rte_realloc(p, C.size_t(size), C.uint(mem.Align)) 40 | } 41 | -------------------------------------------------------------------------------- /table/array.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | 8 | import ( 9 | "unsafe" 10 | 11 | "github.com/yerden/go-dpdk/common" 12 | ) 13 | 14 | // ArrayParams is the Array table parameters. 15 | type ArrayParams struct { 16 | // Number of array entries. Has to be a power of two. 17 | Entries uint32 18 | 19 | // Byte offset within input packet meta-data where lookup key 20 | // (i.e. the array entry index) is located. 21 | Offset uint32 22 | } 23 | 24 | // Transform implements common.Transformer interface. 25 | func (p *ArrayParams) Transform(alloc common.Allocator) (unsafe.Pointer, func(unsafe.Pointer)) { 26 | return common.TransformPOD(alloc, &C.struct_rte_table_array_params{ 27 | n_entries: C.uint32_t(p.Entries), 28 | offset: C.uint32_t(p.Offset), 29 | }) 30 | } 31 | 32 | // Ops implements Params interface. 33 | func (p *ArrayParams) Ops() *Ops { 34 | return (*Ops)(&C.rte_table_array_ops) 35 | } 36 | 37 | // ArrayKey is the key for adding/deleting key from table. 38 | type ArrayKey struct { 39 | Pos uint32 40 | } 41 | -------------------------------------------------------------------------------- /ethdev/flow/action.go: -------------------------------------------------------------------------------- 1 | package flow 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | */ 8 | import "C" 9 | import "unsafe" 10 | 11 | // ActionType is the rte_flow_action type. 12 | type ActionType uint32 13 | 14 | // Reload implements Action interface. 15 | func (t ActionType) Reload() {} 16 | 17 | // Pointer implements Action interface. 18 | func (t ActionType) Pointer() unsafe.Pointer { return nil } 19 | 20 | // Type implements Action interface. 21 | func (t ActionType) Type() ActionType { return t } 22 | 23 | // Action is the definition of a single action. 24 | // 25 | // A list of actions is terminated by a END action. 26 | // 27 | // For simple actions without a configuration object, conf remains 28 | // NULL. 29 | type Action interface { 30 | // Pointer returns a valid C pointer to underlying struct. 31 | Pointer() unsafe.Pointer 32 | 33 | // Reload is used to apply changes so that the underlying struct 34 | // reflects the up-to-date configuration. 35 | Reload() 36 | 37 | // Type returns implemented rte_flow_action_* struct. 38 | Type() ActionType 39 | } 40 | -------------------------------------------------------------------------------- /ring/ring.h: -------------------------------------------------------------------------------- 1 | #ifndef _RING_H_ 2 | #define _RING_H_ 3 | 4 | struct compound_int { 5 | unsigned int n; 6 | unsigned int rc; 7 | }; 8 | 9 | typedef void * ptr_t; 10 | 11 | #define GO_RING_FUNC(func) \ 12 | static struct compound_int func(struct rte_ring *r, \ 13 | uintptr_t objs, unsigned int n) { \ 14 | struct compound_int out; \ 15 | void **obj_table = (typeof(obj_table))objs; \ 16 | out.rc = rte_ring_ ## func(r, obj_table, n, &out.n); \ 17 | return out; \ 18 | } 19 | 20 | // wrap dequeue API 21 | GO_RING_FUNC(mc_dequeue_burst) 22 | GO_RING_FUNC(mc_dequeue_bulk) 23 | GO_RING_FUNC(sc_dequeue_burst) 24 | GO_RING_FUNC(sc_dequeue_bulk) 25 | GO_RING_FUNC(dequeue_burst) 26 | GO_RING_FUNC(dequeue_bulk) 27 | 28 | // wrap enqueue API 29 | GO_RING_FUNC(mp_enqueue_burst) 30 | GO_RING_FUNC(mp_enqueue_bulk) 31 | GO_RING_FUNC(sp_enqueue_burst) 32 | GO_RING_FUNC(sp_enqueue_bulk) 33 | GO_RING_FUNC(enqueue_burst) 34 | GO_RING_FUNC(enqueue_bulk) 35 | 36 | #endif /* _RING_H_ */ 37 | 38 | -------------------------------------------------------------------------------- /common/assert.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "runtime" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | // Assert allows to perform one-lined tests and, optionally, print 12 | // some diagnostic info if the test failed. 13 | // 14 | // If fail is true, test failure will cause panic and cease test 15 | // execution. 16 | func Assert(t testing.TB, fail bool) func(bool, ...interface{}) { 17 | return func(expected bool, v ...interface{}) { 18 | if !expected { 19 | t.Helper() 20 | if t.Error(v...); fail { 21 | t.FailNow() 22 | } 23 | } 24 | } 25 | } 26 | 27 | // FprintStackFrames prints calling stack of the error into specified 28 | // writer. Program counters are specified in pc. 29 | func FprintStackFrames(w io.Writer, pc []uintptr) { 30 | frames := runtime.CallersFrames(pc) 31 | for { 32 | frame, more := frames.Next() 33 | if !more { 34 | break 35 | } 36 | // skipping everything from runtime package. 37 | if strings.HasPrefix(frame.Function, "runtime.") { 38 | continue 39 | } 40 | fmt.Fprintf(w, "... at %s:%d, %s\n", frame.File, frame.Line, 41 | frame.Function) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /common/endian.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "encoding/binary" 5 | "unsafe" 6 | ) 7 | 8 | // CopyFromBytes copies no more than max bytes from src to an area 9 | // pointed to by dst. 10 | func CopyFromBytes(dst unsafe.Pointer, src []byte, max int) int { 11 | return copy(MakeSlice(dst, max), src) 12 | } 13 | 14 | // CopyToBytes copies no more than max bytes from an area pointed to 15 | // by src to dst. 16 | func CopyToBytes(dst []byte, src unsafe.Pointer, max int) int { 17 | return copy(dst, MakeSlice(src, max)) 18 | } 19 | 20 | // PutUint16 stores uint16 value into an area pointed to dst. 21 | func PutUint16(b binary.ByteOrder, dst unsafe.Pointer, d uint16) { 22 | buf := MakeSlice(dst, 2) 23 | b.PutUint16(buf, d) 24 | } 25 | 26 | // PutUint32 stores uint32 value into an area pointed to dst. 27 | func PutUint32(b binary.ByteOrder, dst unsafe.Pointer, d uint32) { 28 | buf := MakeSlice(dst, 4) 29 | b.PutUint32(buf, d) 30 | } 31 | 32 | // PutUint64 stores uint64 value into an area pointed to dst. 33 | func PutUint64(b binary.ByteOrder, dst unsafe.Pointer, d uint64) { 34 | buf := MakeSlice(dst, 8) 35 | b.PutUint64(buf, d) 36 | } 37 | -------------------------------------------------------------------------------- /lcore/lcore_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package lcore 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "syscall" 9 | ) 10 | 11 | var ( 12 | lcore2node []int 13 | ) 14 | 15 | // linux implementation, should return maximum lcore id + 1. 16 | func loadCPUMap() int { 17 | nodeMap := make(map[uint]int) 18 | maxLcoreID := 0 19 | for n := 0; ; n++ { 20 | path := fmt.Sprintf("/sys/devices/system/node/node%d/cpumap", n) 21 | f, err := os.Open(path) 22 | if err != nil { 23 | if e, ok := err.(*os.PathError); !ok || e.Err != syscall.ENOENT { 24 | panic(e) 25 | } 26 | break 27 | } 28 | defer f.Close() 29 | 30 | cores, err := readCPUHexMap(f) 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | for _, c := range cores { 36 | if nodeMap[uint(c)] = n; c > maxLcoreID { 37 | maxLcoreID = c 38 | } 39 | } 40 | } 41 | 42 | lcore2node = make([]int, maxLcoreID+1) 43 | for lcore, node := range nodeMap { 44 | lcore2node[lcore] = node 45 | } 46 | 47 | return len(lcore2node) 48 | } 49 | 50 | func getNumaNode(id uint) int { 51 | if id < uint(len(lcore2node)) { 52 | return lcore2node[id] 53 | } 54 | return NumaNodeAny 55 | } 56 | -------------------------------------------------------------------------------- /mempool/cache.go: -------------------------------------------------------------------------------- 1 | package mempool 2 | 3 | /* 4 | #include 5 | #include 6 | */ 7 | import "C" 8 | 9 | import ( 10 | // "reflect" 11 | // "unsafe" 12 | ) 13 | 14 | // Cache is a structure that stores a per-core object cache. This can 15 | // be used by non-EAL threads to enable caching when they interact 16 | // with a mempool. 17 | type Cache C.struct_rte_mempool_cache 18 | 19 | // CreateCache creates a user-owned mempool cache. 20 | // 21 | // You may specify OptCacheSize and OptSocket options only. 22 | func CreateCache(opts ...Option) (*Cache, error) { 23 | conf := &mpConf{socket: C.SOCKET_ID_ANY} 24 | for _, o := range opts { 25 | o.f(conf) 26 | } 27 | 28 | mpc := C.rte_mempool_cache_create(conf.cacheSize, conf.socket) 29 | if mpc == nil { 30 | return nil, err() 31 | } 32 | return (*Cache)(mpc), nil 33 | } 34 | 35 | // Flush a user-owned mempool cache to the specified mempool. 36 | func (mpc *Cache) Flush(mp *Mempool) { 37 | C.rte_mempool_cache_flush((*C.struct_rte_mempool_cache)(mpc), (*C.struct_rte_mempool)(mp)) 38 | } 39 | 40 | // Free a user-owned mempool cache. 41 | func (mpc *Cache) Free() { 42 | C.rte_mempool_cache_free((*C.struct_rte_mempool_cache)(mpc)) 43 | } 44 | -------------------------------------------------------------------------------- /port/ethdev_test.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/yerden/go-dpdk/common" 7 | "github.com/yerden/go-dpdk/eal" 8 | ) 9 | 10 | func TestPortEthdevRx(t *testing.T) { 11 | assert := common.Assert(t, true) 12 | 13 | // Initialize EAL on all cores 14 | eal.InitOnceSafe("test", 4) 15 | 16 | err := eal.ExecOnMain(func(*eal.LcoreCtx) { 17 | params := &EthdevRx{ 18 | PortID: 0, 19 | QueueID: 0, 20 | } 21 | 22 | ops := params.InOps() 23 | assert(ops != nil) 24 | 25 | in := CreateIn(-1, params) 26 | assert(in != nil) 27 | 28 | assert(nil == in.Free(ops)) 29 | }) 30 | assert(err == nil, err) 31 | } 32 | 33 | func TestPortEthdevTx(t *testing.T) { 34 | assert := common.Assert(t, true) 35 | 36 | // Initialize EAL on all cores 37 | eal.InitOnceSafe("test", 4) 38 | 39 | err := eal.ExecOnMain(func(*eal.LcoreCtx) { 40 | params := &EthdevTx{ 41 | PortID: 0, 42 | QueueID: 0, 43 | TxBurstSize: 32, 44 | NoDrop: true, 45 | Retries: 4, 46 | } 47 | 48 | ops := params.OutOps() 49 | assert(ops != nil) 50 | 51 | out := CreateOut(-1, params) 52 | assert(out != nil) 53 | 54 | assert(nil == out.Free(ops)) 55 | }) 56 | assert(err == nil, err) 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go bindings for DPDK framework. 2 | [![Documentation](https://godoc.org/github.com/yerden/go-dpdk?status.svg)](http://godoc.org/github.com/yerden/go-dpdk) [![Build Status](https://github.com/yerden/go-dpdk/actions/workflows/unit.yml/badge.svg)](https://github.com/yerden/go-dpdk/actions/workflows/unit.yml) [![codecov](https://codecov.io/gh/yerden/go-dpdk/branch/master/graph/badge.svg?token=1XW04KL02S)](https://codecov.io/gh/yerden/go-dpdk) 3 | 4 | # Building apps 5 | 6 | Starting from DPDK 21.05, `pkg-config` becomes the only official way to build DPDK apps. Because of it `go-dpdk` uses `#cgo pkg-config` directive to link against your DPDK distribution. 7 | 8 | Go compiler may fail to accept some C compiler flags. You can fix it by submitting those flags to environment: 9 | ``` 10 | export CGO_CFLAGS_ALLOW="-mrtm" 11 | export CGO_LDFLAGS_ALLOW="-Wl,--(?:no-)?whole-archive" 12 | ``` 13 | 14 | # Caveats 15 | * Only dynamic linking is viable at this point. 16 | * If you isolate CPU cores with `isolcpus` kernel parameter then `GOMAXPROCS` should be manually specified to reflect the actual number of logical cores in CPU mask. E.g. if `isolcpus=12-95` on a 96-core machine then default value for `GOMAXPROCS` would be 12 but it should be at least 84. 17 | -------------------------------------------------------------------------------- /util/make_hash_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "hash/crc32" 6 | "testing" 7 | ) 8 | 9 | func makeGoHash32Func(complement bool) func([]byte, uint32) uint32 { 10 | tab := crc32.MakeTable(crc32.Castagnoli) 11 | if complement { 12 | return func(data []byte, acc uint32) uint32 { 13 | return ^crc32.Update(^acc, tab, data) 14 | } 15 | } 16 | 17 | { 18 | return func(data []byte, acc uint32) uint32 { 19 | return crc32.Update(acc, tab, data) 20 | } 21 | } 22 | } 23 | 24 | func onesComplement(fn func([]byte, uint32) uint32) func([]byte, uint32) uint32 { 25 | return func(data []byte, acc uint32) uint32 { 26 | return ^fn(data, ^acc) 27 | } 28 | } 29 | 30 | func BenchmarkComplementFalse(b *testing.B) { 31 | h := &Hash32{ 32 | OnesComplement: false, 33 | Accum: onesComplement(makeGoHash32Func(false)), 34 | } 35 | 36 | data := bytes.Repeat([]byte{0xab}, 31) 37 | 38 | for i := 0; i < b.N; i++ { 39 | h.Write(data) 40 | } 41 | } 42 | 43 | func BenchmarkComplementTrue(b *testing.B) { 44 | h := &Hash32{ 45 | OnesComplement: true, 46 | Accum: makeGoHash32Func(false), 47 | } 48 | 49 | data := bytes.Repeat([]byte{0xab}, 31) 50 | 51 | for i := 0; i < b.N; i++ { 52 | h.Write(data) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ethdev/flow/utils.go: -------------------------------------------------------------------------------- 1 | package flow 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | */ 8 | import "C" 9 | import ( 10 | "encoding/binary" 11 | "reflect" 12 | "unsafe" 13 | ) 14 | 15 | func off(p unsafe.Pointer, d uintptr) unsafe.Pointer { 16 | return unsafe.Pointer(uintptr(p) + d) 17 | } 18 | 19 | func beU16(n uint16, p unsafe.Pointer) { 20 | var d []byte 21 | sh := (*reflect.SliceHeader)(unsafe.Pointer(&d)) 22 | sh.Data = uintptr(p) 23 | sh.Len = 2 24 | sh.Cap = sh.Len 25 | binary.BigEndian.PutUint16(d, n) 26 | } 27 | 28 | func beU32(n uint32, p unsafe.Pointer) { 29 | var d []byte 30 | sh := (*reflect.SliceHeader)(unsafe.Pointer(&d)) 31 | sh.Data = uintptr(p) 32 | sh.Len = 4 33 | sh.Cap = sh.Len 34 | binary.BigEndian.PutUint32(d, n) 35 | } 36 | 37 | type cPointer struct { 38 | cptr unsafe.Pointer 39 | } 40 | 41 | func (p *cPointer) free() { 42 | C.free(p.cptr) 43 | } 44 | 45 | // Pointer is the blanket implementation of ItemStruct/Action. 46 | func (p *cPointer) Pointer() unsafe.Pointer { 47 | return p.cptr 48 | } 49 | 50 | func (p *cPointer) createOrRet(n C.ulong) unsafe.Pointer { 51 | if p.cptr == nil { 52 | p.cptr = C.malloc(n) 53 | C.memset(p.cptr, 0, n) 54 | } 55 | 56 | return p.cptr 57 | } 58 | -------------------------------------------------------------------------------- /ring/ring_telemetry.go: -------------------------------------------------------------------------------- 1 | package ring 2 | 3 | /* 4 | #include "ring_telemetry.h" 5 | */ 6 | import "C" 7 | import ( 8 | "unsafe" 9 | 10 | "github.com/yerden/go-dpdk/common" 11 | ) 12 | 13 | func telemetryRegisterCmd(path string, handler C.telemetry_cb, help string) C.int { 14 | sPath := C.CString(path) 15 | defer C.free(unsafe.Pointer(sPath)) 16 | sHelp := C.CString(help) 17 | defer C.free(unsafe.Pointer(sHelp)) 18 | return C.rte_telemetry_register_cmd(sPath, (C.telemetry_cb)(handler), sHelp) 19 | } 20 | 21 | type cmdDesc struct { 22 | cmd string 23 | help string 24 | cb C.telemetry_cb 25 | } 26 | 27 | // TelemetryInit initializes telemetry callbacks for rings. 28 | // Specify prefix for cmd path to avoid conflicts in the future. 29 | // "/ring" is the good candidate for a prefix. 30 | func TelemetryInit(prefix string) { 31 | desc := []cmdDesc{ 32 | { 33 | cmd: prefix + "/list", 34 | cb: C.ring_list_cb, 35 | help: "Show list of rings. Takes no parameters.", 36 | }, { 37 | cmd: prefix + "/info", 38 | cb: C.ring_info_cb, 39 | help: "Show info on the ring. Param: ring name.", 40 | }, 41 | } 42 | for _, d := range desc { 43 | if rc := telemetryRegisterCmd(d.cmd, d.cb, d.help); rc != 0 { 44 | panic(common.IntErr(int64(rc))) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.github/workflows/unit.yml: -------------------------------------------------------------------------------- 1 | name: unit-testing 2 | 3 | on: 4 | push: 5 | pull_request: 6 | types: [opened, reopened, synchronize, ready_for_review] 7 | 8 | env: 9 | TERM: "xterm" 10 | FORCE_COLOR: "1" 11 | CGO_CFLAGS_ALLOW: ".*" 12 | CGO_LDFLAGS_ALLOW: ".*" 13 | 14 | jobs: 15 | build: 16 | strategy: 17 | matrix: 18 | env_image: 19 | - nedrey/dpdk-rockylinux8:v21.11-go1.19-snf 20 | - nedrey/dpdk-rockylinux8:v22.11.3-go1.21.3-snf 21 | - nedrey/dpdk-rockylinux8:v23.11-go1.21.4-snf 22 | runs-on: ubuntu-latest 23 | container: ${{ matrix.env_image }} 24 | steps: 25 | - 26 | name: Checkout 27 | uses: actions/checkout@v3 28 | - 29 | name: Unit Tests 30 | run: | 31 | go test -coverprofile=coverage.out -covermode=atomic ./... 32 | - 33 | name: Generate Coverage Report 34 | run: | 35 | gocover-cobertura < coverage.out > coverage.xml 36 | - 37 | name: Save Coverage Report 38 | uses: actions/upload-artifact@v3 39 | with: 40 | name: coverage-report 41 | path: ./coverage.* 42 | retention-days: 14 43 | - 44 | name: Upload Coverage Report 45 | uses: codecov/codecov-action@v3 46 | -------------------------------------------------------------------------------- /port/source_sink_test.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/yerden/go-dpdk/common" 7 | "github.com/yerden/go-dpdk/eal" 8 | "github.com/yerden/go-dpdk/mempool" 9 | ) 10 | 11 | func TestPortSource(t *testing.T) { 12 | assert := common.Assert(t, true) 13 | 14 | // Initialize EAL on all cores 15 | eal.InitOnceSafe("test", 4) 16 | 17 | err := eal.ExecOnMain(func(*eal.LcoreCtx) { 18 | mp, err := mempool.CreateMbufPool( 19 | "hello", 20 | 1024, 21 | 2048, 22 | ) 23 | assert(err == nil, err) 24 | defer mp.Free() 25 | 26 | params := &Source{ 27 | Mempool: mp, 28 | } 29 | 30 | ops := params.InOps() 31 | assert(ops != nil) 32 | 33 | in := CreateIn(-1, params) 34 | assert(in != nil) 35 | 36 | assert(nil == in.Free(ops)) 37 | }) 38 | assert(err == nil, err) 39 | } 40 | 41 | func TestPortSink(t *testing.T) { 42 | assert := common.Assert(t, true) 43 | 44 | // Initialize EAL on all cores 45 | eal.InitOnceSafe("test", 4) 46 | 47 | err := eal.ExecOnMain(func(*eal.LcoreCtx) { 48 | params := &Sink{ 49 | Filename: "/dev/null", 50 | } 51 | 52 | ops := params.OutOps() 53 | assert(ops != nil) 54 | 55 | out := CreateOut(-1, params) 56 | assert(out != nil) 57 | 58 | assert(nil == out.Free(ops)) 59 | }) 60 | assert(err == nil, err) 61 | } 62 | -------------------------------------------------------------------------------- /app/ring/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "sync" 7 | "unsafe" 8 | 9 | "github.com/yerden/go-dpdk/eal" 10 | "github.com/yerden/go-dpdk/ring" 11 | ) 12 | 13 | func main() { 14 | if _, err := eal.Init(os.Args); err != nil { 15 | log.Fatalln("EAL init failed:", err) 16 | } 17 | defer eal.Cleanup() 18 | 19 | // create a ring 20 | var r *ring.Ring 21 | var err error 22 | e := eal.ExecOnMain(func(*eal.LcoreCtx) { 23 | r, err = ring.Create("test_ring", 1024) 24 | }) 25 | if e != nil { 26 | panic(e) 27 | } else if err != nil { 28 | panic(err) 29 | } 30 | defer eal.ExecOnMain(func(*eal.LcoreCtx) { r.Free() }) 31 | 32 | // Pick first slave, panic if none. 33 | slave := eal.LcoresWorker()[0] 34 | 35 | // start sending and receiving messages 36 | var wg sync.WaitGroup 37 | wg.Add(1) 38 | n := 1000000 // 1M messages 39 | go func() { 40 | defer wg.Done() 41 | eal.ExecOnMain(func(ctx *eal.LcoreCtx) { 42 | for i := 0; i < n; { 43 | if r.Enqueue(unsafe.Pointer(r)) { 44 | i++ 45 | } 46 | } 47 | log.Println("sent", n, "messages") 48 | }) 49 | }() 50 | wg.Add(1) 51 | go func() { 52 | defer wg.Done() 53 | eal.ExecOnLcore(slave, func(ctx *eal.LcoreCtx) { 54 | for i := 0; i < n; { 55 | ptr, ok := r.Dequeue() 56 | ok = ok && unsafe.Pointer(r) == ptr 57 | if ok { 58 | i++ 59 | } 60 | } 61 | log.Println("received", n, "messages") 62 | }) 63 | }() 64 | wg.Wait() 65 | } 66 | -------------------------------------------------------------------------------- /common/pointers.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "reflect" 5 | "unsafe" 6 | ) 7 | 8 | // MakeSlice returns byte slice specified by pointer and of len max. 9 | func MakeSlice(buf unsafe.Pointer, max int) []byte { 10 | var dst []byte 11 | sh := (*reflect.SliceHeader)(unsafe.Pointer(&dst)) 12 | sh.Data = uintptr(buf) 13 | sh.Len = max 14 | sh.Cap = max 15 | return dst 16 | } 17 | 18 | // CStruct is a GO structure representation of a C array. 19 | // CStruct has a certain length, don't try to extend it. 20 | type CStruct struct { 21 | // Ptr is a pointer to the beginning of the C array. 22 | Ptr unsafe.Pointer 23 | // Len is the size of memory area pointed by Ptr. 24 | Len int 25 | } 26 | 27 | // Init initializes CStruct instance with specified pointer ptr and 28 | // length len. 29 | func (cs *CStruct) Init(ptr unsafe.Pointer, len int) { 30 | cs.Ptr = ptr 31 | cs.Len = len 32 | } 33 | 34 | // Bytes converts C array into slice of bytes backed by this array. 35 | // Returned slice cannot be extended or it will be reallocated into Go 36 | // memory. 37 | func (cs *CStruct) Bytes() (dst []byte) { 38 | sh := (*reflect.SliceHeader)(unsafe.Pointer(&dst)) 39 | sh.Data = uintptr(cs.Ptr) 40 | sh.Len = cs.Len 41 | sh.Cap = cs.Len 42 | return 43 | } 44 | 45 | // Memset initializes memory pointed by p and with length n. 46 | func Memset(p unsafe.Pointer, init byte, n uintptr) { 47 | b := unsafe.Slice((*byte)(p), n) 48 | for i := range b { 49 | b[i] = init 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /pipeline/pipeline_loop.h: -------------------------------------------------------------------------------- 1 | #ifndef _PIPELINE_LOOP_H_ 2 | #define _PIPELINE_LOOP_H_ 3 | 4 | #include 5 | #include 6 | 7 | /** 8 | * Test if the pipeline should be stopped. 9 | * 10 | * @param params 11 | * Handle to private data. 12 | * 13 | * @param p 14 | * Pipeline instance. 15 | * 16 | * @return 17 | * Return 0 if the pipeline resumes. Return >0 if the pipeline 18 | * stops. 19 | */ 20 | typedef int (*pipeline_op_ctrl)( 21 | void *params, 22 | struct rte_pipeline *p); 23 | 24 | // Pipeline loop operations. 25 | struct pipeline_ops { 26 | pipeline_op_ctrl f_ctrl; 27 | }; 28 | 29 | struct lcore_arg { 30 | struct rte_pipeline *p; 31 | struct pipeline_ops ops; 32 | void *ops_arg; 33 | uint32_t flush; 34 | }; 35 | 36 | static int run_pipeline_loop(void *arg) 37 | { 38 | struct lcore_arg *ctx = arg; 39 | uint32_t n; 40 | int rc; 41 | struct pipeline_ops *ops = &ctx->ops; 42 | struct rte_pipeline *p = ctx->p; 43 | 44 | for (n = 0; ; n++) { 45 | rte_pipeline_run(p); 46 | 47 | if (!ctx->flush || ((n & (ctx->flush - 1)) != 0)) 48 | continue; 49 | 50 | rte_pipeline_flush(p); 51 | 52 | if (NULL == ops->f_ctrl) 53 | continue; 54 | 55 | if ((rc = ops->f_ctrl(ctx->ops_arg, p)) > 0) { 56 | /* pipeline is signalled to stop */ 57 | return 0; 58 | } 59 | 60 | if (rc < 0) { 61 | /* pipeline control error */ 62 | break; 63 | } 64 | } 65 | 66 | return rc; 67 | } 68 | 69 | #endif /* _PIPELINE_LOOP_H_ */ 70 | -------------------------------------------------------------------------------- /util/mbuf_array.h: -------------------------------------------------------------------------------- 1 | #ifndef _RX_BUFFER_H_ 2 | #define _RX_BUFFER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define RX_BUFFER_SIZE(sz) (sizeof(struct mbuf_array) + ((sz)-1) * sizeof(struct rte_mbuf *)) 10 | 11 | #ifndef MBUF_ARRAY_USER_SIZE 12 | #define MBUF_ARRAY_USER_SIZE 64 13 | #endif 14 | 15 | struct mbuf_array { 16 | // number of mbufs allocated, occupied length and position 17 | uint16_t size, length, n; 18 | 19 | uint16_t padding[1]; 20 | 21 | // custom data 22 | uint8_t opaque[MBUF_ARRAY_USER_SIZE]; 23 | 24 | // must preallocate one pointer for Go compiler to be happy 25 | // and able to take pointer of this array. 26 | struct rte_mbuf *pkts[1]; 27 | }; 28 | 29 | struct ethdev_data { 30 | uint16_t pid, qid; 31 | }; 32 | 33 | static int new_mbuf_array(int socket, uint16_t count, struct mbuf_array **pbuf) 34 | { 35 | size_t size = RX_BUFFER_SIZE(count); 36 | struct mbuf_array *buf = (typeof(buf)) rte_zmalloc_socket("mbuf_array", size, 0, socket); 37 | if (buf == NULL) { 38 | return -ENOMEM; 39 | } 40 | 41 | buf->size = count; 42 | *pbuf = buf; 43 | return 0; 44 | } 45 | 46 | static uint16_t mbuf_array_ethdev_reload(struct mbuf_array *buf) 47 | { 48 | uint16_t n; 49 | struct ethdev_data *data = (typeof(data)) buf->opaque; 50 | 51 | for (n = 0; n < buf->length; n++) { 52 | rte_pktmbuf_free(buf->pkts[n]); 53 | } 54 | 55 | buf->length = rte_eth_rx_burst(data->pid, data->qid, buf->pkts, buf->size); 56 | buf->n = 0; 57 | return buf->length; 58 | } 59 | 60 | #endif /* _RX_BUFFER_H_ */ 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, yerden 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /mempool/mbuf.go: -------------------------------------------------------------------------------- 1 | package mempool 2 | 3 | /* 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | static int is_priv_size_aligned(uint16_t priv_size) { 10 | return RTE_ALIGN(priv_size, RTE_MBUF_PRIV_ALIGN) == priv_size; 11 | } 12 | */ 13 | import "C" 14 | 15 | import ( 16 | "unsafe" 17 | ) 18 | 19 | // CreateMbufPool creates mempool of mbufs. See CreateEmpty options 20 | // for a list of options. Only differences are described below. 21 | // 22 | // dataRoomSize specifies the maximum size of data buffer in each 23 | // mbuf, including RTE_PKTMBUF_HEADROOM. 24 | // 25 | // OptPrivateDataSize semantics is different here. It specifies the 26 | // size of private application data between the rte_mbuf structure and 27 | // the data buffer. This value must be aligned to 28 | // RTE_MBUF_PRIV_ALIGN. 29 | // 30 | // The created mempool is already populated and its objects are 31 | // initialized with rte_pktmbuf_init. 32 | func CreateMbufPool(name string, n uint32, dataRoomSize uint16, opts ...Option) (*Mempool, error) { 33 | conf := &mpConf{socket: C.SOCKET_ID_ANY} 34 | for i := range opts { 35 | opts[i].f(conf) 36 | } 37 | 38 | cname := C.CString(name) 39 | defer C.free(unsafe.Pointer(cname)) 40 | var cOps *C.char 41 | if conf.opsName != nil { 42 | cOps = C.CString(*conf.opsName) 43 | defer C.free(unsafe.Pointer(cOps)) 44 | } 45 | mp := (*Mempool)(C.rte_pktmbuf_pool_create_by_ops(cname, C.uint(n), 46 | conf.cacheSize, C.ushort(conf.privDataSize), C.uint16_t(dataRoomSize), 47 | conf.socket, cOps)) 48 | 49 | if mp == nil { 50 | return nil, err() 51 | } 52 | 53 | return mp, nil 54 | } 55 | -------------------------------------------------------------------------------- /port/fd_test.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/yerden/go-dpdk/common" 8 | "github.com/yerden/go-dpdk/eal" 9 | "github.com/yerden/go-dpdk/mempool" 10 | ) 11 | 12 | func TestPortFdRx(t *testing.T) { 13 | assert := common.Assert(t, true) 14 | 15 | // Initialize EAL on all cores 16 | eal.InitOnceSafe("test", 4) 17 | 18 | f, err := os.CreateTemp("", "testfile") 19 | assert(err == nil) 20 | defer os.Remove(f.Name()) 21 | defer f.Close() 22 | 23 | err = eal.ExecOnMain(func(*eal.LcoreCtx) { 24 | mp, err := mempool.CreateMbufPool( 25 | "hello", 26 | 1024, 27 | 2048, 28 | ) 29 | assert(err == nil, err) 30 | defer mp.Free() 31 | 32 | params := &FdRx{ 33 | Mempool: mp, 34 | Fd: f.Fd(), 35 | MTU: 1 << 12, 36 | } 37 | 38 | ops := params.InOps() 39 | assert(ops != nil) 40 | 41 | in := CreateIn(-1, params) 42 | assert(in != nil) 43 | 44 | assert(nil == in.Free(ops)) 45 | }) 46 | assert(err == nil, err) 47 | } 48 | 49 | func TestPortFdOut(t *testing.T) { 50 | assert := common.Assert(t, true) 51 | 52 | // Initialize EAL on all cores 53 | eal.InitOnceSafe("test", 4) 54 | 55 | f, err := os.Open("/dev/null") 56 | assert(err == nil) 57 | defer f.Close() 58 | 59 | err = eal.ExecOnMain(func(*eal.LcoreCtx) { 60 | params := &FdTx{ 61 | Fd: f.Fd(), 62 | BurstSize: 32, 63 | NoDrop: true, 64 | Retries: 32, 65 | } 66 | 67 | ops := params.OutOps() 68 | assert(ops != nil) 69 | 70 | out := CreateOut(-1, params) 71 | assert(out != nil) 72 | 73 | assert(nil == out.Free(ops)) 74 | }) 75 | assert(err == nil, err) 76 | } 77 | -------------------------------------------------------------------------------- /ethdev/flow/item_udp.go: -------------------------------------------------------------------------------- 1 | package flow 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | 8 | static const struct rte_flow_item_udp *get_item_udp_mask() { 9 | return &rte_flow_item_udp_mask; 10 | } 11 | 12 | */ 13 | import "C" 14 | import ( 15 | "runtime" 16 | "unsafe" 17 | ) 18 | 19 | // UDPHeader represents UDP header format. 20 | type UDPHeader struct { 21 | SrcPort uint16 /* UDP source port. */ 22 | DstPort uint16 /* UDP destination port. */ 23 | Length uint16 /* UDP datagram length */ 24 | Checksum uint16 /* UDP datagram checksum */ 25 | } 26 | 27 | // ItemUDP matches an UDP header. 28 | type ItemUDP struct { 29 | cPointer 30 | 31 | Header UDPHeader 32 | } 33 | 34 | var _ ItemStruct = (*ItemUDP)(nil) 35 | 36 | // Reload implements ItemStruct interface. 37 | func (item *ItemUDP) Reload() { 38 | cptr := (*C.struct_rte_flow_item_udp)(item.createOrRet(C.sizeof_struct_rte_flow_item_udp)) 39 | cvtUDPHeader(&cptr.hdr, &item.Header) 40 | runtime.SetFinalizer(item, (*ItemUDP).free) 41 | } 42 | 43 | func cvtUDPHeader(dst *C.struct_rte_udp_hdr, src *UDPHeader) { 44 | beU16(uint16(src.SrcPort), unsafe.Pointer(&dst.src_port)) 45 | beU16(uint16(src.DstPort), unsafe.Pointer(&dst.dst_port)) 46 | beU16(src.Length, unsafe.Pointer(&dst.dgram_len)) 47 | beU16(src.Checksum, unsafe.Pointer(&dst.dgram_cksum)) 48 | } 49 | 50 | // Type implements ItemStruct interface. 51 | func (item *ItemUDP) Type() ItemType { 52 | return ItemTypeUDP 53 | } 54 | 55 | // Mask implements ItemStruct interface. 56 | func (item *ItemUDP) Mask() unsafe.Pointer { 57 | return unsafe.Pointer(C.get_item_udp_mask()) 58 | } 59 | -------------------------------------------------------------------------------- /lcore/lcore.go: -------------------------------------------------------------------------------- 1 | package lcore 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "io" 7 | ) 8 | 9 | // Default NUMA node value. 10 | const ( 11 | NumaNodeAny = -1 12 | ) 13 | 14 | // Maximum number of logical CPU cores on the system. 15 | var ( 16 | MaxLcoreID = loadCPUMap() 17 | ) 18 | 19 | var ( 20 | hexMap = map[byte]int{ 21 | '0': 0x0, '1': 0x1, '2': 0x2, '4': 0x4, 22 | '8': 0x8, '3': 0x3, '5': 0x5, '6': 0x6, 23 | '9': 0x9, 'a': 0xa, 'c': 0xc, '7': 0x7, 24 | 'b': 0xb, 'd': 0xd, 'e': 0xe, 'f': 0xf, 25 | } 26 | ) 27 | 28 | var ( 29 | errInvalidMap = errors.New("invalid cpu map") 30 | ) 31 | 32 | // read cpu map in hex format as a first line from b and return lcore 33 | // ids and error if encountered. 34 | func readCPUHexMap(b io.Reader) ([]int, error) { 35 | cores := []int{} 36 | scanner := bufio.NewScanner(b) 37 | scanner.Split(bufio.ScanWords) 38 | scanner.Scan() 39 | if err := scanner.Err(); err != nil { 40 | return nil, err 41 | } 42 | 43 | cpumap := scanner.Bytes() 44 | for i := range cpumap { 45 | c := cpumap[len(cpumap)-i-1] 46 | n, ok := hexMap[c] 47 | if !ok { 48 | return nil, errInvalidMap 49 | } 50 | if n&0x1 != 0 { 51 | cores = append(cores, 4*i) 52 | } 53 | if n&0x2 != 0 { 54 | cores = append(cores, 4*i+1) 55 | } 56 | if n&0x4 != 0 { 57 | cores = append(cores, 4*i+2) 58 | } 59 | if n&0x8 != 0 { 60 | cores = append(cores, 4*i+3) 61 | } 62 | } 63 | 64 | return cores, nil 65 | } 66 | 67 | // NumaNode returns id of the NUMA node for specified logical CPU 68 | // core id. if core id is invalid, NumaNodeAny is returned. 69 | func NumaNode(id uint) int { 70 | return getNumaNode(id) 71 | } 72 | -------------------------------------------------------------------------------- /hash/crc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package hash wraps RTE hash libraries. 3 | 4 | Please refer to DPDK Programmer's Guide for reference and caveats. 5 | */ 6 | package hash 7 | 8 | /* 9 | #include 10 | #include 11 | */ 12 | import "C" 13 | import ( 14 | "hash" 15 | "reflect" 16 | "runtime" 17 | "unsafe" 18 | 19 | "github.com/yerden/go-dpdk/util" 20 | ) 21 | 22 | // CrcSetAlg allow or disallow use of SSE4.2 instrinsics for CRC32 23 | // hash calculation. Specify OR of declared Crc32* flags. 24 | func CrcSetAlg(alg uint8) { 25 | C.rte_hash_crc_set_alg(C.uint8_t(alg)) 26 | } 27 | 28 | // CrcUpdate calculate CRC32 hash on user-supplied byte array. 29 | func CrcUpdate(data []byte, acc uint32) uint32 { 30 | sh := (*reflect.SliceHeader)(unsafe.Pointer(&data)) 31 | p, length := unsafe.Pointer(sh.Data), C.uint(sh.Len) 32 | //p, length := unsafe.Pointer(&data[0]), C.uint(len(data)) 33 | crc := uint32(C.rte_hash_crc(p, length, C.uint(acc))) 34 | runtime.KeepAlive(data) 35 | return crc 36 | } 37 | 38 | func complementFunc(f func([]byte, uint32) uint32) func([]byte, uint32) uint32 { 39 | return func(data []byte, acc uint32) uint32 { 40 | return ^f(data, ^acc) 41 | } 42 | } 43 | 44 | // NewCrcHash creates new hash.Hash32 implemented on top of CrcUpdate. 45 | // If complement is true then accumulator argument is complemented to 46 | // 1 along with the output. This gives identical output as native Go 47 | // crc32's implementation. 48 | func NewCrcHash(seed uint32, complement bool) hash.Hash32 { 49 | h := &util.Hash32{ 50 | Seed: seed, 51 | Value: seed, 52 | Accum: CrcUpdate, 53 | Block: 8, 54 | } 55 | 56 | if complement { 57 | h.Accum = complementFunc(CrcUpdate) 58 | } 59 | 60 | return h 61 | } 62 | -------------------------------------------------------------------------------- /eal/init_once.go: -------------------------------------------------------------------------------- 1 | package eal 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/hex" 6 | "fmt" 7 | "sync" 8 | ) 9 | 10 | func makeRandomString() string { 11 | b := make([]byte, 16) 12 | if _, err := rand.Read(b); err != nil { 13 | panic(err) 14 | } 15 | 16 | return hex.EncodeToString(b) 17 | } 18 | 19 | // SafeEALArgs returns safe parameters to be used for testing 20 | // purposes. Specify cmdname as the binary name in argv[0] and number 21 | // of lcores. All lcores will be assigned to core 0. 22 | func SafeEALArgs(cmdname string, lcores int) []string { 23 | return []string{ 24 | cmdname, 25 | "--lcores", fmt.Sprintf("(0-%d)@0", lcores-1), 26 | "--file-prefix", "_" + makeRandomString(), 27 | "--vdev", "net_null0", 28 | "-m", "128", 29 | "--no-huge", 30 | "--no-pci", 31 | "--main-lcore", "0", 32 | } 33 | } 34 | 35 | // multiple calls guard 36 | var ealOnce struct { 37 | sync.Mutex 38 | already bool 39 | } 40 | 41 | // InitOnce calls Init guarded with global lock. 42 | // 43 | // If Init returns error it panics. If Init was already called it 44 | // simply returns. 45 | // 46 | // It's mostly intended to use in tests. 47 | func InitOnce(args []string) { 48 | x := &ealOnce 49 | 50 | x.Lock() 51 | defer x.Unlock() 52 | 53 | if !x.already { 54 | if _, err := Init(args); err != nil { 55 | panic(err) 56 | } 57 | } 58 | 59 | x.already = true 60 | } 61 | 62 | // InitOnceSafe calls Init guarded with global lock on arguments 63 | // returned by SafeEALArgs. 64 | // 65 | // If Init returns error it panics. If Init was already called it 66 | // simply returns. 67 | // 68 | // It's mostly intended to use in tests. 69 | func InitOnceSafe(cmdname string, lcores int) { 70 | InitOnce(SafeEALArgs(cmdname, lcores)) 71 | } 72 | -------------------------------------------------------------------------------- /app/test-sniffer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net/http" 7 | "os" 8 | "time" 9 | 10 | "github.com/prometheus/client_golang/prometheus" 11 | "github.com/prometheus/client_golang/prometheus/promhttp" 12 | "github.com/yerden/go-dpdk/eal" 13 | "github.com/yerden/go-dpdk/ethdev" 14 | ) 15 | 16 | var metricsEndpoint = flag.String("metrics", ":10010", "Specify listen address for Prometheus endpoint") 17 | var fcMode FcModeFlag 18 | 19 | func main() { 20 | n, err := eal.Init(os.Args) 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | defer eal.Cleanup() 25 | defer eal.StopLcores() 26 | 27 | os.Args[n], os.Args = os.Args[0], os.Args[n:] 28 | flag.Var(&fcMode, "flowctrl", "Specify Flow Control mode: none (default), rxpause, txpause, full") 29 | 30 | flag.Parse() 31 | reg := prometheus.NewRegistry() 32 | ethdev.RegisterTelemetryLSC("/ethdev/lsc") 33 | app, err := NewApp(reg) 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | retCh := make(chan error, len(app.Work)) 39 | 40 | for lcore, pq := range app.Work { 41 | eal.ExecOnLcoreAsync(lcore, retCh, LcoreFunc(pq, app.QCR)) 42 | } 43 | 44 | // stats report 45 | go func() { 46 | ticker := time.NewTicker(*statsInt) 47 | defer ticker.Stop() 48 | 49 | for range ticker.C { 50 | app.Stats.Report() 51 | } 52 | }() 53 | 54 | go func() { 55 | for err := range retCh { 56 | if err == nil { 57 | continue 58 | } 59 | if e, ok := err.(*eal.ErrLcorePanic); ok { 60 | e.FprintStack(os.Stdout) 61 | } 62 | log.Println(err) 63 | } 64 | }() 65 | 66 | mux := http.NewServeMux() 67 | mux.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{})) 68 | srv := &http.Server{ 69 | Addr: *metricsEndpoint, 70 | Handler: mux, 71 | } 72 | log.Println(srv.ListenAndServe()) 73 | } 74 | -------------------------------------------------------------------------------- /util/make_hash.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "hash" 4 | 5 | var _ hash.Hash32 = (*Hash32)(nil) 6 | 7 | // Hash32 wraps a function Accum to implement hash.Hash32 interface. 8 | type Hash32 struct { 9 | // Seed is the initial hash32 value. 10 | Seed uint32 11 | 12 | // Value is the calculated hash32 value. To reset the Hash32 13 | // state, simply reset this to Seed. 14 | Value uint32 15 | 16 | // Optimal block size. See BlockSize method of hash.Hash32 17 | // interface. 18 | Block int 19 | 20 | // If true then previous hash value is complemented to one before 21 | // specifying to Accum and the output is then complemented to one. 22 | OnesComplement bool 23 | 24 | // Accum is the backend hash implementation. It takes input byte 25 | // array and calculates new hash value based on previous one. 26 | Accum func([]byte, uint32) uint32 27 | } 28 | 29 | // Write implements io.Writer interface needed for hash.Hash32 30 | // implementation. 31 | func (h *Hash32) Write(p []byte) (int, error) { 32 | if h.OnesComplement { 33 | h.Value = ^h.Accum(p, ^h.Value) 34 | } else { 35 | h.Value = h.Accum(p, h.Value) 36 | } 37 | return len(p), nil 38 | } 39 | 40 | // Sum implements hash.Hash32 interface. 41 | func (h *Hash32) Sum(b []byte) []byte { 42 | s := h.Value 43 | return append(b, byte(s>>24), byte(s>>16), byte(s>>8), byte(s)) 44 | } 45 | 46 | // Sum32 implements hash.Hash32 interface. 47 | func (h *Hash32) Sum32() uint32 { 48 | return h.Value 49 | } 50 | 51 | // Reset implements hash.Hash32 interface. 52 | func (h *Hash32) Reset() { 53 | h.Value = h.Seed 54 | } 55 | 56 | // Size implements hash.Hash32 interface. Returns 4. 57 | func (h *Hash32) Size() int { 58 | return 4 59 | } 60 | 61 | // BlockSize implements hash.Hash32 interface. 62 | func (h *Hash32) BlockSize() int { 63 | return h.Block 64 | } 65 | -------------------------------------------------------------------------------- /lcore/thread.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package lcore allows to control execution of user-supplied functions 3 | on specified logical CPU core. 4 | 5 | This may have some advantages such as: reduce context switches, allows 6 | to use non-preemptible algorithms etc. 7 | 8 | Please note that thread function is entirely specified by user. It is 9 | up to user to define how this function would exit. 10 | */ 11 | package lcore 12 | 13 | import ( 14 | // "fmt" 15 | "runtime" 16 | "sync" 17 | 18 | "golang.org/x/sys/unix" 19 | ) 20 | 21 | // Thread is a channel which jobs can be sent to. 22 | type Thread chan<- func() 23 | 24 | // NewLockedThread creates new Thread with user specified channel. 25 | func NewLockedThread(ch chan func()) Thread { 26 | go func() { 27 | runtime.LockOSThread() 28 | for f := range ch { 29 | f() 30 | } 31 | runtime.UnlockOSThread() 32 | }() 33 | 34 | return ch 35 | } 36 | 37 | // Exec sends new job to the Thread. If wait is true this function 38 | // blocks until job finishes execution. 39 | func (t Thread) Exec(wait bool, fn func()) { 40 | if !wait { 41 | t <- fn 42 | return 43 | } 44 | 45 | var wg sync.WaitGroup 46 | wg.Add(1) 47 | t <- func() { 48 | fn() 49 | wg.Done() 50 | } 51 | wg.Wait() 52 | } 53 | 54 | // Gettid returns Thread id. 55 | func (t Thread) Gettid() (tid int) { 56 | t.Exec(true, func() { 57 | tid = unix.Gettid() 58 | }) 59 | return 60 | } 61 | 62 | // SetAffinity pins the thread to specified CPU core id. 63 | func (t Thread) SetAffinity(id uint) error { 64 | var s unix.CPUSet 65 | s.Set(int(id)) 66 | return unix.SchedSetaffinity(t.Gettid(), &s) 67 | } 68 | 69 | // GetAffinity retrieves CPU affinity of the Thread. 70 | func (t Thread) GetAffinity() (s unix.CPUSet, err error) { 71 | return s, unix.SchedGetaffinity(t.Gettid(), &s) 72 | } 73 | 74 | // Close sends a signal to Thread to finish. 75 | func (t Thread) Close() { 76 | close(t) 77 | } 78 | -------------------------------------------------------------------------------- /lcore/thread_test.go: -------------------------------------------------------------------------------- 1 | package lcore_test 2 | 3 | import ( 4 | // "errors" 5 | "fmt" 6 | "sync" 7 | "testing" 8 | // "time" 9 | 10 | "golang.org/x/sys/unix" 11 | 12 | "github.com/yerden/go-dpdk/lcore" 13 | ) 14 | 15 | func TestNewThread(t *testing.T) { 16 | thd := lcore.NewLockedThread(make(chan func())) 17 | defer thd.Close() 18 | err := thd.SetAffinity(0) 19 | if err != nil { 20 | t.Error(err) 21 | t.FailNow() 22 | } 23 | 24 | var s unix.CPUSet 25 | var wg sync.WaitGroup 26 | wg.Add(1) 27 | thd.Exec(false, func() { 28 | defer wg.Done() 29 | err = unix.SchedGetaffinity(0, &s) 30 | }) 31 | wg.Wait() 32 | if err != nil || !s.IsSet(0) || s.Count() != 1 { 33 | t.Error("core did not launch") 34 | t.FailNow() 35 | } 36 | 37 | // execute and wait 38 | thd.Exec(true, func() { 39 | s = unix.CPUSet{} 40 | }) 41 | 42 | if s.Count() != 0 { 43 | t.Error("core did not launch") 44 | t.FailNow() 45 | } 46 | } 47 | 48 | func TestNewThreadFail(t *testing.T) { 49 | thd := lcore.NewLockedThread(make(chan func())) 50 | defer thd.Close() 51 | err := thd.SetAffinity(64) 52 | if err == nil { 53 | t.Error(err) 54 | t.FailNow() 55 | } 56 | } 57 | 58 | func ExampleThread_Exec() { 59 | // create a thread with new channel 60 | thd := lcore.NewLockedThread(make(chan func())) 61 | defer thd.Close() 62 | 63 | var a int 64 | thd.Exec(true, func() { a = 1 }) 65 | fmt.Println(a) 66 | // Output: 1 67 | } 68 | 69 | func ExampleNewLockedThread() { 70 | // create a thread with new channel 71 | thd := lcore.NewLockedThread(make(chan func())) 72 | defer thd.Close() 73 | 74 | // Set affinity to lcore 0 75 | err := thd.SetAffinity(0) 76 | if err != nil { 77 | fmt.Println(err) 78 | return 79 | } 80 | 81 | var wg sync.WaitGroup 82 | wg.Add(1) 83 | var a int 84 | thd.Exec(false, func() { 85 | defer wg.Done() 86 | a = 1 87 | }) 88 | wg.Wait() 89 | 90 | fmt.Println(a) 91 | // Output: 1 92 | } 93 | -------------------------------------------------------------------------------- /mbuf/mbuf_test.go: -------------------------------------------------------------------------------- 1 | package mbuf 2 | 3 | import ( 4 | "crypto/rand" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/yerden/go-dpdk/common" 9 | "github.com/yerden/go-dpdk/eal" 10 | "github.com/yerden/go-dpdk/mempool" 11 | ) 12 | 13 | func getSample(n int) []byte { 14 | d := make([]byte, n) 15 | rand.Read(d) 16 | return d 17 | } 18 | 19 | func TestPktMbufFreeBulk(t *testing.T) { 20 | eal.InitOnceSafe("test-mbuf", 1) 21 | 22 | mp, err := mempool.CreateMbufPool("test-pool", 1000, 1500) 23 | assert.NoError(t, err) 24 | defer mp.Free() 25 | 26 | assert.Zero(t, mp.InUseCount()) 27 | 28 | mbufs := make([]*Mbuf, 100) 29 | assert.NoError(t, PktMbufAllocBulk(mp, mbufs)) 30 | 31 | assert.Equal(t, mp.InUseCount(), 100) 32 | 33 | PktMbufFreeBulk(mbufs) 34 | assert.Zero(t, mp.InUseCount()) 35 | 36 | // test on single mbuf 37 | m := PktMbufAlloc(mp) 38 | assert.NotNil(t, m) 39 | assert.Equal(t, mp.InUseCount(), 1) 40 | 41 | clone := m.PktMbufClone(mp) 42 | assert.NotNil(t, clone) 43 | assert.Equal(t, mp.InUseCount(), 2) 44 | clone.PktMbufFree() 45 | assert.Equal(t, mp.InUseCount(), 1) 46 | 47 | assert.Equal(t, m.HeadRoomSize(), uint16(128)) 48 | assert.Equal(t, m.TailRoomSize(), uint16(1372)) // 1500 - 128 49 | assert.Equal(t, m.BufLen(), uint16(1500)) 50 | assert.Zero(t, m.PktLen()) 51 | 52 | // private area 53 | priv := &common.CStruct{} 54 | m.PrivData(priv) 55 | assert.Zero(t, priv.Len) 56 | assert.Zero(t, m.PrivSize()) 57 | assert.NotNil(t, priv.Ptr) 58 | 59 | // packet data 60 | assert.Zero(t, len(m.Data())) 61 | sample := getSample(100) 62 | assert.NoError(t, m.PktMbufAppend(sample)) 63 | assert.Equal(t, m.Data(), sample) 64 | 65 | assert.Equal(t, m.Mempool(), mp) 66 | 67 | // ref 68 | assert.Equal(t, m.RefCntRead(), uint16(1)) 69 | assert.Equal(t, m.RefCntUpdate(1), uint16(2)) 70 | assert.Equal(t, m.RefCntRead(), uint16(2)) 71 | m.RefCntSet(1) 72 | assert.Equal(t, m.RefCntRead(), uint16(1)) 73 | } 74 | -------------------------------------------------------------------------------- /common/allocator_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | // "fmt" 5 | "reflect" 6 | "testing" 7 | "unsafe" 8 | ) 9 | 10 | func TestAllocatorMallocT(t *testing.T) { 11 | assert := Assert(t, true) 12 | var p *int 13 | m := &StdAlloc{} 14 | 15 | obj := MallocT(m, &p) 16 | *p = 123 17 | assert(*p == 123) 18 | assert(unsafe.Pointer(p) == obj) 19 | 20 | m.Free(obj) 21 | } 22 | 23 | func TestAllocatorSession(t *testing.T) { 24 | assert := Assert(t, true) 25 | var p *int 26 | m := &StdAlloc{} 27 | s := NewAllocatorSession(m) 28 | defer s.Flush() 29 | 30 | obj := MallocT(s, &p) 31 | *p = 123 32 | assert(*p == 123) 33 | assert(unsafe.Pointer(p) == obj) 34 | } 35 | 36 | func TestAllocatorSessionCalloc(t *testing.T) { 37 | assert := Assert(t, true) 38 | var p *int 39 | m := &StdAlloc{} 40 | s := NewAllocatorSession(m) 41 | defer s.Flush() 42 | 43 | obj := CallocT(s, &p, 2) 44 | *p = 123 45 | assert(*p == 123) 46 | assert(unsafe.Pointer(p) == obj) 47 | 48 | p = (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(*p))) 49 | *p = 456 50 | assert(*p == 456) 51 | } 52 | 53 | func TestAllocatorSessionCalloc2(t *testing.T) { 54 | assert := Assert(t, true) 55 | 56 | // allocator 57 | m := &StdAlloc{} 58 | s := NewAllocatorSession(m) 59 | defer s.Flush() 60 | 61 | // declare slice 62 | var p []int 63 | var ptr *int 64 | 65 | // allocate it 66 | sh := (*reflect.SliceHeader)(unsafe.Pointer(&p)) 67 | sh.Data = uintptr(CallocT(s, &ptr, 2)) 68 | sh.Len = 2 69 | sh.Cap = 2 70 | 71 | p[0], p[1] = 123, 456 72 | assert(p[0] == 123) 73 | assert(p[1] == 456) 74 | assert(sh.Data == uintptr(unsafe.Pointer(ptr))) 75 | } 76 | 77 | func TestTransformPOD(t *testing.T) { 78 | assert := Assert(t, true) 79 | 80 | type myType struct { 81 | X, Y int 82 | } 83 | 84 | x := &myType{1, 2} 85 | alloc := &StdAlloc{} 86 | ptr, dtor := TransformPOD(alloc, x) 87 | defer dtor(ptr) 88 | 89 | y := (*myType)(ptr) 90 | assert(*x == *y) 91 | } 92 | -------------------------------------------------------------------------------- /util/lcores.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | ) 8 | 9 | // LcoresList represents a list of unsigned ints, e.g. lcores. 10 | // It implements sort.Interface. 11 | type LcoresList []uint 12 | 13 | func (list LcoresList) Len() int { 14 | return len(list) 15 | } 16 | 17 | func (list LcoresList) Swap(i, j int) { 18 | list[i], list[j] = list[j], list[i] 19 | } 20 | 21 | func (list LcoresList) Less(i, j int) bool { 22 | return list[i] < list[j] 23 | } 24 | 25 | var _ sort.Interface = LcoresList{} 26 | 27 | // Sort sorts list of unsigned ints. 28 | func (list LcoresList) Sort() { 29 | sort.Sort(list) 30 | } 31 | 32 | // Dup allocates new list and copies list's contents into it. 33 | func (list LcoresList) Dup() LcoresList { 34 | newList := append(LcoresList{}, list...) 35 | newList.Sort() 36 | return newList 37 | } 38 | 39 | func (list LcoresList) String() string { 40 | if list.Len() == 0 { 41 | return "" 42 | } 43 | 44 | list = list.Dup() 45 | prev := 0 46 | next := 0 47 | var s []string 48 | 49 | for i := 1; i < list.Len(); i++ { 50 | if list[i] == list[i-1] { 51 | continue 52 | } 53 | 54 | if list[i]-list[i-1] == 1 { 55 | next = i 56 | continue 57 | } 58 | 59 | s = append(s, list.rangeStr(prev, next)) 60 | prev = i 61 | next = i 62 | } 63 | 64 | s = append(s, list.rangeStr(prev, next)) 65 | return strings.Join(s, ",") 66 | } 67 | 68 | func (list LcoresList) rangeStr(prev, next int) string { 69 | if prev == next { 70 | return fmt.Sprintf("%d", list[prev]) 71 | } 72 | return fmt.Sprintf("%d-%d", list[prev], list[next]) 73 | } 74 | 75 | // Equal returns true if list and other are identical. 76 | func (list LcoresList) Equal(other LcoresList) bool { 77 | list = list.Dup() 78 | other = other.Dup() 79 | 80 | if list.Len() != other.Len() { 81 | return false 82 | } 83 | 84 | for i := range list { 85 | if list[i] != other[i] { 86 | return false 87 | } 88 | } 89 | 90 | return true 91 | } 92 | -------------------------------------------------------------------------------- /ethdev/flow/sample_flow.h: -------------------------------------------------------------------------------- 1 | #ifndef _SAMPLE_FLOW_ 2 | #define _SAMPLE_FLOW_ 3 | 4 | #include 5 | #include 6 | 7 | static int eth_vlan_ip4_udp(uint16_t port_id, struct rte_flow_action_rss *rss, 8 | struct rte_flow **pf, struct rte_flow_error *err) 9 | { 10 | /* Declaring structs being used. 8< */ 11 | struct rte_flow_attr attr; 12 | struct rte_flow_item pattern[20]; 13 | struct rte_flow_action action[20]; 14 | struct rte_flow *flow = NULL; 15 | /* >8 End of declaring structs being used. */ 16 | int res; 17 | 18 | memset(pattern, 0, sizeof(pattern)); 19 | memset(action, 0, sizeof(action)); 20 | 21 | /* Set the rule attribute, only ingress packets will be checked. 8< */ 22 | memset(&attr, 0, sizeof(struct rte_flow_attr)); 23 | attr.ingress = 1; 24 | /* >8 End of setting the rule attribute. */ 25 | 26 | /* 27 | * create the action sequence. 28 | * one action only, move packet to queue 29 | */ 30 | action[0].type = RTE_FLOW_ACTION_TYPE_RSS; 31 | action[0].conf = rss; 32 | action[1].type = RTE_FLOW_ACTION_TYPE_END; 33 | 34 | /* 35 | * set the first level of the pattern (ETH). 36 | * since in this example we just want to get the 37 | * ipv4 we set this level to allow all. 38 | */ 39 | 40 | /* IPv4 we set this level to allow all. 8< */ 41 | pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH; 42 | pattern[1].type = RTE_FLOW_ITEM_TYPE_VLAN; 43 | pattern[2].type = RTE_FLOW_ITEM_TYPE_IPV4; 44 | pattern[3].type = RTE_FLOW_ITEM_TYPE_UDP; 45 | pattern[4].type = RTE_FLOW_ITEM_TYPE_END; 46 | /* >8 End of setting the first level of the pattern. */ 47 | 48 | /* Validate the rule and create it. 8< */ 49 | res = rte_flow_validate(port_id, &attr, pattern, action, err); 50 | if (!res && pf != NULL) { 51 | if ((*pf = rte_flow_create(port_id, &attr, pattern, action, err)) == NULL) { 52 | res = rte_errno; 53 | } 54 | } 55 | /* >8 End of validation the rule and create it. */ 56 | 57 | return res; 58 | } 59 | 60 | #endif /* _SAMPLE_FLOW_ */ 61 | -------------------------------------------------------------------------------- /table/table.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package table implements RTE Table. 3 | 4 | This tool is part of the DPDK Packet Framework tool suite and provides a 5 | standard interface to implement different types of lookup tables for data plane 6 | processing. 7 | 8 | Virtually any search algorithm that can uniquely associate data to a lookup key 9 | can be fitted under this lookup table abstraction. For the flow table use-case, 10 | the lookup key is an n-tuple of packet fields that uniquely identifies a 11 | traffic flow, while data represents actions and action meta-data associated 12 | with the same traffic flow. 13 | */ 14 | package table 15 | 16 | /* 17 | #cgo pkg-config: libdpdk 18 | #include 19 | 20 | static void * 21 | go_table_create(struct rte_table_ops *ops, void *params, int socket_id, uint32_t entry_size) 22 | { 23 | return ops->f_create(params, socket_id, entry_size); 24 | } 25 | 26 | static int 27 | go_table_free(struct rte_table_ops *ops, void *table) 28 | { 29 | return ops->f_free(table); 30 | } 31 | 32 | */ 33 | import "C" 34 | import ( 35 | "unsafe" 36 | 37 | "github.com/yerden/go-dpdk/common" 38 | ) 39 | 40 | // Ops is the function table implementing table. 41 | type Ops C.struct_rte_table_ops 42 | 43 | // Table is the instance of a lookup table. 44 | type Table [0]byte 45 | 46 | // Params is the parameters for creating a table. 47 | type Params interface { 48 | common.Transformer 49 | Ops() *Ops 50 | } 51 | 52 | // Create a table manually. May return nil pointer in case of an 53 | // error. 54 | func Create(socket int, p Params, entrySize uint32) *Table { 55 | ops := (*C.struct_rte_table_ops)(p.Ops()) 56 | arg, dtor := p.Transform(alloc) 57 | defer dtor(arg) 58 | return (*Table)(C.go_table_create(ops, arg, C.int(socket), C.uint32_t(entrySize))) 59 | } 60 | 61 | // Free deletes a table. 62 | func (t *Table) Free(tableOps *Ops) error { 63 | ops := (*C.struct_rte_table_ops)(tableOps) 64 | return common.IntErr(int64(C.go_table_free(ops, unsafe.Pointer(t)))) 65 | } 66 | -------------------------------------------------------------------------------- /app/test-sniffer/mempool.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/yerden/go-dpdk/ethdev" 8 | "github.com/yerden/go-dpdk/mempool" 9 | ) 10 | 11 | // RxqMempooler returns a mempool for specified port and rx queue. 12 | type RxqMempooler interface { 13 | GetRxMempool(pid ethdev.Port, qid uint16) (*mempool.Mempool, error) 14 | FreeMempools() 15 | } 16 | 17 | // mpPerPort implements RxqMempooler. 18 | type mpPerPort struct { 19 | pools []*mempool.Mempool 20 | } 21 | 22 | // NewMempoolPerPort creates new RxqMempooler. It creates a mempool 23 | // per each valid port number. 24 | // 25 | // prefix is used to construct a name for created mempools. 26 | // 27 | // Mempools are created from src. Closest to port NUMA node is 28 | // prepended to the list of specified mempool options. If you 29 | // deliberately want to set NUMA node, enlist it in opts. 30 | func NewMempoolPerPort(prefix string, src mempool.Factory, opts ...mempool.Option) (RxqMempooler, error) { 31 | mpp := &mpPerPort{ 32 | pools: make([]*mempool.Mempool, ethdev.CountTotal()), 33 | } 34 | 35 | var err error 36 | for i := range mpp.pools { 37 | if pid := ethdev.Port(i); pid.IsValid() { 38 | name := fmt.Sprintf("%s_%d", prefix, i) 39 | 40 | // initially, we set the socket of mempool to the closest one 41 | // user may specify desired socket if needed 42 | options := append([]mempool.Option{ 43 | mempool.OptSocket(pid.SocketID()), 44 | }, opts...) 45 | 46 | if mpp.pools[i], err = src.NewMempool(name, options); err != nil { 47 | return nil, err 48 | } 49 | } 50 | } 51 | 52 | return mpp, nil 53 | } 54 | 55 | // GetRxMempool implements RxqMempooler interface. 56 | func (mpp *mpPerPort) GetRxMempool(pid ethdev.Port, qid uint16) (*mempool.Mempool, error) { 57 | if !pid.IsValid() { 58 | return nil, os.ErrInvalid 59 | } 60 | 61 | return mpp.pools[pid], nil 62 | } 63 | 64 | // FreeMempools implements RxqMempooler interface. 65 | func (mpp *mpPerPort) FreeMempools() { 66 | for _, mp := range mpp.pools { 67 | mp.Free() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/test-sniffer/queue_count.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | "sync" 6 | 7 | "github.com/prometheus/client_golang/prometheus" 8 | "github.com/yerden/go-dpdk/eal" 9 | "github.com/yerden/go-dpdk/ethdev" 10 | "github.com/yerden/go-dpdk/mbuf" 11 | "github.com/yerden/go-dpdk/util" 12 | ) 13 | 14 | type PacketBytes struct { 15 | Packets prometheus.Counter 16 | Bytes prometheus.Counter 17 | } 18 | 19 | type QueueCounter struct { 20 | RX PacketBytes 21 | } 22 | 23 | type QueueCounterReporter struct { 24 | reg prometheus.Registerer 25 | mtx sync.Mutex 26 | lcores []*QueueCounter 27 | } 28 | 29 | func (qcr *QueueCounterReporter) getLcoresCounter(lcore uint) *QueueCounter { 30 | if extra := int(lcore) - len(qcr.lcores) + 1; extra > 0 { 31 | qcr.lcores = append(qcr.lcores, make([]*QueueCounter, extra)...) 32 | } 33 | 34 | p := new(QueueCounter) 35 | qcr.lcores[lcore] = p 36 | return p 37 | } 38 | 39 | func (qcr *QueueCounterReporter) Register(pid ethdev.Port, qid uint16) *QueueCounter { 40 | qcr.mtx.Lock() 41 | defer qcr.mtx.Unlock() 42 | 43 | qc := qcr.getLcoresCounter(eal.LcoreID()) 44 | name, err := pid.Name() 45 | if err != nil { 46 | panic(util.ErrWrapf(err, "no name for pid=%d", pid)) 47 | } 48 | 49 | labels := prometheus.Labels{ 50 | "index": strconv.FormatUint(uint64(pid), 10), 51 | "queue": strconv.FormatUint(uint64(qid), 10), 52 | "name": name, 53 | } 54 | newCounter := func(name string) prometheus.Counter { 55 | c := prometheus.NewCounter(prometheus.CounterOpts{ 56 | Namespace: statsNamespace, 57 | Subsystem: "rxq", 58 | Name: name, 59 | ConstLabels: labels, 60 | }) 61 | qcr.reg.MustRegister(c) 62 | return c 63 | } 64 | 65 | qc.RX.Packets = newCounter("rx_packets") 66 | qc.RX.Bytes = newCounter("rx_bytes") 67 | 68 | return qc 69 | } 70 | 71 | func (qc *QueueCounter) Incr(pkts []*mbuf.Mbuf) { 72 | dataLen := uint64(0) 73 | for i := range pkts { 74 | dataLen += uint64(len(pkts[i].Data())) 75 | } 76 | qc.RX.Packets.Add(float64(len(pkts))) 77 | qc.RX.Bytes.Add(float64(dataLen)) 78 | } 79 | -------------------------------------------------------------------------------- /ethdev/flow/item_vlan.go: -------------------------------------------------------------------------------- 1 | package flow 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | enum { 10 | ITEM_VLAN_OFF_HDR = offsetof(struct rte_flow_item_vlan, hdr), 11 | }; 12 | 13 | static const void *get_item_vlan_mask() { 14 | return &rte_flow_item_vlan_mask; 15 | } 16 | 17 | static void set_has_more_vlan(struct rte_flow_item_vlan *item, uint32_t d) { 18 | item->has_more_vlan = d; 19 | } 20 | 21 | */ 22 | import "C" 23 | import ( 24 | "runtime" 25 | "unsafe" 26 | ) 27 | 28 | var _ ItemStruct = (*ItemVlan)(nil) 29 | 30 | // ItemVlan matches an 802.1Q/ad VLAN tag. 31 | // 32 | // The corresponding standard outer EtherType (TPID) values are 33 | // RTE_ETHER_TYPE_VLAN or RTE_ETHER_TYPE_QINQ. It can be overridden by the 34 | // preceding pattern item. If a VLAN item is present in the pattern, then only 35 | // tagged packets will match the pattern. The field has_more_vlan can be used to 36 | // match any type of tagged packets, instead of using the eth_proto field of hdr. 37 | // If the eth_proto of hdr and has_more_vlan fields are not specified, then any 38 | // tagged packets will match the pattern. 39 | type ItemVlan struct { 40 | cPointer 41 | HasMoreVlan bool 42 | TCI uint16 43 | InnerType uint16 44 | } 45 | 46 | // Reload implements ItemStruct interface. 47 | func (item *ItemVlan) Reload() { 48 | cptr := (*C.struct_rte_flow_item_vlan)(item.createOrRet(C.sizeof_struct_rte_flow_item_vlan)) 49 | 50 | if item.HasMoreVlan { 51 | C.set_has_more_vlan(cptr, 1) 52 | } else { 53 | C.set_has_more_vlan(cptr, 0) 54 | } 55 | 56 | hdr := (*C.struct_rte_vlan_hdr)(off(unsafe.Pointer(cptr), C.ITEM_VLAN_OFF_HDR)) 57 | beU16(item.TCI, unsafe.Pointer(&hdr.vlan_tci)) 58 | beU16(item.InnerType, unsafe.Pointer(&hdr.eth_proto)) 59 | 60 | runtime.SetFinalizer(item, (*ItemVlan).free) 61 | } 62 | 63 | // Type implements ItemStruct interface. 64 | func (item *ItemVlan) Type() ItemType { 65 | return ItemTypeVlan 66 | } 67 | 68 | // Mask implements ItemStruct interface. 69 | func (item *ItemVlan) Mask() unsafe.Pointer { 70 | return C.get_item_vlan_mask() 71 | } 72 | -------------------------------------------------------------------------------- /common/common.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package common contains basic routines needed by other modules in 3 | go-dpdk package. 4 | */ 5 | package common 6 | 7 | /* 8 | #include 9 | #include 10 | static int rteErrno() { 11 | return rte_errno; 12 | } 13 | */ 14 | import "C" 15 | 16 | import ( 17 | "errors" 18 | "reflect" 19 | "sync" 20 | "syscall" 21 | ) 22 | 23 | // Custom RTE induced errors. 24 | var ( 25 | ErrNoConfig = errors.New("Missing rte_config") 26 | ErrSecondary = errors.New("Operation not allowed in secondary processes") 27 | ) 28 | 29 | // IntErr returns errno as error. 30 | func IntErr(n int64) error { 31 | return errno(n) 32 | } 33 | 34 | func errno(n int64) error { 35 | if n == 0 { 36 | return nil 37 | } else if n < 0 { 38 | n = -n 39 | } 40 | 41 | if n == int64(C.E_RTE_NO_CONFIG) { 42 | return ErrNoConfig 43 | } 44 | 45 | if n == int64(C.E_RTE_SECONDARY) { 46 | return ErrSecondary 47 | } 48 | 49 | return syscall.Errno(int(n)) 50 | } 51 | 52 | // RteErrno returns rte_errno variable. 53 | func RteErrno() error { 54 | return errno(int64(C.rteErrno())) 55 | } 56 | 57 | // IntOrErr returns error as in Errno in case n is negative. 58 | // Otherwise, the value itself with nil error will be returned. 59 | // 60 | // If n is nil, then n = RteErrno() 61 | // if n is not nil and not a signed integer, function panics. 62 | func IntOrErr(n interface{}) (int, error) { 63 | x := reflect.ValueOf(n).Int() 64 | if x >= 0 { 65 | return int(x), nil 66 | } 67 | return 0, errno(x) 68 | } 69 | 70 | // IntToErr converts n into an 'errno' error. If n is not a signed 71 | // integer it will panic. 72 | func IntToErr(n interface{}) error { 73 | x := reflect.ValueOf(n).Int() 74 | return errno(x) 75 | } 76 | 77 | // DoOnce decorates fn in a way that it will effectively run only once 78 | // returning the resulting error value in this and all subsequent 79 | // calls. Useful in unit testing when initialization must be performed 80 | // only once. 81 | func DoOnce(fn func() error) func() error { 82 | var once sync.Once 83 | var err error 84 | return func() error { 85 | once.Do(func() { err = fn() }) 86 | return err 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /pipeline/pipeline_loop_test.go: -------------------------------------------------------------------------------- 1 | package pipeline 2 | 3 | import ( 4 | "io" 5 | "testing" 6 | "time" 7 | 8 | "github.com/yerden/go-dpdk/common" 9 | "github.com/yerden/go-dpdk/eal" 10 | "github.com/yerden/go-dpdk/mempool" 11 | "github.com/yerden/go-dpdk/port" 12 | "github.com/yerden/go-dpdk/table" 13 | ) 14 | 15 | func TestPipelineLoop(t *testing.T) { 16 | assert := common.Assert(t, true) 17 | 18 | // Initialize EAL on all cores 19 | eal.InitOnceSafe("test", 4) 20 | 21 | ctrl := NewSimpleController() 22 | errCh := make(chan error, 2) 23 | 24 | eal.ExecOnMainAsync(errCh, func(*eal.LcoreCtx) { 25 | pl := Create(&Params{ 26 | Name: "test_pipeline", 27 | SocketID: 0, 28 | OffsetPortID: 0, 29 | }) 30 | assert(pl != nil) 31 | 32 | mp, err := mempool.CreateMbufPool( 33 | "hello", 34 | 1024, 35 | 2048, 36 | ) 37 | assert(err == nil, err) 38 | defer mp.Free() 39 | 40 | pSource1, err := pl.PortInCreate(&PortInParams{ 41 | Params: &port.Source{ 42 | Mempool: mp, 43 | }, 44 | BurstSize: 32, 45 | }) 46 | assert(err == nil, err) 47 | 48 | pSink1, err := pl.PortOutCreate(&PortOutParams{ 49 | Params: &port.Sink{ 50 | Filename: "/dev/null", 51 | MaxPackets: 32, 52 | }, 53 | }) 54 | 55 | assert(pSink1 == 0) 56 | assert(nil == err, err) 57 | 58 | // create pipeline tables 59 | tStub, err := pl.TableCreate(&TableParams{ 60 | Params: &table.StubParams{}, 61 | }) 62 | assert(err == nil, err) 63 | 64 | // connect input port. 65 | err = pl.ConnectToTable(pSource1, tStub) 66 | assert(err == nil, err) 67 | 68 | // check the pipeline 69 | assert(nil == pl.Check()) 70 | assert(nil == pl.Flush()) 71 | 72 | errCh <- io.EOF // just some error to signal 73 | 74 | // run infinite loop on the pipeline, waiting for control 75 | err = pl.RunLoop(1024, ctrl) 76 | assert(err == nil, err) 77 | 78 | // destroy the tables, ports and pipeline 79 | assert(nil == pl.Free()) 80 | }) 81 | 82 | assert(io.EOF == <-errCh) // we're about to start pipeline loop 83 | 84 | // waiting for pipeline to soar 85 | time.Sleep(100 * time.Millisecond) 86 | 87 | // stop the pipeline loop 88 | ctrl.Stop() 89 | 90 | assert(nil == <-errCh) 91 | } 92 | -------------------------------------------------------------------------------- /port/fd.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | /* 4 | #include 5 | #include 6 | 7 | #include 8 | */ 9 | import "C" 10 | 11 | import ( 12 | "unsafe" 13 | 14 | "github.com/yerden/go-dpdk/common" 15 | "github.com/yerden/go-dpdk/mempool" 16 | ) 17 | 18 | // FdRx input port built on top of valid non-blocking file 19 | // descriptor. 20 | type FdRx struct { 21 | // Pre-initialized buffer pool. 22 | *mempool.Mempool 23 | 24 | // File descriptor. 25 | Fd uintptr 26 | 27 | // Maximum Transfer Unit (MTU) 28 | MTU uint32 29 | } 30 | 31 | // InOps implements InParams interface. 32 | func (rd *FdRx) InOps() *InOps { 33 | return (*InOps)(&C.rte_port_fd_reader_ops) 34 | } 35 | 36 | // Transform implements common.Transformer interface. 37 | func (rd *FdRx) Transform(alloc common.Allocator) (unsafe.Pointer, func(unsafe.Pointer)) { 38 | return common.TransformPOD(alloc, &C.struct_rte_port_fd_reader_params{ 39 | fd: C.int(rd.Fd), 40 | mtu: C.uint32_t(rd.MTU), 41 | mempool: (*C.struct_rte_mempool)(unsafe.Pointer(rd.Mempool)), 42 | }) 43 | } 44 | 45 | // FdTx is an output port built on top of valid non-blocking file 46 | // descriptor. 47 | type FdTx struct { 48 | // File descriptor. 49 | Fd uintptr 50 | 51 | // Recommended write burst size. The actual burst size can be 52 | // bigger or smaller than this value. 53 | // 54 | // Should be power of 2. 55 | BurstSize uint32 56 | 57 | // If NoDrop set writer makes Retries attempts to write packets to 58 | // ring. 59 | NoDrop bool 60 | 61 | // If NoDrop set and Retries is 0, number of retries is unlimited. 62 | Retries uint32 63 | } 64 | 65 | // OutOps implements OutParams interface. 66 | func (wr *FdTx) OutOps() *OutOps { 67 | if wr.NoDrop { 68 | return (*OutOps)(&C.rte_port_fd_writer_nodrop_ops) 69 | } 70 | 71 | return (*OutOps)(&C.rte_port_fd_writer_ops) 72 | } 73 | 74 | // Transform implements common.Transformer interface. 75 | func (wr *FdTx) Transform(alloc common.Allocator) (unsafe.Pointer, func(unsafe.Pointer)) { 76 | return common.TransformPOD(alloc, &C.struct_rte_port_fd_writer_nodrop_params{ 77 | fd: C.int(wr.Fd), 78 | tx_burst_sz: C.uint32_t(wr.BurstSize), 79 | n_retries: C.uint32_t(wr.Retries), 80 | }) 81 | } 82 | -------------------------------------------------------------------------------- /app/dpdk-exporter/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "log" 7 | "net" 8 | "net/http" 9 | "os" 10 | "os/signal" 11 | "sync" 12 | "syscall" 13 | "time" 14 | 15 | "github.com/prometheus/client_golang/prometheus/promhttp" 16 | "github.com/yerden/go-dpdk/eal" 17 | ) 18 | 19 | var ( 20 | addr = flag.String("addr", ":22017", "Endpoint for prometheus") 21 | interval = flag.Duration("interval", time.Second, "Interval between statistics reports") 22 | ) 23 | 24 | func main() { 25 | // gracefully shutdown 26 | shutdownCtx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGSEGV) 27 | defer stop() 28 | 29 | // initialize the EAL 30 | n, err := eal.Init(os.Args) 31 | if err != nil { 32 | log.Panicf("invalid EAL arguments: %v", err) 33 | } 34 | // clean up the EAL 35 | defer eal.Cleanup() 36 | defer eal.StopLcores() 37 | 38 | os.Args[n], os.Args = os.Args[0], os.Args[n:] 39 | flag.Parse() 40 | 41 | // if the port is already in use, the app will not start 42 | ln, err := net.Listen("tcp", *addr) 43 | if err != nil { 44 | log.Panicf("net listen: %v", err) 45 | } 46 | defer ln.Close() 47 | 48 | var metrics *Metrics 49 | if err := eal.ExecOnMain(func(lc *eal.LcoreCtx) { 50 | var err error 51 | if metrics, err = NewMetrics(); err != nil { 52 | log.Panicf("init metrics collecting: %v", err) 53 | } 54 | }); err != nil { 55 | log.Panicf("exec on main lcore: %v", err) 56 | } 57 | 58 | var wg sync.WaitGroup 59 | wg.Add(1) 60 | go func() { 61 | defer wg.Done() 62 | metrics.StartCollecting(shutdownCtx) 63 | }() 64 | 65 | // export metrics 66 | http.Handle("/metrics", promhttp.Handler()) 67 | srv := &http.Server{Handler: http.DefaultServeMux} 68 | go func() { 69 | if err := srv.Serve(ln); err != nil && err != http.ErrServerClosed { 70 | log.Panicf("http server: %v", err) 71 | } 72 | }() 73 | 74 | log.Println("all workers started") 75 | wg.Wait() 76 | log.Printf("shutting down...") 77 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 78 | defer cancel() 79 | if err := srv.Shutdown(ctx); err != nil { 80 | log.Printf("shut down http server: %v", err) 81 | } 82 | log.Println("all workers stopped") 83 | } 84 | -------------------------------------------------------------------------------- /port/ethdev.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | /* 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | */ 10 | import "C" 11 | 12 | import ( 13 | "unsafe" 14 | 15 | "github.com/yerden/go-dpdk/common" 16 | ) 17 | 18 | var ( 19 | _ InParams = (*EthdevRx)(nil) 20 | _ OutParams = (*EthdevTx)(nil) 21 | ) 22 | 23 | // EthdevRx is an input port built on top of pre-initialized NIC 24 | // RX queue. 25 | type EthdevRx struct { 26 | // Configured Ethernet port and RX queue ID. 27 | PortID, QueueID uint16 28 | } 29 | 30 | var _ InParams = (*EthdevRx)(nil) 31 | 32 | // InOps implements InParams interface. 33 | func (p *EthdevRx) InOps() *InOps { 34 | return (*InOps)(&C.rte_port_ethdev_reader_ops) 35 | } 36 | 37 | // Transform implements common.Transformer interface. 38 | func (p *EthdevRx) Transform(alloc common.Allocator) (unsafe.Pointer, func(unsafe.Pointer)) { 39 | return common.TransformPOD(alloc, &C.struct_rte_port_ethdev_reader_params{ 40 | port_id: C.uint16_t(p.PortID), 41 | queue_id: C.uint16_t(p.QueueID), 42 | }) 43 | } 44 | 45 | // EthdevTx is an output port built on top of pre-initialized NIC 46 | // TX queue. 47 | type EthdevTx struct { 48 | // Configured Ethernet port and TX queue ID. 49 | PortID, QueueID uint16 50 | 51 | // Recommended burst size for NIC TX queue. 52 | TxBurstSize uint32 53 | 54 | // If NoDrop set writer makes Retries attempts to write packets to 55 | // NIC TX queue. 56 | NoDrop bool 57 | 58 | // If NoDrop set and Retries is 0, number of retries is unlimited. 59 | Retries uint32 60 | } 61 | 62 | // OutOps implements OutParams interface. 63 | func (p *EthdevTx) OutOps() *OutOps { 64 | if !p.NoDrop { 65 | return (*OutOps)(&C.rte_port_ethdev_writer_ops) 66 | } 67 | return (*OutOps)(&C.rte_port_ethdev_writer_nodrop_ops) 68 | } 69 | 70 | // Transform implements common.Transformer interface. 71 | func (p *EthdevTx) Transform(alloc common.Allocator) (unsafe.Pointer, func(unsafe.Pointer)) { 72 | return common.TransformPOD(alloc, &C.struct_rte_port_ethdev_writer_nodrop_params{ 73 | port_id: C.uint16_t(p.PortID), 74 | queue_id: C.uint16_t(p.QueueID), 75 | tx_burst_sz: C.uint32_t(p.TxBurstSize), 76 | n_retries: C.uint32_t(p.Retries), 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /common/objects.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // ObjectID is the ID of some opaque object stored in Registry. 8 | type ObjectID uint64 9 | 10 | // Registry implements CRUD operations to map ID and objects. 11 | type Registry interface { 12 | Create(interface{}) ObjectID 13 | Read(ObjectID) interface{} 14 | Update(ObjectID, interface{}) 15 | Delete(ObjectID) 16 | } 17 | 18 | // NewRegistryMap creates new Registry which stores all objects in a 19 | // map. 20 | func NewRegistryMap() Registry { 21 | return &objTable{ 22 | hash: make(map[ObjectID]interface{}), 23 | } 24 | } 25 | 26 | // map of objects references with lock 27 | type objTable struct { 28 | sync.Mutex 29 | hash map[ObjectID]interface{} 30 | id ObjectID 31 | } 32 | 33 | func (r *objTable) Create(obj interface{}) ObjectID { 34 | r.Lock() 35 | id := r.id 36 | r.hash[id] = obj 37 | r.id = id + 1 38 | r.Unlock() 39 | return id 40 | } 41 | 42 | func (r *objTable) Read(id ObjectID) interface{} { 43 | r.Lock() 44 | obj := r.hash[id] 45 | r.Unlock() 46 | return obj 47 | } 48 | 49 | func (r *objTable) Update(id ObjectID, obj interface{}) { 50 | r.Lock() 51 | r.hash[id] = obj 52 | r.Unlock() 53 | } 54 | 55 | func (r *objTable) Delete(id ObjectID) { 56 | r.Lock() 57 | delete(r.hash, id) 58 | r.Unlock() 59 | } 60 | 61 | type objArray struct { 62 | sync.Mutex 63 | array []interface{} 64 | cnt uint64 65 | } 66 | 67 | func (r *objArray) Create(obj interface{}) ObjectID { 68 | r.Lock() 69 | id := ObjectID(len(r.array)) 70 | r.array = append(r.array, obj) 71 | r.cnt++ 72 | r.Unlock() 73 | return id 74 | } 75 | 76 | func (r *objArray) Read(id ObjectID) interface{} { 77 | r.Lock() 78 | obj := r.array[id] 79 | r.Unlock() 80 | return obj 81 | } 82 | 83 | func (r *objArray) Update(id ObjectID, obj interface{}) { 84 | r.Lock() 85 | r.array[id] = obj 86 | r.Unlock() 87 | } 88 | 89 | func (r *objArray) Delete(id ObjectID) { 90 | r.Lock() 91 | r.array[id] = nil 92 | if r.cnt--; r.cnt == 0 { 93 | r.array = make([]interface{}, 0, 10) 94 | } 95 | r.Unlock() 96 | } 97 | 98 | // NewRegistryArray creates new Registry as a linear array. 99 | func NewRegistryArray() Registry { 100 | return &objArray{} 101 | } 102 | -------------------------------------------------------------------------------- /ring/ring_telemetry.h: -------------------------------------------------------------------------------- 1 | #ifndef _RING_TELEMETRY_H_ 2 | #define _RING_TELEMETRY_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #if RTE_VERSION < RTE_VERSION_NUM(23, 3, 0, 0) 10 | #define tel_data_add_dict_uint rte_tel_data_add_dict_u64 11 | #else 12 | #define tel_data_add_dict_uint rte_tel_data_add_dict_uint 13 | #endif 14 | 15 | static inline const char * 16 | trim_prefix(const char *s, const char *pre) 17 | { 18 | int len = strlen(pre); 19 | return s + len * !strncmp(s, pre, len); 20 | } 21 | 22 | static void 23 | memzone_reap_rings( 24 | const struct rte_memzone *mz, 25 | void *arg) 26 | { 27 | struct rte_tel_data *d = (struct rte_tel_data *)(arg); 28 | const char *name = trim_prefix(mz->name, RTE_RING_MZ_PREFIX); 29 | if (rte_ring_lookup(name)) 30 | rte_tel_data_add_array_string(d, name); 31 | } 32 | 33 | static int 34 | ring_list( 35 | __rte_unused const char *cmd, 36 | __rte_unused const char *params, 37 | struct rte_tel_data *d) 38 | { 39 | rte_tel_data_start_array(d, RTE_TEL_STRING_VAL); 40 | rte_memzone_walk(memzone_reap_rings, d); 41 | return 0; 42 | } 43 | 44 | static inline int 45 | ring_info( 46 | __rte_unused const char *cmd, 47 | const char *name, 48 | struct rte_tel_data *d) 49 | { 50 | if (!name|| strlen(name) == 0) 51 | return -EINVAL; 52 | 53 | struct rte_ring *r = rte_ring_lookup(name); 54 | if (r == NULL) 55 | return -ENOENT; 56 | 57 | rte_tel_data_start_dict(d); 58 | rte_tel_data_add_dict_string(d, "ring_name", name); 59 | rte_tel_data_add_dict_string(d, "ring_mz_name", r->memzone->name); 60 | rte_tel_data_add_dict_int(d, "ring_is_sc", rte_ring_is_cons_single(r)); 61 | rte_tel_data_add_dict_int(d, "ring_is_sp", rte_ring_is_prod_single(r)); 62 | rte_tel_data_add_dict_int(d, "ring_prod_sync_type", rte_ring_get_prod_sync_type(r)); 63 | rte_tel_data_add_dict_int(d, "ring_cons_sync_type", rte_ring_get_cons_sync_type(r)); 64 | tel_data_add_dict_uint(d, "ring_size", rte_ring_get_size(r)); 65 | tel_data_add_dict_uint(d, "ring_count", rte_ring_count(r)); 66 | tel_data_add_dict_uint(d, "ring_capacity", rte_ring_get_capacity(r)); 67 | 68 | return 0; 69 | } 70 | 71 | telemetry_cb ring_list_cb = ring_list; 72 | telemetry_cb ring_info_cb = ring_info; 73 | 74 | #endif /* _RING_TELEMETRY_H_ */ 75 | -------------------------------------------------------------------------------- /common/set_test.go: -------------------------------------------------------------------------------- 1 | package common_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/yerden/go-dpdk/common" 8 | 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | func TestCommonMapCreate(t *testing.T) { 13 | assert := common.Assert(t, true) 14 | 15 | a := common.NewMap([]int{0, 1, 2, 3}) 16 | assert(a.IsSet(0)) 17 | assert(a.IsSet(1)) 18 | assert(a.IsSet(2)) 19 | assert(a.IsSet(3)) 20 | assert(a.Count() == 4) 21 | assert(a.String() == "f") 22 | 23 | a = common.NewMap([]int{4, 5, 7, 9}) 24 | assert(a.IsSet(4)) 25 | assert(a.IsSet(5)) 26 | assert(!a.IsSet(6)) 27 | assert(a.IsSet(7)) 28 | assert(!a.IsSet(8)) 29 | assert(a.IsSet(9)) 30 | assert(a.Count() == 4) 31 | assert(a.String() == "2b0") 32 | 33 | a = common.NewMap([]int{6, 8}) 34 | assert(a.String() == "140") 35 | } 36 | 37 | func TestCommonMapCreate2(t *testing.T) { 38 | assert := common.Assert(t, true) 39 | 40 | s := common.NewMap(1) 41 | assert(s.String() == "2", s.String()) 42 | 43 | s = common.NewMap([]int{1, 2, 3}) 44 | assert(s.String() == "e", s.String()) 45 | 46 | s = common.NewMap([]int{4, 5, 6}) 47 | assert(s.String() == "70", s.String()) 48 | 49 | s = common.NewMap(map[uint16]bool{ 50 | 11: true, 51 | 22: true, 52 | 32: true, 53 | }) 54 | assert(s.Count() == 3) 55 | assert(s.IsSet(11)) 56 | assert(s.IsSet(22)) 57 | assert(s.IsSet(32)) 58 | } 59 | 60 | func TestCommonMapSet(t *testing.T) { 61 | assert := common.Assert(t, true) 62 | 63 | a := common.NewMap([]int{0, 1, 2, 3}) 64 | assert(a.IsSet(0)) 65 | assert(a.IsSet(1)) 66 | assert(a.IsSet(2)) 67 | assert(a.IsSet(3)) 68 | assert(a.Count() == 4) 69 | assert(a.String() == "f") 70 | 71 | a.Set(1) 72 | assert(a.Count() == 4) 73 | a.Set(2) 74 | assert(a.Count() == 4) 75 | a.Set(4) // new 76 | assert(a.Count() == 5) 77 | assert(a.IsSet(4)) 78 | 79 | a.Zero() 80 | assert(a.Count() == 0) 81 | } 82 | 83 | func TestCommonMapFromSet(t *testing.T) { 84 | assert := common.Assert(t, true) 85 | 86 | a := common.NewMap(nil) 87 | assert(a.Count() == 0) 88 | 89 | var set unix.CPUSet 90 | err := unix.SchedGetaffinity(0, &set) 91 | assert(err == nil, err) 92 | 93 | a = common.NewMap(&set) 94 | assert(a.Count() == set.Count()) 95 | } 96 | 97 | func ExampleNewMap() { 98 | x := common.NewMap([]int{0, 1, 2, 3}) 99 | fmt.Println(x) 100 | // Output: f 101 | } 102 | -------------------------------------------------------------------------------- /common/pointers_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "testing" 5 | "unsafe" 6 | ) 7 | 8 | const someByte = 0xDC 9 | 10 | func initBytes(b []byte) { 11 | for i := range b { 12 | b[i] = someByte 13 | } 14 | } 15 | 16 | func checkBytesNil(t testing.TB, b []byte, n int) { 17 | t.Helper() 18 | 19 | for i := 0; i < n; i++ { 20 | if b[i] != 0 { 21 | t.Fatalf("byte array of size %d not zeroed\n", n) 22 | } 23 | } 24 | 25 | if b[n] != someByte { 26 | t.Fatalf("invalid initialization of array of size %d\n", n) 27 | } 28 | } 29 | 30 | func TestMemset(t *testing.T) { 31 | b := make([]byte, 1024) 32 | 33 | for i := 1; i < len(b)-1; i++ { 34 | initBytes(b) 35 | Memset(unsafe.Pointer(&b[0]), 0, uintptr(i)) 36 | checkBytesNil(t, b, i) 37 | } 38 | } 39 | 40 | var globalSlice []byte 41 | 42 | func benchmarkMemsetN(b *testing.B, n int) { 43 | b.Helper() 44 | data := make([]byte, n) 45 | initBytes(data) 46 | 47 | for i := 0; i < b.N; i++ { 48 | Memset(unsafe.Pointer(&data[0]), 0, uintptr(len(data))) 49 | } 50 | 51 | globalSlice = data 52 | } 53 | 54 | func benchmarkPlainInitN(b *testing.B, n int) { 55 | b.Helper() 56 | data := make([]byte, n) 57 | initBytes(data) 58 | 59 | for i := 0; i < b.N; i++ { 60 | for j := range data { 61 | data[j] = 0 62 | } 63 | } 64 | 65 | globalSlice = data 66 | } 67 | 68 | func BenchmarkMemset4(b *testing.B) { 69 | benchmarkMemsetN(b, 4) 70 | } 71 | 72 | func BenchmarkPlainInit4(b *testing.B) { 73 | benchmarkPlainInitN(b, 4) 74 | } 75 | 76 | func BenchmarkMemset6(b *testing.B) { 77 | benchmarkMemsetN(b, 6) 78 | } 79 | 80 | func BenchmarkPlainInit6(b *testing.B) { 81 | benchmarkPlainInitN(b, 6) 82 | } 83 | 84 | func BenchmarkMemset8(b *testing.B) { 85 | benchmarkMemsetN(b, 8) 86 | } 87 | 88 | func BenchmarkPlainInit8(b *testing.B) { 89 | benchmarkPlainInitN(b, 8) 90 | } 91 | 92 | func BenchmarkMemset12(b *testing.B) { 93 | benchmarkMemsetN(b, 12) 94 | } 95 | 96 | func BenchmarkPlainInit12(b *testing.B) { 97 | benchmarkPlainInitN(b, 12) 98 | } 99 | 100 | func BenchmarkMemset31(b *testing.B) { 101 | benchmarkMemsetN(b, 31) 102 | } 103 | 104 | func BenchmarkPlainInit31(b *testing.B) { 105 | benchmarkPlainInitN(b, 31) 106 | } 107 | 108 | func BenchmarkMemset511(b *testing.B) { 109 | benchmarkMemsetN(b, 511) 110 | } 111 | 112 | func BenchmarkPlainInit511(b *testing.B) { 113 | benchmarkPlainInitN(b, 511) 114 | } 115 | -------------------------------------------------------------------------------- /port/ring.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | /* 4 | #include 5 | #include 6 | 7 | #include 8 | */ 9 | import "C" 10 | 11 | import ( 12 | "unsafe" 13 | 14 | "github.com/yerden/go-dpdk/common" 15 | "github.com/yerden/go-dpdk/ring" 16 | ) 17 | 18 | // compile time checks 19 | var _ = []InParams{ 20 | &RingRx{}, 21 | } 22 | 23 | var _ = []OutParams{ 24 | &RingTx{}, 25 | } 26 | 27 | // RingRx is an input port built on top of pre-initialized RTE ring. 28 | type RingRx struct { 29 | // Underlying ring 30 | *ring.Ring 31 | 32 | // Set if specified ring is multi consumer. 33 | Multi bool 34 | } 35 | 36 | // InOps implements InParams interface. 37 | func (rd *RingRx) InOps() *InOps { 38 | if !rd.Multi { 39 | return (*InOps)(&C.rte_port_ring_reader_ops) 40 | } 41 | return (*InOps)(&C.rte_port_ring_multi_reader_ops) 42 | } 43 | 44 | // Transform implements common.Transformer interface. 45 | func (rd *RingRx) Transform(alloc common.Allocator) (unsafe.Pointer, func(unsafe.Pointer)) { 46 | return common.TransformPOD(alloc, &C.struct_rte_port_ring_reader_params{ 47 | ring: (*C.struct_rte_ring)(unsafe.Pointer(rd.Ring)), 48 | }) 49 | } 50 | 51 | // RingTx is an output port built on top of pre-initialized single 52 | // producer ring. 53 | type RingTx struct { 54 | // Underlying ring 55 | *ring.Ring 56 | 57 | // Recommended burst size for ring operations. 58 | TxBurstSize uint32 59 | 60 | // Set if specified ring is multi producer. 61 | Multi bool 62 | 63 | // If NoDrop set writer makes Retries attempts to write packets to 64 | // ring. 65 | NoDrop bool 66 | 67 | // If NoDrop set and Retries is 0, number of retries is unlimited. 68 | Retries uint32 69 | } 70 | 71 | // OutOps implements OutParams interface. 72 | func (wr *RingTx) OutOps() *OutOps { 73 | ops := []*C.struct_rte_port_out_ops{ 74 | &C.rte_port_ring_writer_ops, 75 | &C.rte_port_ring_multi_writer_ops, 76 | &C.rte_port_ring_writer_nodrop_ops, 77 | &C.rte_port_ring_multi_writer_nodrop_ops, 78 | } 79 | 80 | if wr.Multi { 81 | ops = ops[1:] 82 | } 83 | 84 | if wr.NoDrop { 85 | ops = ops[2:] 86 | } 87 | 88 | return (*OutOps)(ops[0]) 89 | } 90 | 91 | // Transform implements common.Transformer interface. 92 | func (wr *RingTx) Transform(alloc common.Allocator) (unsafe.Pointer, func(unsafe.Pointer)) { 93 | return common.TransformPOD(alloc, &C.struct_rte_port_ring_writer_nodrop_params{ 94 | ring: (*C.struct_rte_ring)(unsafe.Pointer(wr.Ring)), 95 | tx_burst_sz: C.uint32_t(wr.TxBurstSize), 96 | n_retries: C.uint32_t(wr.Retries), 97 | }) 98 | } 99 | -------------------------------------------------------------------------------- /table/acl_params.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | 8 | import ( 9 | "unsafe" 10 | ) 11 | 12 | // ACLField describes a field to match. 13 | type ACLField [C.sizeof_struct_rte_acl_field / 8]uint64 14 | type cACLField C.struct_rte_acl_field 15 | 16 | // NewACLField8 returns ACLField with 8-bit value and range mask. 17 | func NewACLField8(value, maskRange uint8) ACLField { 18 | var ret ACLField 19 | dst := (*C.struct_rte_acl_field)(unsafe.Pointer(&ret)) 20 | *(*uint8)(unsafe.Pointer(&dst.value)) = value 21 | *(*uint8)(unsafe.Pointer(&dst.mask_range)) = maskRange 22 | return ret 23 | } 24 | 25 | // NewACLField16 returns ACLField with 16-bit value and range mask. 26 | func NewACLField16(value, maskRange uint16) ACLField { 27 | var ret ACLField 28 | dst := (*C.struct_rte_acl_field)(unsafe.Pointer(&ret)) 29 | *(*uint16)(unsafe.Pointer(&dst.value)) = value 30 | *(*uint16)(unsafe.Pointer(&dst.mask_range)) = maskRange 31 | return ret 32 | } 33 | 34 | // NewACLField32 returns ACLField with 32-bit value and range mask. 35 | func NewACLField32(value, maskRange uint32) ACLField { 36 | var ret ACLField 37 | dst := (*C.struct_rte_acl_field)(unsafe.Pointer(&ret)) 38 | *(*uint32)(unsafe.Pointer(&dst.value)) = value 39 | *(*uint32)(unsafe.Pointer(&dst.mask_range)) = maskRange 40 | return ret 41 | } 42 | 43 | // NewACLField64 returns ACLField with 64-bit value and range mask. 44 | func NewACLField64(value, maskRange uint64) ACLField { 45 | var ret ACLField 46 | dst := (*C.struct_rte_acl_field)(unsafe.Pointer(&ret)) 47 | *(*uint64)(unsafe.Pointer(&dst.value)) = value 48 | *(*uint64)(unsafe.Pointer(&dst.mask_range)) = maskRange 49 | return ret 50 | } 51 | 52 | // ACLRuleAdd is the key for adding a key/value to a table. 53 | type ACLRuleAdd struct { 54 | Priority int32 55 | FieldValue [C.RTE_ACL_MAX_FIELDS]ACLField 56 | } 57 | type cACLRuleAdd C.struct_rte_table_acl_rule_add_params 58 | 59 | // ACLRuleDelete is the key for deleting a key/value from a table. 60 | type ACLRuleDelete struct { 61 | FieldValue [C.RTE_ACL_MAX_FIELDS]ACLField 62 | } 63 | type cACLRuleDelete C.struct_rte_table_acl_rule_delete_params 64 | 65 | var _ = []uintptr{ 66 | unsafe.Sizeof(ACLField{}) - unsafe.Sizeof(cACLField{}), 67 | unsafe.Sizeof(cACLField{}) - unsafe.Sizeof(ACLField{}), 68 | 69 | unsafe.Sizeof(ACLRuleAdd{}) - unsafe.Sizeof(cACLRuleAdd{}), 70 | unsafe.Sizeof(cACLRuleAdd{}) - unsafe.Sizeof(ACLRuleAdd{}), 71 | 72 | unsafe.Sizeof(ACLRuleDelete{}) - unsafe.Sizeof(cACLRuleDelete{}), 73 | unsafe.Sizeof(cACLRuleDelete{}) - unsafe.Sizeof(ACLRuleDelete{}), 74 | } 75 | -------------------------------------------------------------------------------- /port/port_test.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/yerden/go-dpdk/common" 7 | "github.com/yerden/go-dpdk/eal" 8 | "github.com/yerden/go-dpdk/ring" 9 | ) 10 | 11 | func TestPortRingRx(t *testing.T) { 12 | assert := common.Assert(t, true) 13 | 14 | // Initialize EAL on all cores 15 | eal.InitOnceSafe("test", 4) 16 | 17 | err := eal.ExecOnMain(func(*eal.LcoreCtx) { 18 | r, err := ring.Create("test_ring", 1024) 19 | assert(err == nil, err) 20 | defer r.Free() 21 | 22 | confRx := &RingRx{Ring: r, Multi: true} 23 | 24 | ops := confRx.InOps() 25 | assert(ops != nil) 26 | 27 | arg, dtor := confRx.Transform(alloc) 28 | defer dtor(arg) 29 | 30 | in := CreateIn(-1, confRx) 31 | assert(in != nil) 32 | 33 | err = in.Free(ops) 34 | assert(err == nil, err) 35 | }) 36 | assert(err == nil, err) 37 | } 38 | 39 | func TestPortRingTx(t *testing.T) { 40 | assert := common.Assert(t, true) 41 | 42 | // Initialize EAL on all cores 43 | eal.InitOnceSafe("test", 4) 44 | 45 | err := eal.ExecOnMain(func(*eal.LcoreCtx) { 46 | r, err := ring.Create("test_ring", 1024) 47 | assert(err == nil, err) 48 | defer r.Free() 49 | 50 | confTx := &RingTx{Ring: r, Multi: true, NoDrop: false, TxBurstSize: 64} 51 | 52 | ops := confTx.OutOps() 53 | tx := CreateOut(-1, confTx) 54 | assert(tx != nil) 55 | 56 | err = tx.Free(ops) 57 | assert(err == nil, err) 58 | }) 59 | assert(err == nil, err) 60 | } 61 | 62 | func TestPortRingCreateRx(t *testing.T) { 63 | assert := common.Assert(t, true) 64 | 65 | // Initialize EAL on all cores 66 | eal.InitOnceSafe("test", 4) 67 | 68 | err := eal.ExecOnMain(func(*eal.LcoreCtx) { 69 | r, err := ring.Create("test_ring", 1024) 70 | assert(err == nil, err) 71 | defer r.Free() 72 | 73 | confRx := &RingRx{Ring: r, Multi: true} 74 | ops := confRx.InOps() 75 | rx := CreateIn(-1, confRx) 76 | assert(rx != nil) 77 | assert(rx.Free(ops) == nil) 78 | }) 79 | assert(err == nil, err) 80 | } 81 | 82 | func TestPortRingCreateTx(t *testing.T) { 83 | assert := common.Assert(t, true) 84 | 85 | // Initialize EAL on all cores 86 | eal.InitOnceSafe("test", 4) 87 | 88 | err := eal.ExecOnMain(func(*eal.LcoreCtx) { 89 | r, err := ring.Create("test_ring", 1024) 90 | assert(err == nil, err) 91 | defer r.Free() 92 | 93 | confTx := &RingTx{Ring: r, Multi: true, NoDrop: false, TxBurstSize: 64} 94 | ops := confTx.OutOps() 95 | tx := CreateOut(-1, confTx) 96 | assert(tx != nil, tx) 97 | assert(tx.Free(ops) == nil) 98 | }) 99 | assert(err == nil, err) 100 | } 101 | -------------------------------------------------------------------------------- /table/acl.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | /* 4 | #include 5 | #include 6 | */ 7 | import "C" 8 | 9 | import ( 10 | "unsafe" 11 | 12 | "github.com/yerden/go-dpdk/common" 13 | ) 14 | 15 | // ACLFieldDef is the ACL Field definition. Each field in the ACL rule 16 | // has an associate definition. It defines the type of field, its 17 | // size, its offset in the input buffer, the field index, and the 18 | // input index. For performance reasons, the inner loop of the search 19 | // function is unrolled to process four input bytes at a time. This 20 | // requires the input to be grouped into sets of 4 consecutive bytes. 21 | // The loop processes the first input byte as part of the setup and 22 | // then subsequent bytes must be in groups of 4 consecutive bytes. 23 | type ACLFieldDef struct { 24 | Type uint8 // type - RTE_ACL_FIELD_TYPE_*. 25 | Size uint8 // size of field 1,2,4, or 8. 26 | FieldIndex uint8 // index of field inside the rule. 27 | InputIndex uint8 // 0-N input index. 28 | Offset uint32 // offset to start of field. 29 | } 30 | 31 | // ACLParams is the ACL table parameters. 32 | type ACLParams struct { 33 | Name string // Name. 34 | Rules uint32 // Maximum number of ACL rules in the table. 35 | FieldFormat []ACLFieldDef // Format specification of the fields of the ACL rule. 36 | } 37 | 38 | var _ Params = (*ACLParams)(nil) 39 | 40 | // Ops implements Params interface. 41 | func (p *ACLParams) Ops() *Ops { 42 | return (*Ops)(&C.rte_table_acl_ops) 43 | } 44 | 45 | var alloc = &common.StdAlloc{} 46 | 47 | // Transform implements common.Transformer interface. 48 | func (p *ACLParams) Transform(alloc common.Allocator) (unsafe.Pointer, func(unsafe.Pointer)) { 49 | var params *C.struct_rte_table_acl_params 50 | params = (*C.struct_rte_table_acl_params)(alloc.Malloc(unsafe.Sizeof(*params))) 51 | 52 | if len(p.FieldFormat) > len(params.field_format) { 53 | panic("excessive field format entries in ACL params") 54 | } 55 | 56 | params.name = (*C.char)(common.CString(alloc, p.Name)) 57 | params.n_rules = C.uint(p.Rules) 58 | params.n_rule_fields = C.uint(len(p.FieldFormat)) 59 | for i := range p.FieldFormat { 60 | src := &p.FieldFormat[i] 61 | dst := ¶ms.field_format[i] 62 | dst._type = C.uint8_t(src.Type) 63 | dst.size = C.uint8_t(src.Size) 64 | dst.field_index = C.uint8_t(src.FieldIndex) 65 | dst.input_index = C.uint8_t(src.InputIndex) 66 | dst.offset = C.uint32_t(src.Offset) 67 | } 68 | 69 | return unsafe.Pointer(params), func(p unsafe.Pointer) { 70 | params := (*C.struct_rte_table_acl_params)(p) 71 | alloc.Free(unsafe.Pointer(params.name)) 72 | alloc.Free(unsafe.Pointer(params)) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /ethdev/lsc_telemetry.h: -------------------------------------------------------------------------------- 1 | #ifndef _LSC_TELEMETRY_H_ 2 | #define _LSC_TELEMETRY_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #if RTE_VERSION < RTE_VERSION_NUM(23, 3, 0, 0) 12 | #define tel_data_add_dict_uint rte_tel_data_add_dict_u64 13 | #else 14 | #define tel_data_add_dict_uint rte_tel_data_add_dict_uint 15 | #endif 16 | 17 | struct lsc_counters { 18 | struct { 19 | int enabled; 20 | rte_atomic64_t counter; 21 | } ports[RTE_MAX_ETHPORTS]; 22 | }; 23 | 24 | extern struct lsc_counters global_lsc_counters; 25 | 26 | static int 27 | lsc_counters_callback( 28 | __rte_unused uint16_t port_id, 29 | enum rte_eth_event_type event, 30 | void *cb_arg, 31 | __rte_unused void *ret_param) 32 | { 33 | rte_atomic64_t *counter = cb_arg; 34 | rte_atomic64_add(counter, event == RTE_ETH_EVENT_INTR_LSC); 35 | return 0; 36 | } 37 | 38 | static int 39 | lsc_counters_callback_register(uint16_t port_id) 40 | { 41 | struct lsc_counters *c = &global_lsc_counters; 42 | if (!rte_eth_dev_is_valid_port(port_id)) 43 | return -EINVAL; 44 | 45 | c->ports[port_id].enabled = 1; 46 | rte_atomic64_t *counter = &c->ports[port_id].counter; 47 | return rte_eth_dev_callback_register(port_id, RTE_ETH_EVENT_INTR_LSC, 48 | lsc_counters_callback, counter); 49 | } 50 | 51 | static int 52 | lsc_counters_callback_unregister(uint16_t port_id) 53 | { 54 | struct lsc_counters *c = &global_lsc_counters; 55 | if (!rte_eth_dev_is_valid_port(port_id)) 56 | return -EINVAL; 57 | 58 | c->ports[port_id].enabled = 0; 59 | rte_atomic64_t *counter = &c->ports[port_id].counter; 60 | return rte_eth_dev_callback_unregister(port_id, RTE_ETH_EVENT_INTR_LSC, 61 | lsc_counters_callback, counter); 62 | } 63 | 64 | static int 65 | ethdev_lsc( 66 | __rte_unused const char *cmd, 67 | const char *params, 68 | struct rte_tel_data *d) 69 | { 70 | struct lsc_counters *c = &global_lsc_counters; 71 | int port_id; 72 | char *end_param; 73 | 74 | if (params == NULL || strlen(params) == 0) 75 | return -1; 76 | 77 | port_id = strtoul(params, &end_param, 0); 78 | if (*end_param != '\0') 79 | return -1; 80 | 81 | if (!rte_eth_dev_is_valid_port(port_id)) 82 | return -ENOENT; 83 | 84 | rte_tel_data_start_dict(d); 85 | rte_tel_data_add_dict_int(d, "enabled", c->ports[port_id].enabled); 86 | tel_data_add_dict_uint(d, "lsc_counter", rte_atomic64_read(&c->ports[port_id].counter)); 87 | return 0; 88 | } 89 | 90 | static inline void 91 | lsc_register_telemetry_cmd(const char *name) 92 | { 93 | rte_telemetry_register_cmd(name, ethdev_lsc, 94 | "Returns LSC counter for specified port id"); 95 | } 96 | 97 | #endif /* _LSC_TELEMETRY_H_ */ 98 | -------------------------------------------------------------------------------- /ethdev/flow/item_ipv4.go: -------------------------------------------------------------------------------- 1 | package flow 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | 8 | enum { 9 | IPv4_HDR_OFF_DST_VERSION_IHL = offsetof(struct rte_ipv4_hdr, version_ihl), 10 | }; 11 | 12 | static const struct rte_flow_item_ipv4 *get_item_ipv4_mask() { 13 | return &rte_flow_item_ipv4_mask; 14 | } 15 | 16 | */ 17 | import "C" 18 | import ( 19 | "runtime" 20 | "unsafe" 21 | ) 22 | 23 | // IPv4 represents a raw IPv4 address. 24 | type IPv4 [4]byte 25 | 26 | // IPv4Header is the IPv4 header raw format. 27 | type IPv4Header struct { 28 | VersionIHL uint8 /* Version and header length. */ 29 | ToS uint8 /* Type of service. */ 30 | TotalLength uint16 /* Length of packet. */ 31 | ID uint16 /* Packet ID. */ 32 | FragmentOffset uint16 /* Fragmentation offset. */ 33 | TTL uint8 /* Time to live. */ 34 | Proto uint8 /* Protocol ID. */ 35 | Checksum uint16 /* Header checksum. */ 36 | SrcAddr IPv4 /* Source address. */ 37 | DstAddr IPv4 /* Destination address. */ 38 | } 39 | 40 | // ItemIPv4 matches an IPv4 header. 41 | // 42 | // Note: IPv4 options are handled by dedicated pattern items. 43 | type ItemIPv4 struct { 44 | cPointer 45 | 46 | Header IPv4Header 47 | } 48 | 49 | var _ ItemStruct = (*ItemIPv4)(nil) 50 | 51 | // Reload implements ItemStruct interface. 52 | func (item *ItemIPv4) Reload() { 53 | cptr := (*C.struct_rte_flow_item_ipv4)(item.createOrRet(C.sizeof_struct_rte_flow_item_ipv4)) 54 | cvtIPv4Header(&cptr.hdr, &item.Header) 55 | runtime.SetFinalizer(item, (*ItemIPv4).free) 56 | } 57 | 58 | func cvtIPv4Header(dst *C.struct_rte_ipv4_hdr, src *IPv4Header) { 59 | setIPv4HdrVersionIHL(dst, src) 60 | 61 | dst.type_of_service = C.uint8_t(src.ToS) 62 | beU16(src.TotalLength, unsafe.Pointer(&dst.total_length)) 63 | beU16(src.ID, unsafe.Pointer(&dst.packet_id)) 64 | beU16(src.FragmentOffset, unsafe.Pointer(&dst.fragment_offset)) 65 | dst.time_to_live = C.uint8_t(src.TTL) 66 | dst.next_proto_id = C.uint8_t(src.Proto) 67 | beU16(src.Checksum, unsafe.Pointer(&dst.hdr_checksum)) 68 | 69 | dst.src_addr = *(*C.rte_be32_t)(unsafe.Pointer(&src.SrcAddr[0])) 70 | dst.dst_addr = *(*C.rte_be32_t)(unsafe.Pointer(&src.DstAddr[0])) 71 | } 72 | 73 | func setIPv4HdrVersionIHL(dst *C.struct_rte_ipv4_hdr, src *IPv4Header) { 74 | p := off(unsafe.Pointer(dst), C.IPv4_HDR_OFF_DST_VERSION_IHL) 75 | *(*C.uint8_t)(p) = (C.uchar)(src.VersionIHL) 76 | } 77 | 78 | // Type implements ItemStruct interface. 79 | func (item *ItemIPv4) Type() ItemType { 80 | return ItemTypeIPv4 81 | } 82 | 83 | // Mask implements ItemStruct interface. 84 | func (item *ItemIPv4) Mask() unsafe.Pointer { 85 | return unsafe.Pointer(C.get_item_ipv4_mask()) 86 | } 87 | -------------------------------------------------------------------------------- /util/rx_buffer.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | /* 4 | #cgo pkg-config: libdpdk 5 | 6 | #define MBUF_ARRAY_USER_SIZE 64 7 | 8 | #include 9 | #include 10 | #include 11 | #include "mbuf_array.h" 12 | 13 | */ 14 | import "C" 15 | 16 | import ( 17 | "reflect" 18 | "unsafe" 19 | 20 | "github.com/yerden/go-dpdk/common" 21 | "github.com/yerden/go-dpdk/ethdev" 22 | "github.com/yerden/go-dpdk/mbuf" 23 | ) 24 | 25 | func geterr(n ...interface{}) error { 26 | if len(n) == 0 { 27 | return common.RteErrno() 28 | } 29 | 30 | return common.IntToErr(n[0]) 31 | } 32 | 33 | // MbufArray is a wrapper of port and queue id. 34 | type MbufArray C.struct_mbuf_array 35 | 36 | // NewMbufArray allocates new MbufArray from huge pages memory for 37 | // socket NUMA node and containing up to size mbufs. If EAL failed to 38 | // allocate memory it will panic. 39 | func NewMbufArray(socket int, size uint16) *MbufArray { 40 | var p *C.struct_mbuf_array 41 | if e := geterr(C.new_mbuf_array(C.int(socket), C.ushort(size), &p)); e != nil { 42 | panic(e) 43 | } 44 | return (*MbufArray)(p) 45 | } 46 | 47 | // Opaque returns slice of bytes pointing to user-defined data in MbufArray. 48 | func (buf *MbufArray) Opaque() (opaque []byte) { 49 | return (*[unsafe.Sizeof(buf.opaque)]byte)(unsafe.Pointer(&opaque))[:] 50 | } 51 | 52 | // NewEthdevMbufArray allocates new MbufArray from huge pages memory 53 | // for specified queue id, socket NUMA node and containing up to size 54 | // mbufs. If EAL failed to allocate memory it will panic. 55 | func NewEthdevMbufArray(pid ethdev.Port, qid uint16, socket int, size uint16) *MbufArray { 56 | p := NewMbufArray(socket, size) 57 | opaque := (*C.struct_ethdev_data)(unsafe.Pointer(&p.opaque)) 58 | opaque.pid = C.ushort(pid) 59 | opaque.qid = C.ushort(qid) 60 | return p 61 | } 62 | 63 | // Free releases acquired huge pages memory. 64 | func (buf *MbufArray) Free() { 65 | C.rte_free(unsafe.Pointer(buf)) 66 | } 67 | 68 | func (buf *MbufArray) cursor() *mbuf.Mbuf { 69 | p := (*[1 << 31]*mbuf.Mbuf)(unsafe.Pointer(&buf.pkts[0])) 70 | return p[buf.n] 71 | } 72 | 73 | // Buffer returns all mbufs in buf. 74 | func (buf *MbufArray) Buffer() (ret []*mbuf.Mbuf) { 75 | sh := (*reflect.SliceHeader)(unsafe.Pointer(&ret)) 76 | sh.Len = int(buf.size) 77 | sh.Cap = int(buf.size) 78 | sh.Data = uintptr(unsafe.Pointer(&buf.pkts[0])) 79 | return 80 | } 81 | 82 | // Mbufs returns all mbufs retrieved by the ethdev API. 83 | func (buf *MbufArray) Mbufs() (ret []*mbuf.Mbuf) { 84 | return buf.Buffer()[:buf.length] 85 | } 86 | 87 | // Recharge releases previously retrieved packets and retrieve new 88 | // ones. Returns number of retrieved packets. 89 | func (buf *MbufArray) Recharge() int { 90 | return int(C.mbuf_array_ethdev_reload((*C.struct_mbuf_array)(buf))) 91 | } 92 | -------------------------------------------------------------------------------- /ring/enqueue.go: -------------------------------------------------------------------------------- 1 | package ring 2 | 3 | /* 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "ring.h" 12 | 13 | struct someptr { 14 | void *p; 15 | }; 16 | */ 17 | import "C" 18 | 19 | import ( 20 | "unsafe" 21 | ) 22 | 23 | // Enqueue enqueues an object into given Ring. 24 | func (r *Ring) Enqueue(obj unsafe.Pointer) bool { 25 | n, _ := r.EnqueueBulk([]unsafe.Pointer{obj}) 26 | return n != 0 27 | } 28 | 29 | // SpEnqueue enqueues an object into given Ring. 30 | func (r *Ring) SpEnqueue(obj unsafe.Pointer) bool { 31 | n, _ := r.SpEnqueueBulk([]unsafe.Pointer{obj}) 32 | return n != 0 33 | } 34 | 35 | // MpEnqueue enqueues an object into given Ring. 36 | func (r *Ring) MpEnqueue(obj unsafe.Pointer) bool { 37 | n, _ := r.MpEnqueueBulk([]unsafe.Pointer{obj}) 38 | return n != 0 39 | } 40 | 41 | // MpEnqueueBulk enqueues given objects from slice into Ring. 42 | // Returns number of enqueued objects (either 0 or len(obj)) and 43 | // amount of space in the ring after the enqueue operation has 44 | // finished. 45 | func (r *Ring) MpEnqueueBulk(obj []unsafe.Pointer) (n, free uint32) { 46 | return ret(C.mp_enqueue_bulk(args(r, obj))) 47 | } 48 | 49 | // SpEnqueueBulk enqueues given objects from slice into Ring. 50 | // Returns number of enqueued objects (either 0 or len(obj)) and 51 | // amount of space in the ring after the enqueue operation has 52 | // finished. 53 | func (r *Ring) SpEnqueueBulk(obj []unsafe.Pointer) (n, free uint32) { 54 | return ret(C.sp_enqueue_bulk(args(r, obj))) 55 | } 56 | 57 | // EnqueueBulk enqueues given objects from slice into Ring. 58 | // Returns number of enqueued objects (either 0 or len(obj)) and 59 | // amount of space in the ring after the enqueue operation has 60 | // finished. 61 | func (r *Ring) EnqueueBulk(obj []unsafe.Pointer) (n, free uint32) { 62 | return ret(C.enqueue_bulk(args(r, obj))) 63 | } 64 | 65 | // MpEnqueueBurst enqueues given objects from slice into Ring. 66 | // Returns number of enqueued objects and amount of space in the ring 67 | // after the enqueue operation has finished. 68 | func (r *Ring) MpEnqueueBurst(obj []unsafe.Pointer) (n, free uint32) { 69 | return ret(C.mp_enqueue_burst(args(r, obj))) 70 | } 71 | 72 | // SpEnqueueBurst enqueues given objects from slice into Ring. 73 | // Returns number of enqueued objects and amount of space in the ring 74 | // after the enqueue operation has finished. 75 | func (r *Ring) SpEnqueueBurst(obj []unsafe.Pointer) (n, free uint32) { 76 | return ret(C.sp_enqueue_burst(args(r, obj))) 77 | } 78 | 79 | // EnqueueBurst enqueues given objects from slice into Ring. Returns 80 | // number of enqueued objects and amount of space in the ring after 81 | // the enqueue operation has finished. 82 | func (r *Ring) EnqueueBurst(obj []unsafe.Pointer) (n, free uint32) { 83 | return ret(C.enqueue_burst(args(r, obj))) 84 | } 85 | -------------------------------------------------------------------------------- /ethdev/flow/action_rss.go: -------------------------------------------------------------------------------- 1 | package flow 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | */ 8 | import "C" 9 | import ( 10 | "runtime" 11 | "unsafe" 12 | ) 13 | 14 | var _ Action = (*ActionRSS)(nil) 15 | 16 | // ActionRSS implements Receive-Side Scaling feature. 17 | // 18 | // Similar to QUEUE, except RSS is additionally performed on packets 19 | // to spread them among several queues according to the provided 20 | // parameters. 21 | // 22 | // Unlike global RSS settings used by other DPDK APIs, unsetting the 23 | // types field does not disable RSS in a flow rule. Doing so instead 24 | // requests safe unspecified "best-effort" settings from the 25 | // underlying PMD, which depending on the flow rule, may result in 26 | // anything ranging from empty (single queue) to all-inclusive RSS. 27 | // 28 | // Note: RSS hash result is stored in the hash.rss mbuf field which 29 | // overlaps hash.fdir.lo. Since the MARK action sets the hash.fdir.hi 30 | // field only, both can be requested simultaneously. 31 | type ActionRSS struct { 32 | Func HashFunction 33 | Queues []uint16 34 | Key []byte 35 | Level uint32 36 | Types uint64 37 | 38 | cptr *C.struct_rte_flow_action_rss 39 | } 40 | 41 | func (action *ActionRSS) free() { 42 | cptr := action.cptr 43 | C.free(unsafe.Pointer(cptr.key)) 44 | C.free(unsafe.Pointer(cptr.queue)) 45 | C.free(unsafe.Pointer(cptr)) 46 | } 47 | 48 | // Reload implements Action interface. 49 | func (action *ActionRSS) Reload() { 50 | // allocate if needed 51 | cptr := action.cptr 52 | if cptr == nil { 53 | cptr = (*C.struct_rte_flow_action_rss)(C.malloc(C.sizeof_struct_rte_flow_action_rss)) 54 | *cptr = C.struct_rte_flow_action_rss{} 55 | action.cptr = cptr 56 | } 57 | 58 | // set queues 59 | if len(action.Queues) > 0 { 60 | sz := C.size_t(len(action.Queues)) * C.size_t(unsafe.Sizeof(action.Queues[0])) 61 | cQueues := C.malloc(sz) 62 | C.memcpy(cQueues, unsafe.Pointer(&action.Queues[0]), sz) 63 | C.free(unsafe.Pointer(cptr.queue)) 64 | cptr.queue_num = C.uint32_t(len(action.Queues)) 65 | cptr.queue = (*C.uint16_t)(cQueues) 66 | } 67 | 68 | // set key 69 | if len(action.Key) > 0 { 70 | sz := C.size_t(len(action.Key)) 71 | cKey := C.malloc(sz) 72 | C.memcpy(cKey, unsafe.Pointer(&action.Key[0]), sz) 73 | C.free(unsafe.Pointer(cptr.key)) 74 | cptr.key_len = C.uint32_t(len(action.Key)) 75 | cptr.key = (*C.uint8_t)(cKey) 76 | } 77 | 78 | cptr.level = C.uint32_t(action.Level) 79 | cptr.types = C.uint64_t(action.Types) 80 | cptr._func = uint32(action.Func) 81 | 82 | runtime.SetFinalizer(action, nil) 83 | runtime.SetFinalizer(action, (*ActionRSS).free) 84 | } 85 | 86 | // Pointer implements Action interface. 87 | func (action *ActionRSS) Pointer() unsafe.Pointer { 88 | return unsafe.Pointer(action.cptr) 89 | } 90 | 91 | // Type implements Action interface. 92 | func (action *ActionRSS) Type() ActionType { 93 | return ActionTypeRss 94 | } 95 | -------------------------------------------------------------------------------- /port/source_sink.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | /* 4 | #include 5 | #include 6 | */ 7 | import "C" 8 | 9 | import ( 10 | "unsafe" 11 | 12 | "github.com/yerden/go-dpdk/common" 13 | "github.com/yerden/go-dpdk/mempool" 14 | ) 15 | 16 | // compile time checks 17 | var _ = []InParams{ 18 | &Source{}, 19 | } 20 | 21 | var _ = []OutParams{ 22 | &Sink{}, 23 | } 24 | 25 | // Source is an input port that can be used to generate packets. 26 | type Source struct { 27 | // Pre-initialized buffer pool. 28 | *mempool.Mempool 29 | 30 | // The full path of the pcap file to read packets from. 31 | Filename string 32 | 33 | // The number of bytes to be read from each packet in the pcap file. If 34 | // this value is 0, the whole packet is read; if it is bigger than packet 35 | // size, the generated packets will contain the whole packet. 36 | BytesPerPacket uint32 37 | } 38 | 39 | // InOps implements InParams interface. 40 | func (rd *Source) InOps() *InOps { 41 | return (*InOps)(&C.rte_port_source_ops) 42 | } 43 | 44 | // Transform implements common.Transformer interface. 45 | func (rd *Source) Transform(alloc common.Allocator) (unsafe.Pointer, func(unsafe.Pointer)) { 46 | // port 47 | var params *C.struct_rte_port_source_params 48 | params = (*C.struct_rte_port_source_params)(alloc.Malloc(unsafe.Sizeof(*params))) 49 | params.mempool = (*C.struct_rte_mempool)(unsafe.Pointer(rd.Mempool)) 50 | params.n_bytes_per_pkt = C.uint32_t(rd.BytesPerPacket) 51 | 52 | if rd.Filename != "" { 53 | params.file_name = (*C.char)(common.CString(alloc, rd.Filename)) 54 | } 55 | 56 | return unsafe.Pointer(params), func(arg unsafe.Pointer) { 57 | params := (*C.struct_rte_port_source_params)(arg) 58 | alloc.Free(unsafe.Pointer(params.file_name)) 59 | alloc.Free(arg) 60 | } 61 | } 62 | 63 | // Sink is an output port that drops all packets written to it. 64 | type Sink struct { 65 | // The full path of the pcap file to write the packets to. 66 | Filename string 67 | 68 | // The maximum number of packets write to the pcap file. If this value is 69 | // 0, the "infinite" write will be carried out. 70 | MaxPackets uint32 71 | } 72 | 73 | // OutOps implements OutParams interface. 74 | func (wr *Sink) OutOps() *OutOps { 75 | return (*OutOps)(&C.rte_port_sink_ops) 76 | } 77 | 78 | // Transform implements common.Transformer interface. 79 | func (wr *Sink) Transform(alloc common.Allocator) (unsafe.Pointer, func(unsafe.Pointer)) { 80 | var params *C.struct_rte_port_sink_params 81 | params = (*C.struct_rte_port_sink_params)(alloc.Malloc(unsafe.Sizeof(*params))) 82 | params.max_n_pkts = C.uint32_t(wr.MaxPackets) 83 | 84 | if wr.Filename != "" { 85 | params.file_name = (*C.char)(common.CString(alloc, wr.Filename)) 86 | } 87 | 88 | return unsafe.Pointer(params), func(arg unsafe.Pointer) { 89 | params := (*C.struct_rte_port_sink_params)(arg) 90 | alloc.Free(unsafe.Pointer(params.file_name)) 91 | alloc.Free(unsafe.Pointer(arg)) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /ethdev/flow/errors.go: -------------------------------------------------------------------------------- 1 | package flow 2 | 3 | /* 4 | #include 5 | #include 6 | */ 7 | import "C" 8 | 9 | import ( 10 | "fmt" 11 | "unsafe" 12 | ) 13 | 14 | // Error is a verbose error structure definition. 15 | // 16 | // This object is normally allocated by applications and set by PMDs, 17 | // the message points to a constant string which does not need to be 18 | // freed by the application, however its pointer can be considered 19 | // valid only as long as its associated DPDK port remains configured. 20 | // Closing the underlying device or unloading the PMD invalidates it. 21 | // 22 | // Both cause and message may be NULL regardless of the error type. 23 | type Error C.struct_rte_flow_error 24 | 25 | func (e *Error) Error() string { 26 | return fmt.Sprintf("%v: %s", e.Unwrap(), C.GoString(e.message)) 27 | } 28 | 29 | func (e *Error) Unwrap() error { 30 | return ErrorType(e._type) 31 | } 32 | 33 | // Cause returns object responsible for error. 34 | func (e *Error) Cause() unsafe.Pointer { 35 | return e.cause 36 | } 37 | 38 | // ErrorType is a type of an error. 39 | type ErrorType uint 40 | 41 | func (e ErrorType) Error() string { 42 | if s, ok := errStr[e]; ok { 43 | return s 44 | } 45 | return "" 46 | } 47 | 48 | var ( 49 | errStr = make(map[ErrorType]string) 50 | ) 51 | 52 | func registerErr(c uint, str string) ErrorType { 53 | et := ErrorType(c) 54 | errStr[et] = str 55 | return et 56 | } 57 | 58 | // Error types. 59 | var ( 60 | ErrTypeNone = registerErr(C.RTE_FLOW_ERROR_TYPE_NONE, "No error") 61 | ErrTypeUnspecified = registerErr(C.RTE_FLOW_ERROR_TYPE_UNSPECIFIED, "Cause unspecified") 62 | ErrTypeHandle = registerErr(C.RTE_FLOW_ERROR_TYPE_HANDLE, "Flow rule (handle)") 63 | ErrTypeAttrGroup = registerErr(C.RTE_FLOW_ERROR_TYPE_ATTR_GROUP, "Group field") 64 | ErrTypeAttrPriority = registerErr(C.RTE_FLOW_ERROR_TYPE_ATTR_PRIORITY, "Priority field") 65 | ErrTypeAttrIngress = registerErr(C.RTE_FLOW_ERROR_TYPE_ATTR_INGRESS, "Ingress field") 66 | ErrTypeAttrEgress = registerErr(C.RTE_FLOW_ERROR_TYPE_ATTR_EGRESS, "Egress field") 67 | ErrTypeAttrTransfer = registerErr(C.RTE_FLOW_ERROR_TYPE_ATTR_TRANSFER, "Transfer field") 68 | ErrTypeAttr = registerErr(C.RTE_FLOW_ERROR_TYPE_ATTR, "Attributes structure") 69 | ErrTypeItemNum = registerErr(C.RTE_FLOW_ERROR_TYPE_ITEM_NUM, "Pattern length") 70 | ErrTypeItemSpec = registerErr(C.RTE_FLOW_ERROR_TYPE_ITEM_SPEC, "Item specification spec") 71 | ErrTypeItemLast = registerErr(C.RTE_FLOW_ERROR_TYPE_ITEM_LAST, "Item specification range") 72 | ErrTypeItemMask = registerErr(C.RTE_FLOW_ERROR_TYPE_ITEM_MASK, "Item specification mask") 73 | ErrTypeItem = registerErr(C.RTE_FLOW_ERROR_TYPE_ITEM, "Specific pattern item") 74 | ErrTypeActionNum = registerErr(C.RTE_FLOW_ERROR_TYPE_ACTION_NUM, "Number of actions") 75 | ErrTypeActionConf = registerErr(C.RTE_FLOW_ERROR_TYPE_ACTION_CONF, "Action configuration") 76 | ErrTypeAction = registerErr(C.RTE_FLOW_ERROR_TYPE_ACTION, "Specific action") 77 | ) 78 | -------------------------------------------------------------------------------- /app/test-sniffer/port.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "syscall" 8 | 9 | "github.com/yerden/go-dpdk/ethdev" 10 | "github.com/yerden/go-dpdk/util" 11 | ) 12 | 13 | // EthdevCallback specifies callback to call on ethdev.Port. 14 | type EthdevCallback interface { 15 | EthdevCall(ethdev.Port) error 16 | } 17 | 18 | type EthdevCallbackFunc func(ethdev.Port) error 19 | 20 | func (f EthdevCallbackFunc) EthdevCall(pid ethdev.Port) error { 21 | return f(pid) 22 | } 23 | 24 | // EthdevConfig specifies information on how to configure ethdev.Port. 25 | type EthdevConfig struct { 26 | Options []ethdev.Option 27 | RxQueues uint16 28 | 29 | // Hooks to call after configuration 30 | OnConfig []EthdevCallback 31 | 32 | // RX queue config 33 | Pooler RxqMempooler 34 | RxDescriptors uint16 35 | RxOptions []ethdev.QueueOption 36 | 37 | // Flow Control Mode 38 | FcMode uint32 39 | } 40 | 41 | func ifaceName(pid ethdev.Port) string { 42 | name, _ := pid.Name() 43 | return name 44 | } 45 | 46 | // Configure must be called on main lcore to configure ethdev.Port. 47 | func (conf *EthdevConfig) Configure(pid ethdev.Port) error { 48 | var info ethdev.DevInfo 49 | if err := pid.InfoGet(&info); err != nil { 50 | return err 51 | } 52 | 53 | opts := conf.Options 54 | lscOpt := ethdev.OptIntrConf(ethdev.IntrConf{LSC: true}) 55 | if info.DevFlags().IsIntrLSC() { 56 | opts = append(conf.Options, lscOpt) 57 | pid.RegisterCallbackLSC() 58 | } else { 59 | fmt.Printf("port %d doesn't support LSC interrupt\n", pid) 60 | } 61 | 62 | if err := pid.DevConfigure(conf.RxQueues, 0, opts...); err != nil { 63 | return err 64 | } 65 | 66 | if err := pid.PromiscEnable(); err != nil { 67 | return err 68 | } 69 | 70 | for qid := uint16(0); qid < conf.RxQueues; qid++ { 71 | //fmt.Printf("configuring rxq: %d@%d\n", pid, qid) 72 | mp, err := conf.Pooler.GetRxMempool(pid, qid) 73 | if err != nil { 74 | return err 75 | } 76 | if err := pid.RxqSetup(qid, conf.RxDescriptors, mp, conf.RxOptions...); err != nil { 77 | return err 78 | } 79 | } 80 | 81 | var fc ethdev.FcConf 82 | 83 | if err := pid.FlowCtrlGet(&fc); err == nil { 84 | fc.SetMode(conf.FcMode) 85 | if err := pid.FlowCtrlSet(&fc); err != nil { 86 | return util.ErrWrapf(err, "FlowCtrlSet") 87 | } 88 | 89 | log.Printf("pid=%d: Flow Control set to %d", pid, conf.FcMode) 90 | } else if !errors.Is(err, syscall.ENOTSUP) { 91 | return util.ErrWrapf(err, "FlowCtrlGet") 92 | } 93 | 94 | for i := range conf.OnConfig { 95 | if err := conf.OnConfig[i].EthdevCall(pid); err != nil { 96 | return util.ErrWrapf(err, "OnConfig %d: %v", i, conf.OnConfig[i]) 97 | } 98 | } 99 | 100 | return nil 101 | } 102 | 103 | func printPortConfig(pid ethdev.Port) error { 104 | var info ethdev.DevInfo 105 | if err := pid.InfoGet(&info); err != nil { 106 | return err 107 | } 108 | 109 | log.Printf("port %d: nrxq=%d\n", pid, info.NbRxQueues()) 110 | return nil 111 | } 112 | -------------------------------------------------------------------------------- /pipeline/stats.go: -------------------------------------------------------------------------------- 1 | package pipeline 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | 8 | import ( 9 | "unsafe" 10 | 11 | "github.com/yerden/go-dpdk/common" 12 | "github.com/yerden/go-dpdk/port" 13 | "github.com/yerden/go-dpdk/table" 14 | ) 15 | 16 | // PortInStats is the pipeline input port stats. 17 | type PortInStats struct { 18 | port.InStats 19 | 20 | // Number of packets dropped by action handler. 21 | PacketsDroppedByAH uint64 22 | } 23 | 24 | // PortOutStats is the pipeline output port stats. 25 | type PortOutStats struct { 26 | port.OutStats 27 | 28 | // Number of packets dropped by action handler. 29 | PacketsDroppedByAH uint64 30 | } 31 | 32 | // TableStats is the pipeline table stats. 33 | type TableStats struct { 34 | table.Stats 35 | 36 | PacketsDroppedByHitAH uint64 37 | PacketsDroppedByMissAH uint64 38 | PacketsDroppedByHit uint64 39 | PacketsDroppedByMiss uint64 40 | } 41 | 42 | var _ = []uintptr{ 43 | unsafe.Sizeof(PortInStats{}) - unsafe.Sizeof(C.struct_rte_pipeline_port_in_stats{}), 44 | unsafe.Sizeof(C.struct_rte_pipeline_port_in_stats{}) - unsafe.Sizeof(PortInStats{}), 45 | unsafe.Sizeof(PortOutStats{}) - unsafe.Sizeof(C.struct_rte_pipeline_port_out_stats{}), 46 | unsafe.Sizeof(C.struct_rte_pipeline_port_out_stats{}) - unsafe.Sizeof(PortOutStats{}), 47 | unsafe.Sizeof(TableStats{}) - unsafe.Sizeof(C.struct_rte_pipeline_table_stats{}), 48 | unsafe.Sizeof(C.struct_rte_pipeline_table_stats{}) - unsafe.Sizeof(TableStats{}), 49 | } 50 | 51 | // PortOutStatsRead reads stats on output port registered in the 52 | // pipeline. 53 | // 54 | // If clear s true then clear stats after reading. 55 | func (pl *Pipeline) PortOutStatsRead(port PortOut, s *PortOutStats, clear bool) error { 56 | var c C.int 57 | if clear { 58 | c = 1 59 | } 60 | 61 | return common.IntErr(int64(C.rte_pipeline_port_out_stats_read( 62 | (*C.struct_rte_pipeline)(pl), 63 | C.uint32_t(port), 64 | (*C.struct_rte_pipeline_port_out_stats)(unsafe.Pointer(s)), 65 | c))) 66 | } 67 | 68 | // PortInStatsRead reads stats on input port registered in the 69 | // pipeline. 70 | // 71 | // If clear s true then clear stats after reading. 72 | func (pl *Pipeline) PortInStatsRead(port PortIn, s *PortInStats, clear bool) error { 73 | var c C.int 74 | if clear { 75 | c = 1 76 | } 77 | 78 | return common.IntErr(int64(C.rte_pipeline_port_in_stats_read( 79 | (*C.struct_rte_pipeline)(pl), 80 | C.uint32_t(port), 81 | (*C.struct_rte_pipeline_port_in_stats)(unsafe.Pointer(s)), 82 | c))) 83 | } 84 | 85 | // TableStatsRead reads stats on table registered in the pipeline. 86 | // 87 | // If clear s true then clear stats after reading. 88 | func (pl *Pipeline) TableStatsRead(table Table, s *TableStats, clear bool) error { 89 | var c C.int 90 | if clear { 91 | c = 1 92 | } 93 | 94 | return common.IntErr(int64(C.rte_pipeline_table_stats_read( 95 | (*C.struct_rte_pipeline)(pl), 96 | C.uint32_t(table), 97 | (*C.struct_rte_pipeline_table_stats)(unsafe.Pointer(s)), 98 | c))) 99 | } 100 | -------------------------------------------------------------------------------- /common/parse.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "errors" 7 | "unicode" 8 | "unicode/utf8" 9 | ) 10 | 11 | // Splitter parsing errors. 12 | var ( 13 | ErrUnprintable = errors.New("unprintable char") 14 | ErrOpenQuote = errors.New("no closing quote") 15 | ) 16 | 17 | // DefaultSplitter parses tokens as space-separated words treating 18 | // double and signal quotation mark as 'quotes'. 19 | var DefaultSplitter = &Splitter{ 20 | unicode.IsSpace, 21 | func(r rune) (rune, bool) { 22 | if r == '"' { 23 | return '"', true 24 | } 25 | if r == '\'' { 26 | return '\'', true 27 | } 28 | return ' ', false 29 | }, 30 | false, 31 | } 32 | 33 | // Splitter is used to parse string into words. Quotes are used to 34 | // protect words from being separated into separate token. 35 | type Splitter struct { 36 | // True if rune is a white space. 37 | IsSpace func(rune) bool 38 | 39 | // True if rune is quote. Any rune embraced by the one of these 40 | // pairs is considered a part of a token even if IsSpace returns 41 | // true. A pairs must not contradict white space and another 42 | // pair. 43 | // 44 | // If true, return closing quote rune. 45 | IsQuote func(rune) (rune, bool) 46 | 47 | // If true, final token is allowed not to contain closing quote. 48 | // If false, ErrOpenQuote error will be returned if no closing 49 | // quote found. 50 | AllowOpenQuote bool 51 | } 52 | 53 | // SplitFunc generates bufio.SplitFunc to use in bufio.Scanner. 54 | func SplitFunc(s *Splitter) bufio.SplitFunc { 55 | isSpaceOrQuote := func(r rune) bool { 56 | _, ok := s.IsQuote(r) 57 | return ok || s.IsSpace(r) 58 | } 59 | 60 | isNotSpace := func(r rune) bool { 61 | return !s.IsSpace(r) 62 | } 63 | 64 | return func(data []byte, atEOF bool) (advance int, token []byte, err error) { 65 | // skip whitespace 66 | n := bytes.IndexFunc(data, isNotSpace) 67 | if n < 0 { 68 | return len(data), nil, nil 69 | } 70 | 71 | // start of a token 72 | quoted := false 73 | advance = n 74 | data = data[n:] 75 | n = 0 76 | 77 | for { 78 | k := bytes.IndexFunc(data[n:], isSpaceOrQuote) 79 | if k < 0 { 80 | // unterminated token, not enough data 81 | break 82 | } 83 | 84 | // rune 'r' is either space or quote 85 | r, wid := utf8.DecodeRune(data[n+k:]) 86 | 87 | // if quote, then look for closing quote 88 | if q, ok := s.IsQuote(r); ok { 89 | n += k + wid 90 | if k = bytes.IndexRune(data[n:], q); k < 0 { 91 | quoted = true 92 | break 93 | } 94 | n += k + wid 95 | continue 96 | } 97 | 98 | // 'r' is white space 99 | return advance + n + k + wid, data[:n+k], nil 100 | } 101 | 102 | if !atEOF { 103 | // unterminated token, need more data 104 | return advance, nil, nil 105 | } else if !quoted || s.AllowOpenQuote { 106 | // unterminated token, no more data 107 | return advance + len(data), data, nil 108 | } else { 109 | // unterminated quote is not allowed 110 | return advance + len(data), data, ErrOpenQuote 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /ethdev/stats_test.go: -------------------------------------------------------------------------------- 1 | package ethdev 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | "time" 7 | "unsafe" 8 | 9 | "github.com/yerden/go-dpdk/eal" 10 | ) 11 | 12 | func assert(t testing.TB, expected bool, args ...interface{}) { 13 | if !expected { 14 | t.Helper() 15 | t.Fatal(args...) 16 | } 17 | } 18 | 19 | func TestEthXstats(t *testing.T) { 20 | eal.InitOnceSafe("test", 4) 21 | 22 | var names []XstatName 23 | var ealErr error 24 | namesByID := map[uint64]string{} 25 | 26 | err := eal.ExecOnMain(func(*eal.LcoreCtx) { 27 | pid := Port(0) 28 | names, ealErr = pid.XstatNames() 29 | assert(t, ealErr == nil) 30 | 31 | namesByID, ealErr = pid.XstatNameIDs() 32 | assert(t, ealErr == nil) 33 | }) 34 | 35 | assert(t, err == nil, err) 36 | 37 | assert(t, len(names) > 0) 38 | assert(t, len(namesByID) == len(names), namesByID) 39 | } 40 | 41 | // TestEthStats tests that EthStats can be casted to Stats. 42 | func TestEthStats(t *testing.T) { 43 | var srcA [10]Stats 44 | 45 | b := (*[unsafe.Sizeof(srcA)]byte)(unsafe.Pointer(&srcA))[:] 46 | rand.Seed(time.Now().UnixNano()) 47 | rand.Read(b) 48 | 49 | for i := range srcA { 50 | src := &srcA[i] 51 | dst := src.Cast() 52 | if uint64(src.ipackets) != dst.Ipackets { 53 | t.Fatal("uint64(src.ipackets) != dst.Ipackets", i, uint64(src.ipackets), dst.Ipackets) 54 | } 55 | 56 | if uint64(src.opackets) != dst.Opackets { 57 | t.Fatal("uint64(src.opackets) != dst.Opackets", i, uint64(src.opackets), dst.Opackets) 58 | } 59 | 60 | if uint64(src.ibytes) != dst.Ibytes { 61 | t.Fatal("uint64(src.ibytes) != dst.Ibytes", i, uint64(src.ibytes), dst.Ibytes) 62 | } 63 | 64 | if uint64(src.obytes) != dst.Obytes { 65 | t.Fatal("uint64(src.obytes) != dst.Obytes", i, uint64(src.obytes), dst.Obytes) 66 | } 67 | 68 | if uint64(src.imissed) != dst.Imissed { 69 | t.Fatal("uint64(src.imissed) != dst.Imissed", i, uint64(src.imissed), dst.Imissed) 70 | } 71 | 72 | if uint64(src.ierrors) != dst.Ierrors { 73 | t.Fatal("uint64(src.ierrors) != dst.Ierrors", i, uint64(src.ierrors), dst.Ierrors) 74 | } 75 | 76 | if uint64(src.oerrors) != dst.Oerrors { 77 | t.Fatal("uint64(src.oerrors) != dst.Oerrors", i, uint64(src.oerrors), dst.Oerrors) 78 | } 79 | 80 | if uint64(src.rx_nombuf) != dst.RxNoMbuf { 81 | t.Fatal("uint64(src.rx_nombuf) != dst.RxNoMbuf", i, uint64(src.rx_nombuf), dst.RxNoMbuf) 82 | } 83 | } 84 | } 85 | 86 | // TestEthStats tests that EthStats can be casted to Stats. 87 | func TestEthXstat(t *testing.T) { 88 | var srcA [10]cXstat 89 | 90 | b := (*[unsafe.Sizeof(srcA)]byte)(unsafe.Pointer(&srcA))[:] 91 | rand.Seed(time.Now().UnixNano()) 92 | rand.Read(b) 93 | 94 | dstA := (*[10]Xstat)(unsafe.Pointer(&srcA)) 95 | 96 | for i := range srcA { 97 | src := &srcA[i] 98 | dst := dstA[i] 99 | if uint64(src.id) != dst.Index { 100 | t.Fatal("uint64(src.id) != dst.Index", i, uint64(src.id), dst.Index) 101 | } 102 | if uint64(src.value) != dst.Value { 103 | t.Fatal("uint64(src.value) != dst.Value", i, uint64(src.value), dst.Value) 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /ethdev/flow/item.go: -------------------------------------------------------------------------------- 1 | package flow 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | */ 8 | import "C" 9 | import "unsafe" 10 | 11 | // ItemType represents rte_flow_item type. 12 | type ItemType uint32 13 | 14 | // Reload implements ItemStruct interface. 15 | func (t ItemType) Reload() {} 16 | 17 | // Pointer implements ItemStruct interface. 18 | func (t ItemType) Pointer() unsafe.Pointer { return nil } 19 | 20 | // Type implements ItemStruct interface. 21 | func (t ItemType) Type() ItemType { return t } 22 | 23 | // Mask implements ItemStruct interface. 24 | func (t ItemType) Mask() unsafe.Pointer { return nil } 25 | 26 | // ItemStruct should be implemented to specify in Item. 27 | type ItemStruct interface { 28 | // Pointer returns a valid C pointer to underlying struct. 29 | Pointer() unsafe.Pointer 30 | 31 | // Reload is used to apply changes so that the underlying struct 32 | // reflects the up-to-date configuration. 33 | Reload() 34 | 35 | // Type returns implemented rte_flow_item_* struct. 36 | Type() ItemType 37 | 38 | // Mask returns pointer to rte_flow_item_*_mask variables. They 39 | // should not be changed by user so Mask returns pointer to C 40 | // struct. 41 | Mask() unsafe.Pointer 42 | } 43 | 44 | // Item is the matching pattern item definition. 45 | // 46 | // A pattern is formed by stacking items starting from the lowest 47 | // protocol layer to match. This stacking restriction does not apply 48 | // to meta items which can be placed anywhere in the stack without 49 | // affecting the meaning of the resulting pattern. 50 | // 51 | // Patterns are terminated by END items. 52 | // 53 | // The spec field should be a valid pointer to a structure of the 54 | // related item type. It may remain unspecified (NULL) in many cases 55 | // to request broad (nonspecific) matching. In such cases, last and 56 | // mask must also be set to NULL. 57 | // 58 | // Optionally, last can point to a structure of the same type to 59 | // define an inclusive range. This is mostly supported by integer and 60 | // address fields, may cause errors otherwise. Fields that do not 61 | // support ranges must be set to 0 or to the same value as the 62 | // corresponding fields in spec. 63 | // 64 | // Only the fields defined to nonzero values in the default masks (see 65 | // rte_flow_item_{name}_mask constants) are considered relevant by 66 | // default. This can be overridden by providing a mask structure of 67 | // the same type with applicable bits set to one. It can also be used 68 | // to partially filter out specific fields (e.g. as an alternate mean 69 | // to match ranges of IP addresses). 70 | // 71 | // Mask is a simple bit-mask applied before interpreting the contents 72 | // of spec and last, which may yield unexpected results if not used 73 | // carefully. For example, if for an IPv4 address field, spec provides 74 | // 10.1.2.3, last provides 10.3.4.5 and mask provides 255.255.0.0, the 75 | // effective range becomes 10.1.0.0 to 10.3.255.255. 76 | // 77 | // Go: you may also specify ItemType as a Spec field if you don't want 78 | // to specify any pattern for the item. 79 | type Item struct { 80 | Spec, Last, Mask ItemStruct 81 | } 82 | -------------------------------------------------------------------------------- /ring/dequeue.go: -------------------------------------------------------------------------------- 1 | package ring 2 | 3 | /* 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "ring.h" 12 | 13 | struct someptr { 14 | void *p; 15 | }; 16 | */ 17 | import "C" 18 | 19 | import ( 20 | "unsafe" 21 | ) 22 | 23 | // Dequeue dequeues single object from Ring. 24 | func (r *Ring) Dequeue() (unsafe.Pointer, bool) { 25 | objs := []unsafe.Pointer{nil} 26 | n, _ := r.DequeueBulk(objs) 27 | return objs[0], n != 0 28 | } 29 | 30 | // ScDequeue dequeues single object from Ring. 31 | func (r *Ring) ScDequeue() (unsafe.Pointer, bool) { 32 | objs := []unsafe.Pointer{nil} 33 | n, _ := r.ScDequeueBulk(objs) 34 | return objs[0], n != 0 35 | } 36 | 37 | // McDequeue dequeues single object from Ring. 38 | func (r *Ring) McDequeue() (unsafe.Pointer, bool) { 39 | objs := []unsafe.Pointer{nil} 40 | n, _ := r.McDequeueBulk(objs) 41 | return objs[0], n != 0 42 | } 43 | 44 | // McDequeueBulk dequeues objects into given slice of pointers. 45 | // Returns number of dequeued objects (either 0 or len(obj)) and 46 | // amount of remaining ring entries in the ring after the enqueue 47 | // operation has finished. 48 | func (r *Ring) McDequeueBulk(obj []unsafe.Pointer) (n, avail uint32) { 49 | return ret(C.mc_dequeue_bulk(args(r, obj))) 50 | } 51 | 52 | // ScDequeueBulk dequeues objects into given slice of pointers. 53 | // Returns number of dequeued objects (either 0 or len(obj)) and 54 | // amount of remaining ring entries in the ring after the enqueue 55 | // operation has finished. 56 | func (r *Ring) ScDequeueBulk(obj []unsafe.Pointer) (n, avail uint32) { 57 | return ret(C.sc_dequeue_bulk(args(r, obj))) 58 | } 59 | 60 | // DequeueBulk dequeues objects into given slice of pointers. 61 | // Returns number of dequeued objects (either 0 or len(obj)) and 62 | // amount of remaining ring entries in the ring after the enqueue 63 | // operation has finished. 64 | func (r *Ring) DequeueBulk(obj []unsafe.Pointer) (n, avail uint32) { 65 | return ret(C.dequeue_bulk(args(r, obj))) 66 | } 67 | 68 | // McDequeueBurst dequeues objects into given slice of pointers. 69 | // Returns number of dequeued objects and amount of remaining ring 70 | // entries in the ring after the enqueue operation has finished. 71 | // after the enqueue operation has finished. 72 | func (r *Ring) McDequeueBurst(obj []unsafe.Pointer) (n, avail uint32) { 73 | return ret(C.mc_dequeue_burst(args(r, obj))) 74 | } 75 | 76 | // ScDequeueBurst dequeues objects into given slice of pointers. 77 | // Returns number of dequeued objects and amount of remaining ring 78 | // entries in the ring after the enqueue operation has finished. 79 | // after the enqueue operation has finished. 80 | func (r *Ring) ScDequeueBurst(obj []unsafe.Pointer) (n, avail uint32) { 81 | return ret(C.sc_dequeue_burst(args(r, obj))) 82 | } 83 | 84 | // DequeueBurst dequeues objects into given slice of pointers. 85 | // Returns number of dequeued objects and amount of remaining ring 86 | // entries in the ring after the enqueue operation has finished. 87 | // after the enqueue operation has finished. 88 | func (r *Ring) DequeueBurst(obj []unsafe.Pointer) (n, avail uint32) { 89 | return ret(C.dequeue_burst(args(r, obj))) 90 | } 91 | -------------------------------------------------------------------------------- /port/port.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | /* 4 | #include 5 | #include 6 | 7 | static void * 8 | go_in_create(struct rte_port_in_ops *ops, void *arg, int socket) 9 | { 10 | return ops->f_create(arg, socket); 11 | } 12 | 13 | static int 14 | go_in_free(struct rte_port_in_ops *ops, void *port) 15 | { 16 | return ops->f_free(port); 17 | } 18 | 19 | static void * 20 | go_out_create(struct rte_port_out_ops *ops, void *arg, int socket) 21 | { 22 | return ops->f_create(arg, socket); 23 | } 24 | 25 | static int 26 | go_out_free(struct rte_port_out_ops *ops, void *port) 27 | { 28 | return ops->f_free(port); 29 | } 30 | 31 | */ 32 | import "C" 33 | import ( 34 | "unsafe" 35 | 36 | "github.com/yerden/go-dpdk/common" 37 | ) 38 | 39 | type ( 40 | // InOps is the function table which implements input port. 41 | InOps C.struct_rte_port_in_ops 42 | 43 | // In is the instantiated input port. 44 | In [0]byte 45 | ) 46 | 47 | type ( 48 | // OutOps is the function table which implements output port. 49 | OutOps C.struct_rte_port_out_ops 50 | 51 | // Out is the instantiated output port. 52 | Out [0]byte 53 | ) 54 | 55 | // InParams describes the input port interface. 56 | type InParams interface { 57 | // Returns allocated opaque structure along with its destructor. 58 | // Since InParams describes Go implementation of the port 59 | // configuration this member allocates its C counterpart as stated 60 | // in DPDK rte_port. 61 | common.Transformer 62 | 63 | // Returns pointer to associated rte_port_in_ops. 64 | InOps() *InOps 65 | } 66 | 67 | // OutParams describes configuration and behaviour of output port. 68 | type OutParams interface { 69 | // Returns allocated opaque structure argument along with its 70 | // destructor. It is used with ops function table. 71 | common.Transformer 72 | 73 | // Returns pointer to associated rte_port_out_ops. 74 | OutOps() *OutOps 75 | } 76 | 77 | var alloc = &common.StdAlloc{} 78 | 79 | // CreateIn creates input port for specified socket and configuration. 80 | // 81 | // It may return nil in case of an error. 82 | func CreateIn(socket int, params InParams) *In { 83 | ops := (*C.struct_rte_port_in_ops)(params.InOps()) 84 | 85 | arg, dtor := params.Transform(alloc) 86 | defer dtor(arg) 87 | 88 | return (*In)(C.go_in_create(ops, arg, C.int(socket))) 89 | } 90 | 91 | // Free destroys created input port. 92 | func (port *In) Free(inOps *InOps) error { 93 | ops := (*C.struct_rte_port_in_ops)(inOps) 94 | return common.IntErr(int64(C.go_in_free(ops, unsafe.Pointer(port)))) 95 | } 96 | 97 | // CreateOut creates output port for specified socket and 98 | // configuration. 99 | // 100 | // It may return nil in case of an error. 101 | func CreateOut(socket int, params OutParams) *Out { 102 | ops := (*C.struct_rte_port_out_ops)(params.OutOps()) 103 | 104 | arg, dtor := params.Transform(alloc) 105 | defer dtor(arg) 106 | 107 | return (*Out)(C.go_out_create(ops, arg, C.int(socket))) 108 | } 109 | 110 | // Free destroys created output port. 111 | func (port *Out) Free(outOps *OutOps) error { 112 | ops := (*C.struct_rte_port_out_ops)(outOps) 113 | return common.IntErr(int64(C.go_out_free(ops, unsafe.Pointer(port)))) 114 | } 115 | -------------------------------------------------------------------------------- /app/test-sniffer/init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | 8 | import ( 9 | "bytes" 10 | "flag" 11 | "fmt" 12 | 13 | "github.com/prometheus/client_golang/prometheus" 14 | "github.com/yerden/go-dpdk/eal" 15 | "github.com/yerden/go-dpdk/ethdev" 16 | "github.com/yerden/go-dpdk/mempool" 17 | ) 18 | 19 | type CmdMempool struct{} 20 | 21 | var mbufElts = flag.Int("poolMbufs", 100000, "Specify number of mbufs in mempool") 22 | var mbufSize = flag.Int("dataRoomSize", 3000, "Specify mbuf size in mempool") 23 | var poolCache = flag.Int("poolCache", 512, "Specify amount of mbufs in per-lcore cache") 24 | var rxQueues = flag.Int("nrxq", 32, "Specify number of RX queues per port") 25 | var rxDesc = flag.Int("ndesc", 256, "Specify number of RX queues per port") 26 | 27 | func (*CmdMempool) NewMempool(name string, opts []mempool.Option) (*mempool.Mempool, error) { 28 | return mempool.CreateMbufPool(name, uint32(*mbufElts), uint16(*mbufSize), opts...) 29 | } 30 | 31 | type App struct { 32 | RxqMempooler 33 | Stats *Stats 34 | Ports []ethdev.Port 35 | Work map[uint]PortQueue 36 | QCR *QueueCounterReporter 37 | } 38 | 39 | func NewApp(reg prometheus.Registerer) (*App, error) { 40 | var app *App 41 | return app, doOnMain(func() error { 42 | var err error 43 | app, err = newApp(reg) 44 | return err 45 | }) 46 | } 47 | 48 | func newApp(reg prometheus.Registerer) (*App, error) { 49 | rxqPools, err := NewMempoolPerPort("mbuf_pool", &CmdMempool{}, 50 | mempool.OptCacheSize(uint32(*poolCache)), 51 | mempool.OptOpsName("lf_stack"), 52 | mempool.OptPrivateDataSize(64), // for each mbuf 53 | ) 54 | 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | rssConf := ðdev.RssConf{ 60 | Key: bytes.Repeat([]byte{0x6D, 0x5A}, 20), 61 | Hf: C.RTE_ETH_RSS_IP, 62 | } 63 | 64 | ethdevCfg := &EthdevConfig{ 65 | Options: []ethdev.Option{ 66 | ethdev.OptRss(*rssConf), 67 | ethdev.OptRxMode(ethdev.RxMode{ 68 | MqMode: C.RTE_ETH_MQ_RX_RSS, 69 | }), 70 | }, 71 | RxQueues: uint16(*rxQueues), 72 | OnConfig: []EthdevCallback{ 73 | EthdevCallbackFunc((ethdev.Port).Start), 74 | // &RssConfig{rssConf}, 75 | }, 76 | Pooler: rxqPools, 77 | RxDescriptors: uint16(*rxDesc), 78 | FcMode: fcMode.Mode, 79 | } 80 | 81 | ports := make([]ethdev.Port, 0, ethdev.CountTotal()) 82 | 83 | for i := 0; i < cap(ports); i++ { 84 | if pid := ethdev.Port(i); pid.IsValid() { 85 | ports = append(ports, pid) 86 | } 87 | } 88 | 89 | for i := range ports { 90 | fmt.Printf("configuring port %d: %s... ", ports[i], ifaceName(ports[i])) 91 | if err := ethdevCfg.Configure(ports[i]); err != nil { 92 | fmt.Println(err) 93 | return nil, err 94 | } 95 | fmt.Println("OK") 96 | } 97 | 98 | metrics, err := NewStats(reg, ports) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | work, err := DistributeQueues(ports, eal.LcoresWorker()) 104 | if err != nil { 105 | return nil, err 106 | } 107 | 108 | app := &App{ 109 | RxqMempooler: rxqPools, 110 | Ports: ports, 111 | Stats: metrics, 112 | Work: work, 113 | QCR: &QueueCounterReporter{reg: reg}, 114 | } 115 | 116 | return app, nil 117 | } 118 | -------------------------------------------------------------------------------- /pipeline/pipeline_loop.go: -------------------------------------------------------------------------------- 1 | package pipeline 2 | 3 | /* 4 | #include "pipeline_loop.h" 5 | 6 | static int 7 | simple_control(void *params, struct rte_pipeline *p) 8 | { 9 | volatile uint8_t *stop = (uint8_t *)params; 10 | return stop[0]; 11 | } 12 | 13 | pipeline_op_ctrl go_simple_control = simple_control; 14 | 15 | */ 16 | import "C" 17 | import ( 18 | "runtime" 19 | "unsafe" 20 | 21 | "github.com/yerden/go-dpdk/common" 22 | ) 23 | 24 | // OpsCtrlF returns 0 if the pipeline continues, >0 if the pipeline 25 | // should be stopped and <0 in case of error. 26 | // 27 | // The implementation should satisfy the signature: 28 | // int (*pipeline_op_ctrl)(void *params, struct rte_pipeline *p); 29 | type OpsCtrlF C.pipeline_op_ctrl 30 | 31 | // Ops is the control operations of a pipeline tight loop. 32 | type Ops struct { 33 | Ctrl OpsCtrlF 34 | } 35 | 36 | // Controller controls execution of pipeline loop. 37 | type Controller interface { 38 | // Transformer is implemented to reflect the configuration into C 39 | // memory. 40 | common.Transformer 41 | 42 | // Ops returns the C implementation of hooks for controlling 43 | // pipeline execution. 44 | Ops() *Ops 45 | } 46 | 47 | // RunLoop runs pipeline continuously, flushing it every 'flush' 48 | // iterations. RunLoop returns if value referenced by 'stop' is set to 49 | // non-zero value. 50 | // 51 | // flush must be power of 2. 52 | func (pl *Pipeline) RunLoop(flush uint32, c Controller) error { 53 | if flush&(flush-1) != 0 { 54 | panic("flush should be power of 2") 55 | } 56 | 57 | params := &C.struct_lcore_arg{} 58 | 59 | if c != nil { 60 | arg, dtor := c.Transform(alloc) 61 | defer dtor(arg) 62 | params.ops_arg = arg 63 | ops := c.Ops() 64 | params.ops.f_ctrl = (C.pipeline_op_ctrl)(ops.Ctrl) 65 | } 66 | 67 | params.p = (*C.struct_rte_pipeline)(pl) 68 | params.flush = C.uint32_t(flush) 69 | 70 | return common.IntErr(int64(C.run_pipeline_loop(unsafe.Pointer(params)))) 71 | } 72 | 73 | // SimpleController implements Controller for pipeline control. 74 | // 75 | // It allocates a single byte which serves as a stop flag for pipeline 76 | // loop. Empty SimpleController is not usable, please use 77 | // NewSimpleController to create SimpleController instance. 78 | type SimpleController struct { 79 | stop unsafe.Pointer 80 | } 81 | 82 | // NewSimpleController allocates new SimpleController. 83 | func NewSimpleController() *SimpleController { 84 | c := &SimpleController{} 85 | c.stop = alloc.Malloc(1) 86 | *(*uint8)(c.stop) = 0 87 | 88 | runtime.SetFinalizer(c, func(c *SimpleController) { 89 | alloc.Free(c.stop) 90 | }) 91 | 92 | return c 93 | } 94 | 95 | // Transform implements common.Transformer interface. 96 | func (c *SimpleController) Transform(alloc common.Allocator) (unsafe.Pointer, func(unsafe.Pointer)) { 97 | return c.stop, func(unsafe.Pointer) {} 98 | } 99 | 100 | // Ops implements Controller interface. 101 | func (c *SimpleController) Ops() *Ops { 102 | return &Ops{ 103 | Ctrl: OpsCtrlF(C.go_simple_control), 104 | } 105 | } 106 | 107 | // Stop stops the execution of a pipeline that watches this 108 | // controller. It may be used by multiple pipelines. 109 | func (c *SimpleController) Stop() { 110 | *(*uint8)(c.stop) = 1 111 | } 112 | -------------------------------------------------------------------------------- /common/set.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "reflect" 5 | "sort" 6 | ) 7 | 8 | var ( 9 | hexMap = []byte{ 10 | '0', '1', '2', '3', '4', '5', '6', '7', 11 | '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 12 | } 13 | ) 14 | 15 | func getHexByte(n int) byte { return hexMap[n] } 16 | func getHexIndex(c byte) int { 17 | for i, x := range hexMap { 18 | if x == c { 19 | return i 20 | } 21 | } 22 | return 0 23 | } 24 | 25 | func hex(a []int) string { 26 | var out []byte 27 | for _, n := range a { 28 | i, r := n/4, uint(n&3) 29 | if i >= len(out) { 30 | add := make([]byte, i-len(out)+1) 31 | for k := range add { 32 | add[k] = '0' 33 | } 34 | out = append(add, out...) 35 | } 36 | i = len(out) - 1 - i 37 | out[i] = getHexByte(getHexIndex(out[i]) | (1 << r)) 38 | } 39 | 40 | return string(out) 41 | } 42 | 43 | // Set represents a set of integer numbers. 44 | type Set interface { 45 | // IsSet tests if given int is inside the Set. 46 | IsSet(int) bool 47 | // Count returns the number of integers in the Set. 48 | Count() int 49 | // Set stores integer in the Set 50 | Set(int) 51 | } 52 | 53 | // Map is an []int array-based implementation of a Set. 54 | type Map struct { 55 | array []int 56 | } 57 | 58 | func (m *Map) find(n int) (int, bool) { 59 | k := sort.SearchInts(m.array, n) 60 | return k, k < len(m.array) && m.array[k] == n 61 | } 62 | 63 | // Set implements Set interface. 64 | func (m *Map) Set(n int) { 65 | if k, ok := m.find(n); !ok { 66 | m.array = append(m.array, n) 67 | copy(m.array[k+1:], m.array[k:]) 68 | m.array[k] = n 69 | } 70 | } 71 | 72 | // IsSet implements Set interface. 73 | func (m *Map) IsSet(n int) bool { 74 | _, ok := m.find(n) 75 | return ok 76 | } 77 | 78 | // Zero zeroes out Map. 79 | func (m *Map) Zero() { 80 | m.array = m.array[:0] 81 | } 82 | 83 | // Count implements Set interface. 84 | func (m *Map) Count() int { 85 | return len(m.array) 86 | } 87 | 88 | // String implements fmt.Stringer interface. 89 | func (m *Map) String() string { 90 | return hex(m.array) 91 | } 92 | 93 | // copySet copies non-negative members of src to dst. 94 | func copySet(dst, src Set) int { 95 | var n int 96 | for i := 0; n < src.Count(); i++ { 97 | if src.IsSet(i) { 98 | dst.Set(i) 99 | n++ 100 | } 101 | } 102 | return n 103 | } 104 | 105 | // NewMap creates instance of a Map. 106 | // 107 | // i may represent a Set, an array or a slice of integers, a map with 108 | // integer keys. Otherwise, the function would panic. 109 | func NewMap(i interface{}) *Map { 110 | m := &Map{} 111 | 112 | if i == nil { 113 | return m 114 | } 115 | 116 | if s, ok := i.(Set); ok { 117 | copySet(m, s) 118 | return m 119 | } 120 | 121 | intType := reflect.ValueOf(int(0)).Type() 122 | 123 | v := reflect.ValueOf(i) 124 | switch v.Kind() { 125 | case reflect.Map: 126 | keys := v.MapKeys() 127 | for _, k := range keys { 128 | m.Set(int(k.Convert(intType).Int())) 129 | } 130 | case reflect.Array: 131 | fallthrough 132 | case reflect.Slice: 133 | for n := 0; n < v.Len(); n++ { 134 | elem := v.Index(n).Convert(intType).Int() 135 | m.Set(int(elem)) 136 | } 137 | default: 138 | elem := v.Convert(intType).Int() 139 | m.Set(int(elem)) 140 | } 141 | return m 142 | } 143 | -------------------------------------------------------------------------------- /lpm/lpm6.go: -------------------------------------------------------------------------------- 1 | package lpm 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | */ 8 | import "C" 9 | 10 | import ( 11 | "net/netip" 12 | "unsafe" 13 | 14 | "github.com/yerden/go-dpdk/common" 15 | ) 16 | 17 | // LPM6 is an RTE Longest Prefix Match lookup object. 18 | type LPM6 C.struct_rte_lpm6 19 | 20 | // Config6 is used to configure LPM6 object while creation. 21 | type Config6 struct { 22 | MaxRules uint32 23 | NumberTbl8s uint32 24 | Flags int 25 | } 26 | 27 | // Create6 an LPM6 object. 28 | // 29 | // Specify name as an LPM6 object name, socket_id as a NUMA socket ID 30 | // for LPM6 table memory allocation and config as a structure 31 | // containing the configuration. 32 | // 33 | // Returns handle to LPM6 object on success, and errno value: 34 | // 35 | // E_RTE_NO_CONFIG - function could not get pointer to rte_config structure 36 | // E_RTE_SECONDARY - function was called from a secondary process instance 37 | // EINVAL - invalid parameter passed to function 38 | // ENOSPC - the maximum number of memzones has already been allocated 39 | // EEXIST - a memzone with the same name already exists 40 | // ENOMEM - no appropriate memory area found in which to create memzone 41 | func Create6(name string, socket int, cfg *Config6) (*LPM6, error) { 42 | s := C.CString(name) 43 | defer C.free(unsafe.Pointer(s)) 44 | config := &C.struct_rte_lpm6_config{ 45 | max_rules: C.uint32_t(cfg.MaxRules), 46 | number_tbl8s: C.uint32_t(cfg.NumberTbl8s), 47 | flags: C.int(cfg.Flags), 48 | } 49 | 50 | if r := (*LPM6)(C.rte_lpm6_create(s, C.int(socket), config)); r != nil { 51 | return r, nil 52 | } 53 | 54 | return nil, common.RteErrno() 55 | } 56 | 57 | // Free an LPM6 object. 58 | func (r *LPM6) Free() { 59 | C.rte_lpm6_free((*C.struct_rte_lpm6)(r)) 60 | } 61 | 62 | // Add a rule to LPM6 object. 63 | // 64 | // ip/prefix is an IP address/subnet to add, nextHop is a value 65 | // associated with added IP subnet. Panics if ip is not IPv6. 66 | func (r *LPM6) Add(ipnet netip.Prefix, nextHop uint32) error { 67 | b, prefix := cvtIPv6Net(ipnet) 68 | rc := C.rte_lpm6_add((*C.struct_rte_lpm6)(r), (*C.uint8_t)(&b[0]), C.uint8_t(prefix), C.uint32_t(nextHop)) 69 | return common.IntToErr(rc) 70 | } 71 | 72 | // Delete a rule from LPM6 object. Panics if ip is not IPv6. 73 | func (r *LPM6) Delete(ipnet netip.Prefix) error { 74 | b, prefix := cvtIPv6Net(ipnet) 75 | rc := C.rte_lpm6_delete((*C.struct_rte_lpm6)(r), (*C.uint8_t)(&b[0]), C.uint8_t(prefix)) 76 | return common.IntToErr(rc) 77 | } 78 | 79 | // Lookup an IP in LPM6 object. Panics if ip is not IPv6. 80 | func (r *LPM6) Lookup(ip netip.Addr) (uint32, error) { 81 | var res uint32 82 | ip16 := ip.As16() 83 | rc := C.rte_lpm6_lookup((*C.struct_rte_lpm6)(r), (*C.uint8_t)(&ip16[0]), (*C.uint32_t)(&res)) 84 | return res, common.IntToErr(rc) 85 | } 86 | 87 | // DeleteAll removes all rules from LPM6 object. 88 | func (r *LPM6) DeleteAll() { 89 | C.rte_lpm6_delete_all((*C.struct_rte_lpm6)(r)) 90 | } 91 | 92 | // IsRulePresent checks if a rule present in the LPM6 and returns 93 | // nextHop if it is. Panics if ip is not IPv6. 94 | func (r *LPM6) IsRulePresent(ipnet netip.Prefix, nextHop *uint32) (bool, error) { 95 | b, prefix := cvtIPv6Net(ipnet) 96 | rc := C.rte_lpm6_is_rule_present((*C.struct_rte_lpm6)(r), (*C.uint8_t)(&b[0]), C.uint8_t(prefix), (*C.uint32_t)(nextHop)) 97 | n, err := common.IntOrErr(rc) 98 | return n != 0, err 99 | } 100 | -------------------------------------------------------------------------------- /memzone/memzone_test.go: -------------------------------------------------------------------------------- 1 | package memzone_test 2 | 3 | import ( 4 | "syscall" 5 | "testing" 6 | 7 | "github.com/yerden/go-dpdk/common" 8 | "github.com/yerden/go-dpdk/eal" 9 | "github.com/yerden/go-dpdk/memzone" 10 | ) 11 | 12 | func TestMemzoneCreateErr(t *testing.T) { 13 | assert := common.Assert(t, true) 14 | 15 | // Initialize EAL on all cores 16 | eal.InitOnceSafe("test", 4) 17 | 18 | var mz *memzone.Memzone 19 | var err error 20 | // create and test mempool on main lcore 21 | execErr := eal.ExecOnMain(func(ctx *eal.LcoreCtx) { 22 | // create empty mempool 23 | n := 100000000 24 | mz, err = memzone.Reserve("test_mz", 25 | uintptr(n), // size of zone 26 | memzone.OptSocket(10), // incorrect, ctx.SocketID()), 27 | memzone.OptFlag(memzone.PageSizeHintOnly)) 28 | }) 29 | if eal.HasHugePages() { 30 | assert(mz == nil && err == syscall.ENOMEM, mz, err) 31 | } else { 32 | assert(mz != nil && err == nil) 33 | mz.Free() 34 | } 35 | assert(execErr == nil, execErr) 36 | } 37 | 38 | func TestMemzoneCreate(t *testing.T) { 39 | assert := common.Assert(t, true) 40 | 41 | // Initialize EAL on all cores 42 | eal.InitOnceSafe("test", 4) 43 | 44 | // create and test mempool on main lcore 45 | err := eal.ExecOnMain(func(ctx *eal.LcoreCtx) { 46 | // create empty mempool 47 | n := 100000000 48 | mz, err := memzone.Reserve("test_mz", 49 | uintptr(n), // size of zone 50 | memzone.OptSocket(eal.SocketID()), 51 | memzone.OptFlag(memzone.PageSizeHintOnly)) 52 | assert(mz != nil && err == nil, err) 53 | 54 | mz1, err := memzone.Lookup("test_mz") 55 | assert(mz == mz1 && err == nil) 56 | 57 | err = mz.Free() 58 | assert(err == nil, err) 59 | 60 | _, err = memzone.Lookup("test_mz") 61 | assert(err != nil) 62 | 63 | }) 64 | assert(err == nil, err) 65 | } 66 | 67 | func TestMemzoneWriteTo(t *testing.T) { 68 | assert := common.Assert(t, true) 69 | 70 | // Initialize EAL on all cores 71 | eal.InitOnceSafe("test", 4) 72 | 73 | // create and test mempool on main lcore 74 | err := eal.ExecOnMain(func(ctx *eal.LcoreCtx) { 75 | // create empty mempool 76 | n := 100000000 77 | mz, err := memzone.Reserve("test_mz", 78 | uintptr(n), // size of zone 79 | memzone.OptSocket(eal.SocketID()), 80 | memzone.OptFlag(memzone.PageSizeHintOnly)) 81 | assert(mz != nil && err == nil, err) 82 | defer mz.Free() 83 | 84 | b := mz.Bytes() 85 | assert(len(b) == n) 86 | assert(n == copy(b, make([]byte, n))) 87 | assert("test_mz" == mz.Name()) 88 | }) 89 | assert(err == nil, err) 90 | } 91 | 92 | func TestMemzoneAligned(t *testing.T) { 93 | assert := common.Assert(t, true) 94 | 95 | // Initialize EAL on all cores 96 | eal.InitOnceSafe("test", 4) 97 | 98 | // create and test mempool on main lcore 99 | err := eal.ExecOnMain(func(ctx *eal.LcoreCtx) { 100 | // create empty mempool 101 | n := 100000000 102 | mz, err := memzone.Reserve("test_mz", 103 | uintptr(n), // size of zone 104 | memzone.OptSocket(eal.SocketID()), 105 | memzone.OptFlag(memzone.PageSizeHintOnly), 106 | memzone.OptAligned(1024)) 107 | assert(mz != nil && err == nil, err) 108 | defer mz.Free() 109 | 110 | b := mz.Bytes() 111 | assert(len(b) == n) 112 | assert(n == copy(b, make([]byte, n))) 113 | assert("test_mz" == mz.Name()) 114 | 115 | var mz1 *memzone.Memzone 116 | memzone.Walk(func(mz *memzone.Memzone) { 117 | if mz.Name() == "test_mz" { 118 | mz1 = mz 119 | } 120 | }) 121 | assert(mz == mz1, mz1) 122 | }) 123 | assert(err == nil, err) 124 | } 125 | -------------------------------------------------------------------------------- /ethdev/flow/item_eth.go: -------------------------------------------------------------------------------- 1 | package flow 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | enum { 11 | ITEM_ETH_OFF_HDR = offsetof(struct rte_flow_item_eth, hdr), 12 | }; 13 | 14 | // Ethernet header fields renamed since commit: 04d43857ea3acbd4db4b28939dc2807932b85e72. 15 | #if RTE_VERSION < RTE_VERSION_NUM(21, 11, 0, 0) 16 | enum { 17 | ETHER_HDR_OFF_SRC = offsetof(struct rte_ether_hdr, s_addr), 18 | ETHER_HDR_OFF_DST = offsetof(struct rte_ether_hdr, d_addr), 19 | }; 20 | #else 21 | enum { 22 | ETHER_HDR_OFF_SRC = offsetof(struct rte_ether_hdr, src_addr), 23 | ETHER_HDR_OFF_DST = offsetof(struct rte_ether_hdr, dst_addr), 24 | }; 25 | #endif 26 | 27 | void set_has_vlan(struct rte_flow_item_eth *item, uint32_t b) { 28 | item->has_vlan = b; 29 | } 30 | 31 | static const struct rte_flow_item_eth *get_item_eth_mask() { 32 | return &rte_flow_item_eth_mask; 33 | } 34 | 35 | */ 36 | import "C" 37 | import ( 38 | "net" 39 | "reflect" 40 | "runtime" 41 | "unsafe" 42 | ) 43 | 44 | var _ ItemStruct = (*ItemEth)(nil) 45 | 46 | // ItemEth matches an Ethernet header. 47 | // 48 | // Inside hdr field, the sub-field ether_type stands either for 49 | // EtherType or TPID, depending on whether the item is followed by a 50 | // VLAN item or not. If two VLAN items follow, the sub-field refers to 51 | // the outer one, which, in turn, contains the inner TPID in the 52 | // similar header field. The innermost VLAN item contains a layer-3 53 | // EtherType. All of that follows the order seen on the wire. 54 | // 55 | // If the field in question contains a TPID value, only tagged packets 56 | // with the specified TPID will match the pattern. Alternatively, it's 57 | // possible to match any type of tagged packets by means of the field 58 | // has_vlan rather than use the EtherType/TPID field. Also, it's 59 | // possible to leave the two fields unused. If this is the case, both 60 | // tagged and untagged packets will match the pattern. 61 | type ItemEth struct { 62 | cPointer 63 | HasVlan bool 64 | Src, Dst net.HardwareAddr 65 | EtherType uint16 66 | } 67 | 68 | // Reload implements ItemStruct interface. 69 | func (item *ItemEth) Reload() { 70 | cptr := (*C.struct_rte_flow_item_eth)(item.createOrRet(C.sizeof_struct_rte_flow_item_eth)) 71 | 72 | var u uint32 73 | if item.HasVlan { 74 | u = 1 75 | } 76 | C.set_has_vlan(cptr, C.uint32_t(u)) 77 | 78 | hdr := (*C.struct_rte_ether_hdr)(off(unsafe.Pointer(cptr), C.ITEM_ETH_OFF_HDR)) 79 | 80 | if len(item.Src) > 0 { 81 | p := off(unsafe.Pointer(hdr), C.ETHER_HDR_OFF_SRC) 82 | setAddr((*C.struct_rte_ether_addr)(p), item.Src) 83 | } 84 | 85 | if len(item.Dst) > 0 { 86 | p := off(unsafe.Pointer(hdr), C.ETHER_HDR_OFF_DST) 87 | setAddr((*C.struct_rte_ether_addr)(p), item.Dst) 88 | } 89 | 90 | beU16(item.EtherType, unsafe.Pointer(&hdr.ether_type)) 91 | 92 | runtime.SetFinalizer(item, (*ItemEth).free) 93 | } 94 | 95 | func setAddr(p *C.struct_rte_ether_addr, addr net.HardwareAddr) { 96 | var hwaddr []byte 97 | sh := (*reflect.SliceHeader)(unsafe.Pointer(&hwaddr)) 98 | sh.Data = uintptr(unsafe.Pointer(&p.addr_bytes[0])) 99 | sh.Len = len(p.addr_bytes) 100 | sh.Cap = sh.Len 101 | copy(hwaddr, addr) 102 | } 103 | 104 | // Type implements ItemStruct interface. 105 | func (item *ItemEth) Type() ItemType { 106 | return ItemTypeEth 107 | } 108 | 109 | // Mask implements ItemStruct interface. 110 | func (item *ItemEth) Mask() unsafe.Pointer { 111 | return unsafe.Pointer(C.get_item_eth_mask()) 112 | } 113 | -------------------------------------------------------------------------------- /app/test-sniffer/lcores.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/yerden/go-dpdk/eal" 10 | "github.com/yerden/go-dpdk/ethdev" 11 | "github.com/yerden/go-dpdk/util" 12 | ) 13 | 14 | var burstSize = flag.Int("burst", 256, "Specify RX burst size") 15 | var printMetadata = flag.Bool("print", false, "Specify to print each packet's metadata") 16 | var dryRun = flag.Bool("dryRun", false, "If true traffic will not be processed") 17 | 18 | // PortQueue describes port and rx queue id. 19 | type PortQueue struct { 20 | Pid ethdev.Port 21 | Qid uint16 22 | } 23 | 24 | // dissect all given lcores and store them into map hashed by affine 25 | // socket id. 26 | func dissectLcores(lcores []uint) map[uint][]uint { 27 | table := map[uint][]uint{} 28 | 29 | for _, lcore := range lcores { 30 | socket := eal.LcoreToSocket(lcore) 31 | 32 | if affine, ok := table[socket]; !ok { 33 | table[socket] = []uint{lcore} 34 | } else { 35 | table[socket] = append(affine, lcore) 36 | } 37 | } 38 | 39 | return table 40 | } 41 | 42 | // DistributeQueues assigns all RX queues for each port in ports to 43 | // lcores. Assignment is NUMA-aware. 44 | // 45 | // Returns os.ErrInvalid if port id is invalid. 46 | // Returns os.ErrNotExist if no lcores are available by NUMA 47 | // constraints. 48 | func DistributeQueues(ports []ethdev.Port, lcores []uint) (map[uint]PortQueue, error) { 49 | table := map[uint]PortQueue{} 50 | lcoreMap := dissectLcores(lcores) 51 | 52 | for _, pid := range ports { 53 | if err := distributeQueuesPort(pid, lcoreMap, table); err != nil { 54 | return nil, err 55 | } 56 | } 57 | 58 | return table, nil 59 | } 60 | 61 | func distributeQueuesPort(pid ethdev.Port, lcoreMap map[uint][]uint, table map[uint]PortQueue) error { 62 | var info ethdev.DevInfo 63 | 64 | if err := pid.InfoGet(&info); err != nil { 65 | return err 66 | } 67 | 68 | socket := pid.SocketID() 69 | if socket < 0 { 70 | return os.ErrInvalid 71 | } 72 | 73 | lcores, ok := lcoreMap[uint(socket)] 74 | if !ok { 75 | fmt.Println("no lcores for socket:", socket) 76 | return os.ErrNotExist 77 | } 78 | 79 | nrx := info.NbRxQueues() 80 | if nrx == 0 { 81 | return os.ErrClosed 82 | } 83 | 84 | if int(nrx) > len(lcores) { 85 | return fmt.Errorf("pid=%d nrx=%d cannot run on %d lcores", pid, nrx, len(lcores)) 86 | } 87 | 88 | var lcore uint 89 | var acquired util.LcoresList 90 | for i := uint16(0); i < nrx; i++ { 91 | lcore, lcores = lcores[0], lcores[1:] 92 | acquired = append(acquired, lcore) 93 | lcoreMap[uint(socket)] = lcores 94 | table[lcore] = PortQueue{Pid: pid, Qid: i} 95 | } 96 | 97 | fmt.Printf("pid=%d runs on socket=%d, lcores=%v\n", pid, socket, util.LcoresList(acquired)) 98 | 99 | return nil 100 | } 101 | 102 | func LcoreFunc(pq PortQueue, qcr *QueueCounterReporter) func(*eal.LcoreCtx) { 103 | return func(ctx *eal.LcoreCtx) { 104 | defer log.Println("lcore", eal.LcoreID(), "exited") 105 | 106 | if *dryRun { 107 | return 108 | } 109 | // eal 110 | pid := pq.Pid 111 | qid := pq.Qid 112 | qc := qcr.Register(pid, qid) 113 | 114 | src := util.NewEthdevMbufArray(pid, qid, int(eal.SocketID()), uint16(*burstSize)) 115 | defer src.Free() 116 | 117 | buf := src.Buffer() 118 | 119 | log.Printf("processing pid=%d, qid=%d, lcore=%d\n", pid, qid, eal.LcoreID()) 120 | for { 121 | n := src.Recharge() 122 | 123 | for i := 0; i < n; i++ { 124 | data := buf[i].Data() 125 | 126 | if *printMetadata { 127 | fmt.Printf("packet: %d bytes\n", len(data)) 128 | } 129 | } 130 | 131 | qc.Incr(buf[:n]) 132 | } 133 | 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /ethdev/flow/attr.go: -------------------------------------------------------------------------------- 1 | package flow 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | 8 | void set_flow_attr_ingress(struct rte_flow_attr *attr) { 9 | attr->ingress = 1; 10 | } 11 | 12 | void set_flow_attr_egress(struct rte_flow_attr *attr) { 13 | attr->egress = 1; 14 | } 15 | 16 | void set_flow_attr_transfer(struct rte_flow_attr *attr) { 17 | attr->transfer = 1; 18 | } 19 | */ 20 | import "C" 21 | 22 | // Attr is Flow rule attributes. 23 | // 24 | // Priorities are set on a per rule based within groups. 25 | // 26 | // Lower values denote higher priority, the highest priority for a 27 | // flow rule is 0, so that a flow that matches for than one rule, the 28 | // rule with the lowest priority value will always be matched. 29 | // 30 | // Although optional, applications are encouraged to group similar 31 | // rules as much as possible to fully take advantage of hardware 32 | // capabilities (e.g. optimized matching) and work around limitations 33 | // (e.g. a single pattern type possibly allowed in a given group). 34 | // Applications should be aware that groups are not linked by default, 35 | // and that they must be explicitly linked by the application using 36 | // the JUMP action. 37 | // 38 | // Priority levels are arbitrary and up to the application, they do 39 | // not need to be contiguous nor start from 0, however the maximum 40 | // number varies between devices and may be affected by existing flow 41 | // rules. 42 | // 43 | // If a packet is matched by several rules of a given group for a 44 | // given priority level, the outcome is undefined. It can take any 45 | // path, may be duplicated or even cause unrecoverable errors. 46 | // 47 | // Note that support for more than a single group and priority level 48 | // is not guaranteed. 49 | // 50 | // Flow rules can apply to inbound and/or outbound traffic 51 | // (ingress/egress). 52 | // 53 | // Several pattern items and actions are valid and can be used in both 54 | // directions. Those valid for only one direction are described as 55 | // such. 56 | // 57 | // At least one direction must be specified. 58 | // 59 | // Specifying both directions at once for a given rule is not 60 | // recommended but may be valid in a few cases (e.g. shared counter). 61 | type Attr struct { 62 | // Priority group. 63 | Group uint32 64 | 65 | // Rule priority level within group. 66 | Priority uint32 67 | 68 | // Rule applies to ingress traffic. 69 | Ingress bool 70 | 71 | // Rule applies to egress traffic. 72 | Egress bool 73 | 74 | // Instead of simply matching the properties of traffic as it 75 | // would appear on a given DPDK port ID, enabling this attribute 76 | // transfers a flow rule to the lowest possible level of any 77 | // device endpoints found in the pattern. 78 | // 79 | // When supported, this effectively enables an application to 80 | // re-route traffic not necessarily intended for it (e.g. coming 81 | // from or addressed to different physical ports, VFs or 82 | // applications) at the device level. 83 | // 84 | // It complements the behavior of some pattern items such as 85 | // RTE_FLOW_ITEM_TYPE_PHY_PORT and is meaningless without them. 86 | // 87 | // When transferring flow rules, ingress and egress attributes 88 | // keep their original meaning, as if processing traffic emitted 89 | // or received by the application. 90 | Transfer bool 91 | } 92 | 93 | func (a *Attr) cvtAttr() (out C.struct_rte_flow_attr) { 94 | out.group = C.uint32_t(a.Group) 95 | out.priority = C.uint32_t(a.Priority) 96 | if a.Ingress { 97 | C.set_flow_attr_ingress(&out) 98 | } 99 | if a.Egress { 100 | C.set_flow_attr_egress(&out) 101 | } 102 | if a.Transfer { 103 | C.set_flow_attr_transfer(&out) 104 | } 105 | return 106 | } 107 | -------------------------------------------------------------------------------- /app/test-sniffer/util.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "os" 8 | "strings" 9 | 10 | "github.com/yerden/go-dpdk/eal" 11 | "github.com/yerden/go-dpdk/ethdev" 12 | "github.com/yerden/go-dpdk/ethdev/flow" 13 | ) 14 | 15 | func doOnMain(fn func() error) error { 16 | var err error 17 | e := eal.ExecOnMain(func(*eal.LcoreCtx) { 18 | err = fn() 19 | }) 20 | if e != nil { 21 | return e 22 | } 23 | 24 | return err 25 | } 26 | 27 | // generate queues array [0..n) 28 | func queuesSeq(n int) []uint16 { 29 | q := make([]uint16, n) 30 | 31 | for i := range q { 32 | q[i] = uint16(i) 33 | } 34 | 35 | return q 36 | } 37 | 38 | func failOnErr(err error) { 39 | if err != nil { 40 | var e *eal.ErrLcorePanic 41 | if errors.As(err, &e) { 42 | e.FprintStack(os.Stdout) 43 | } 44 | log.Fatal(err) 45 | } 46 | } 47 | 48 | func driverName(pid ethdev.Port) string { 49 | var devInfo ethdev.DevInfo 50 | if err := pid.InfoGet(&devInfo); err != nil { 51 | return "" 52 | } 53 | return devInfo.DriverName() 54 | } 55 | 56 | func rssEthVlanIPv4(pid ethdev.Port, conf *ethdev.RssConf) (*flow.Flow, error) { 57 | attr := &flow.Attr{Ingress: true} 58 | 59 | pattern := []flow.Item{ 60 | {Spec: flow.ItemTypeEth}, // Ethernet 61 | {Spec: flow.ItemTypeVlan}, // VLAN 62 | {Spec: flow.ItemTypeIPv4}, // IPv4 63 | } 64 | 65 | actions := []flow.Action{ 66 | &flow.ActionRSS{ 67 | Types: conf.Hf, 68 | Func: flow.HashFunctionToeplitz, 69 | }, 70 | } 71 | 72 | e := &flow.Error{} 73 | if err := flow.Validate(pid, attr, pattern, actions, e); err == nil { 74 | if f, err := flow.Create(pid, attr, pattern, actions, e); err == nil { 75 | return f, nil 76 | } 77 | } 78 | 79 | return nil, e 80 | } 81 | 82 | func mlxRssEthVlanIPv4(pid ethdev.Port, conf *ethdev.RssConf) (*flow.Flow, error) { 83 | attr := &flow.Attr{Ingress: true} 84 | 85 | pattern := []flow.Item{ 86 | {Spec: flow.ItemTypeEth}, // Ethernet 87 | {Spec: flow.ItemTypeVlan}, // VLAN 88 | {Spec: flow.ItemTypeIPv4}, // IPv4 89 | } 90 | 91 | var info ethdev.DevInfo 92 | if err := pid.InfoGet(&info); err != nil { 93 | return nil, err 94 | } 95 | 96 | actions := []flow.Action{ 97 | &flow.ActionRSS{ 98 | Types: conf.Hf, 99 | Key: conf.Key, 100 | Queues: queuesSeq(int(info.NbRxQueues())), 101 | Func: flow.HashFunctionToeplitz, 102 | }, 103 | } 104 | 105 | e := &flow.Error{} 106 | if err := flow.Validate(pid, attr, pattern, actions, e); err == nil { 107 | if f, err := flow.Create(pid, attr, pattern, actions, e); err == nil { 108 | return f, nil 109 | } 110 | } 111 | 112 | return nil, e 113 | } 114 | 115 | type RssConfig struct { 116 | Conf *ethdev.RssConf 117 | } 118 | 119 | func (c *RssConfig) EthdevCall(pid ethdev.Port) error { 120 | var err error 121 | switch driverName(pid) { 122 | case "mlx5_pci": 123 | _, err = mlxRssEthVlanIPv4(pid, c.Conf) 124 | case "net_af_packet": 125 | fallthrough 126 | case "net_ice": 127 | _, err = rssEthVlanIPv4(pid, c.Conf) 128 | default: 129 | fmt.Println("no RSS configured") 130 | } 131 | 132 | return err 133 | } 134 | 135 | var fcModes = map[string]uint32{ 136 | "none": ethdev.FcNone, 137 | "rxpause": ethdev.FcRxPause, 138 | "txpause": ethdev.FcTxPause, 139 | "full": ethdev.FcFull, 140 | } 141 | 142 | type FcModeFlag struct { 143 | Mode uint32 144 | } 145 | 146 | func (fc *FcModeFlag) Set(s string) error { 147 | var ok bool 148 | fc.Mode, ok = fcModes[strings.ToLower(s)] 149 | if !ok { 150 | return fmt.Errorf("invalid Flow Control mode: %s", s) 151 | } 152 | 153 | return nil 154 | } 155 | 156 | func (fc *FcModeFlag) String() string { 157 | for desc, mode := range fcModes { 158 | if mode == fc.Mode { 159 | return desc 160 | } 161 | } 162 | 163 | return fmt.Sprintf("mode=%d", fc.Mode) 164 | } 165 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 2 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 3 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 4 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 5 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 9 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 10 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 11 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 12 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 13 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 14 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 15 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 16 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 17 | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= 18 | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 19 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 20 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 21 | github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= 22 | github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= 23 | github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= 24 | github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= 25 | github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= 26 | github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= 27 | github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= 28 | github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= 29 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 30 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 31 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 32 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 33 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= 34 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 35 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 36 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 37 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 38 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 39 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 40 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 41 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 42 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 43 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 44 | -------------------------------------------------------------------------------- /table/hash.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | rte_hash_function go_crc32f = rte_hash_crc; 10 | 11 | */ 12 | import "C" 13 | 14 | import ( 15 | "unsafe" 16 | 17 | "github.com/yerden/go-dpdk/common" 18 | ) 19 | 20 | var ( 21 | // Crc32Hash is the DPDK CRC32 hash function. 22 | Crc32Hash HashFunc = (HashFunc)(C.go_crc32f) 23 | ) 24 | 25 | // HashOpHash is the signature of hash function used for Hash table 26 | // implementation. 27 | type HashOpHash C.rte_table_hash_op_hash 28 | 29 | // HashFunc is the signature of hash function used for Cuckoo-Hash 30 | // table implementation. 31 | type HashFunc C.rte_hash_function 32 | 33 | // Table ops implementations. 34 | var ( 35 | HashExtOps = (*Ops)(&C.rte_table_hash_ext_ops) 36 | HashLruOps = (*Ops)(&C.rte_table_hash_lru_ops) 37 | HashCuckooOps = (*Ops)(&C.rte_table_hash_cuckoo_ops) 38 | ) 39 | 40 | // HashParams is the Hash table parameters. 41 | type HashParams struct { 42 | // Set this to desired Hash*Ops variable. 43 | TableOps *Ops 44 | 45 | // Name. 46 | Name string 47 | 48 | // Key size (number of bytes). 49 | KeySize uint32 50 | 51 | // Byte offset within packet meta-data where the key is located 52 | KeyOffset uint32 53 | 54 | // Key mask. 55 | KeyMask []byte 56 | 57 | // Number of keys. 58 | KeysNum uint32 59 | 60 | // Number of buckets. 61 | BucketsNum uint32 62 | 63 | // Hash function. 64 | Hash struct { 65 | Func HashOpHash 66 | Seed uint64 67 | } 68 | 69 | // Hash function for HashCuckooOps. 70 | CuckooHash struct { 71 | Func HashFunc 72 | Seed uint32 73 | } 74 | } 75 | 76 | // Transform implements common.Transformer interface. 77 | func (p *HashParams) Transform(alloc common.Allocator) (unsafe.Pointer, func(unsafe.Pointer)) { 78 | switch p.TableOps { 79 | case HashExtOps: 80 | fallthrough 81 | case HashLruOps: 82 | return p.tformToOrdinary(alloc) 83 | case HashCuckooOps: 84 | return p.tformToCuckoo(alloc) 85 | } 86 | 87 | panic("unsupported Hash Table ops") 88 | } 89 | 90 | func (p *HashParams) tformToOrdinary(alloc common.Allocator) (unsafe.Pointer, func(unsafe.Pointer)) { 91 | var params *C.struct_rte_table_hash_params 92 | params = (*C.struct_rte_table_hash_params)(alloc.Malloc(unsafe.Sizeof(*params))) 93 | params.name = (*C.char)(common.CString(alloc, p.Name)) 94 | params.key_size = C.uint32_t(p.KeySize) 95 | params.key_offset = C.uint32_t(p.KeyOffset) 96 | params.key_mask = (*C.uint8_t)(common.CBytes(alloc, p.KeyMask)) 97 | params.n_keys = C.uint32_t(p.KeysNum) 98 | params.n_buckets = C.uint32_t(p.BucketsNum) 99 | params.f_hash = C.rte_table_hash_op_hash(p.Hash.Func) 100 | params.seed = C.uint64_t(p.Hash.Seed) 101 | return unsafe.Pointer(params), func(p unsafe.Pointer) { 102 | params := (*C.struct_rte_table_hash_params)(p) 103 | alloc.Free(unsafe.Pointer(params.name)) 104 | alloc.Free(unsafe.Pointer(params.key_mask)) 105 | alloc.Free(p) 106 | } 107 | } 108 | 109 | func (p *HashParams) tformToCuckoo(alloc common.Allocator) (unsafe.Pointer, func(unsafe.Pointer)) { 110 | var params *C.struct_rte_table_hash_cuckoo_params 111 | params = (*C.struct_rte_table_hash_cuckoo_params)(alloc.Malloc(unsafe.Sizeof(*params))) 112 | params.name = (*C.char)(common.CString(alloc, p.Name)) 113 | params.key_size = C.uint32_t(p.KeySize) 114 | params.key_offset = C.uint32_t(p.KeyOffset) 115 | params.key_mask = (*C.uint8_t)(common.CBytes(alloc, p.KeyMask)) 116 | params.n_keys = C.uint32_t(p.KeysNum) 117 | params.n_buckets = C.uint32_t(p.BucketsNum) 118 | params.f_hash = C.rte_hash_function(p.CuckooHash.Func) 119 | params.seed = C.uint32_t(p.CuckooHash.Seed) 120 | return unsafe.Pointer(params), func(p unsafe.Pointer) { 121 | params := (*C.struct_rte_table_hash_cuckoo_params)(p) 122 | alloc.Free(unsafe.Pointer(params.name)) 123 | alloc.Free(unsafe.Pointer(params.key_mask)) 124 | alloc.Free(p) 125 | } 126 | } 127 | 128 | // Ops implements Params interface. 129 | func (p *HashParams) Ops() *Ops { 130 | return p.TableOps 131 | } 132 | -------------------------------------------------------------------------------- /hash/crc_test.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import ( 4 | "hash" 5 | "hash/crc32" 6 | "math/rand" 7 | "testing" 8 | "time" 9 | 10 | "github.com/yerden/go-dpdk/util" 11 | ) 12 | 13 | func makeGoHash32Func() func([]byte, uint32) uint32 { 14 | tab := crc32.MakeTable(crc32.Castagnoli) 15 | return func(data []byte, acc uint32) uint32 { 16 | return ^crc32.Update(^acc, tab, data) 17 | } 18 | } 19 | 20 | func testHash32(t *testing.T, src *rand.Rand, h1, h2 hash.Hash32, n int) { 21 | data := make([]byte, n) 22 | src.Read(data) 23 | 24 | h1.Reset() 25 | h1.Write(data) 26 | 27 | h2.Reset() 28 | h2.Write(data) 29 | 30 | if v2, v1 := h2.Sum32(), h1.Sum32(); v2 != v1 { 31 | t.Helper() 32 | t.Error("hash values not equal for n=", n, v2, "!=", v1) 33 | } 34 | } 35 | 36 | func testHash32Upd(t *testing.T, src *rand.Rand, h1, h2 func([]byte, uint32) uint32, n int) { 37 | data := make([]byte, n) 38 | src.Read(data) 39 | seed := src.Uint32() 40 | 41 | if v1, v2 := h1(data, seed), h2(data, seed); v2 != v1 { 42 | t.Helper() 43 | t.Error("hash values not equal for n=", n, v2, "!=", v1) 44 | } 45 | } 46 | 47 | func testAdditivity(t *testing.T, src *rand.Rand, hf func([]byte, uint32) uint32, n int) { 48 | data := make([]byte, n) 49 | src.Read(data) 50 | seed := src.Uint32() 51 | 52 | for i := 1; i < n-1; i++ { 53 | part1 := data[:i] 54 | part2 := data[i:] 55 | 56 | v1 := seed 57 | v1 = hf(part1, v1) 58 | v1 = hf(part2, v1) 59 | 60 | v2 := hf(data, seed) 61 | if v1 != v2 { 62 | t.Error("not additive") 63 | } 64 | } 65 | } 66 | 67 | func TestCrcAdditivity15(t *testing.T) { 68 | src := rand.New(rand.NewSource(time.Now().UnixNano())) 69 | for i := 2; i < 100; i++ { 70 | testAdditivity(t, src, CrcUpdate, i) 71 | } 72 | } 73 | 74 | func TestCrcGoAdditivity15(t *testing.T) { 75 | src := rand.New(rand.NewSource(time.Now().UnixNano())) 76 | for i := 2; i < 100; i++ { 77 | testAdditivity(t, src, makeGoHash32Func(), i) 78 | } 79 | } 80 | 81 | func TestCrc(t *testing.T) { 82 | src := rand.New(rand.NewSource(time.Now().UnixNano())) 83 | 84 | for seed := uint32(0); seed < 1000; seed++ { 85 | rteCrc32 := &util.Hash32{ 86 | Seed: seed, 87 | Block: 8, 88 | Accum: CrcUpdate, 89 | } 90 | 91 | goHashCrc32 := &util.Hash32{ 92 | Seed: seed, 93 | Block: 8, 94 | Accum: makeGoHash32Func(), 95 | } 96 | 97 | for n := 0; n < 100; n++ { 98 | testHash32(t, src, rteCrc32, goHashCrc32, n) 99 | } 100 | } 101 | } 102 | 103 | func TestCrcComplement(t *testing.T) { 104 | src := rand.New(rand.NewSource(time.Now().UnixNano())) 105 | 106 | rteCrc32 := &util.Hash32{ 107 | Seed: 0, 108 | Block: 8, 109 | Accum: complementFunc(CrcUpdate), 110 | } 111 | 112 | goHashCrc32 := crc32.New(crc32.MakeTable(crc32.Castagnoli)) 113 | 114 | for n := 0; n < 100; n++ { 115 | testHash32(t, src, rteCrc32, goHashCrc32, n) 116 | } 117 | } 118 | 119 | func TestCrcUpdate(t *testing.T) { 120 | src := rand.New(rand.NewSource(time.Now().UnixNano())) 121 | 122 | goCrc32 := makeGoHash32Func() 123 | 124 | for n := 1; n < 100; n++ { 125 | testHash32Upd(t, src, CrcUpdate, goCrc32, n) 126 | } 127 | } 128 | 129 | func benchmarkHash32(b *testing.B, src *rand.Rand, h hash.Hash32, block int) { 130 | data := make([]byte, block) 131 | src.Read(data) 132 | 133 | h.Reset() 134 | 135 | for i := 0; i < b.N; i++ { 136 | h.Write(data) 137 | } 138 | } 139 | 140 | func benchmarkHash32Func(b *testing.B, f func([]byte, uint32) uint32, block int) { 141 | src := rand.New(rand.NewSource(time.Now().UnixNano())) 142 | 143 | testHash := &util.Hash32{ 144 | Seed: 0, 145 | Block: 8, 146 | Accum: f, 147 | } 148 | 149 | benchmarkHash32(b, src, testHash, block) 150 | } 151 | 152 | func BenchmarkCrcUpdate8(b *testing.B) { 153 | benchmarkHash32Func(b, CrcUpdate, 8) 154 | } 155 | 156 | func BenchmarkCrcUpdate31(b *testing.B) { 157 | benchmarkHash32Func(b, CrcUpdate, 31) 158 | } 159 | 160 | func BenchmarkGoCrc32Update8(b *testing.B) { 161 | benchmarkHash32Func(b, makeGoHash32Func(), 8) 162 | } 163 | 164 | func BenchmarkGoCrc32Update31(b *testing.B) { 165 | benchmarkHash32Func(b, makeGoHash32Func(), 31) 166 | } 167 | -------------------------------------------------------------------------------- /table/lpm.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | /* 4 | #include 5 | #include 6 | */ 7 | import "C" 8 | 9 | import ( 10 | "unsafe" 11 | 12 | "github.com/yerden/go-dpdk/common" 13 | ) 14 | 15 | var ( 16 | // LPMOps is the LPM IPv4 ops. 17 | LPMOps = (*Ops)(&C.rte_table_lpm_ops) 18 | 19 | // LPM6Ops is the LPM IPv6 ops. 20 | LPM6Ops = (*Ops)(&C.rte_table_lpm_ipv6_ops) 21 | ) 22 | 23 | // LPMParams is the Hash table parameters. 24 | type LPMParams struct { 25 | TableOps *Ops 26 | 27 | // Name. 28 | Name string 29 | 30 | // Number of rules. 31 | Rules uint32 32 | 33 | // Field is currently unused. 34 | NumTBL8 uint32 35 | 36 | // Number of bytes at the start of the table entry that uniquely 37 | // identify the entry. Cannot be bigger than table entry size. 38 | EntryUniqueSize uint32 39 | 40 | // Byte offset within input packet meta-data where lookup key 41 | // (i.e. the destination IP address) is located. 42 | Offset uint32 43 | } 44 | 45 | // Ops implements Params interface. 46 | func (p *LPMParams) Ops() *Ops { 47 | return p.TableOps 48 | } 49 | 50 | // Transform implements common.Transformer interface. 51 | func (p *LPMParams) Transform(alloc common.Allocator) (unsafe.Pointer, func(unsafe.Pointer)) { 52 | switch p.TableOps { 53 | case LPMOps: 54 | return p.tformTo4(alloc) 55 | case LPM6Ops: 56 | return p.tformTo6(alloc) 57 | } 58 | 59 | panic("unsupported LPM Table ops") 60 | } 61 | 62 | func (p *LPMParams) tformTo4(alloc common.Allocator) (unsafe.Pointer, func(unsafe.Pointer)) { 63 | var params *C.struct_rte_table_lpm_params 64 | params = (*C.struct_rte_table_lpm_params)(alloc.Malloc(unsafe.Sizeof(*params))) 65 | params.name = (*C.char)(common.CString(alloc, p.Name)) 66 | params.number_tbl8s = C.uint32_t(p.NumTBL8) 67 | params.n_rules = C.uint32_t(p.Rules) 68 | params.entry_unique_size = C.uint32_t(p.EntryUniqueSize) 69 | params.offset = C.uint32_t(p.Offset) 70 | 71 | return unsafe.Pointer(params), func(p unsafe.Pointer) { 72 | params := (*C.struct_rte_table_lpm_params)(p) 73 | alloc.Free(unsafe.Pointer(params.name)) 74 | alloc.Free(p) 75 | } 76 | } 77 | 78 | func (p *LPMParams) tformTo6(alloc common.Allocator) (unsafe.Pointer, func(unsafe.Pointer)) { 79 | var params *C.struct_rte_table_lpm_ipv6_params 80 | params = (*C.struct_rte_table_lpm_ipv6_params)(alloc.Malloc(unsafe.Sizeof(*params))) 81 | params.name = (*C.char)(common.CString(alloc, p.Name)) 82 | params.number_tbl8s = C.uint32_t(p.NumTBL8) 83 | params.n_rules = C.uint32_t(p.Rules) 84 | params.entry_unique_size = C.uint32_t(p.EntryUniqueSize) 85 | params.offset = C.uint32_t(p.Offset) 86 | 87 | return unsafe.Pointer(params), func(p unsafe.Pointer) { 88 | params := (*C.struct_rte_table_lpm_ipv6_params)(p) 89 | alloc.Free(unsafe.Pointer(params.name)) 90 | alloc.Free(p) 91 | } 92 | } 93 | 94 | // LPMKey is the insert key for LPM IPv4 table. 95 | type LPMKey struct { 96 | // IP address. 97 | IP uint32 98 | 99 | // IP address depth. The most significant "depth" bits of the IP 100 | // address specify the network part of the IP address, while the 101 | // rest of the bits specify the host part of the address and are 102 | // ignored for the purpose of route specification. 103 | Depth uint8 104 | 105 | // padding to fix alignment 106 | _ [3]byte 107 | } 108 | 109 | type cLPMKey C.struct_rte_table_lpm_key 110 | 111 | // LPM6Key is the insert key for LPM IPv4 table. 112 | type LPM6Key struct { 113 | // IP address. 114 | IP [C.RTE_LPM_IPV6_ADDR_SIZE]byte 115 | 116 | // IP address depth. The most significant "depth" bits of the IP 117 | // address specify the network part of the IP address, while the 118 | // rest of the bits specify the host part of the address and are 119 | // ignored for the purpose of route specification. 120 | Depth uint8 121 | } 122 | 123 | type cLPM6Key C.struct_rte_table_lpm_ipv6_key 124 | 125 | var _ = []uintptr{ 126 | unsafe.Sizeof(LPMKey{}) - unsafe.Sizeof(cLPMKey{}), 127 | unsafe.Sizeof(cLPMKey{}) - unsafe.Sizeof(LPMKey{}), 128 | 129 | unsafe.Sizeof(LPM6Key{}) - unsafe.Sizeof(cLPM6Key{}), 130 | unsafe.Sizeof(cLPM6Key{}) - unsafe.Sizeof(LPM6Key{}), 131 | } 132 | -------------------------------------------------------------------------------- /pipeline/pipeline_test.go: -------------------------------------------------------------------------------- 1 | package pipeline 2 | 3 | import ( 4 | "testing" 5 | "unsafe" 6 | 7 | "github.com/yerden/go-dpdk/common" 8 | "github.com/yerden/go-dpdk/eal" 9 | "github.com/yerden/go-dpdk/mempool" 10 | "github.com/yerden/go-dpdk/port" 11 | "github.com/yerden/go-dpdk/ring" 12 | "github.com/yerden/go-dpdk/table" 13 | ) 14 | 15 | func TestPortRingRx(t *testing.T) { 16 | assert := common.Assert(t, true) 17 | 18 | // Initialize EAL on all cores 19 | eal.InitOnceSafe("test", 4) 20 | 21 | err := eal.ExecOnMain(func(*eal.LcoreCtx) { 22 | pl := Create(&Params{ 23 | Name: "test_pipeline", 24 | SocketID: 0, 25 | OffsetPortID: 0, 26 | }) 27 | assert(pl != nil) 28 | 29 | mp, err := mempool.CreateMbufPool( 30 | "hello", 31 | 1024, 32 | 2048, 33 | ) 34 | assert(err == nil, err) 35 | defer mp.Free() 36 | 37 | pSource1, err := pl.PortInCreate(&PortInParams{ 38 | Params: &port.Source{ 39 | Mempool: mp, 40 | }, 41 | BurstSize: 32, 42 | }) 43 | 44 | assert(err == nil, err) 45 | 46 | table1, err := pl.TableCreate(&TableParams{ 47 | Params: &table.ArrayParams{ 48 | Entries: 16, 49 | }, 50 | }) 51 | assert(err == nil, err) 52 | 53 | err = pl.ConnectToTable(pSource1, table1) 54 | assert(err == nil, err) 55 | 56 | pSink1, err := pl.PortOutCreate(&PortOutParams{ 57 | Params: &port.Sink{ 58 | Filename: "/dev/null", 59 | MaxPackets: 32, 60 | }, 61 | }) 62 | 63 | assert(pSink1 == 0) 64 | assert(nil == err, err) 65 | 66 | // sample entry 67 | entry := &TableEntry{} 68 | entry.SetAction(ActionPort) 69 | entry.SetPortID(pSink1) 70 | 71 | // add default entry 72 | dfltEntry, err := pl.TableDefaultEntryAdd(table1, entry) 73 | assert(err == nil, err) 74 | assert(*dfltEntry == *entry) 75 | 76 | // remove default entry 77 | err = pl.TableDefaultEntryDelete(table1, entry) 78 | assert(err == nil, err) 79 | assert(entry.GetAction() == ActionPort) 80 | assert(entry.GetPortID() == pSink1) 81 | 82 | var e *TableEntry 83 | _, err = pl.TableEntryAdd(table1, 84 | unsafe.Pointer(&table.ArrayKey{Pos: 0}), 85 | entry, &e) 86 | assert(err == nil, err) 87 | assert(*e == *entry) 88 | 89 | assert(nil == pl.Check()) 90 | assert(nil == pl.Disable(pSource1)) 91 | assert(nil == pl.Flush()) 92 | 93 | // destroy the tables, ports and pipeline 94 | assert(nil == pl.Free()) 95 | }) 96 | assert(err == nil, err) 97 | } 98 | 99 | func TestPipelineStub(t *testing.T) { 100 | assert := common.Assert(t, true) 101 | 102 | // Initialize EAL on all cores 103 | eal.InitOnceSafe("test", 4) 104 | 105 | err := eal.ExecOnMain(func(*eal.LcoreCtx) { 106 | pl := Create(&Params{ 107 | Name: "test_pipeline", 108 | SocketID: 0, 109 | OffsetPortID: 0, 110 | }) 111 | assert(pl != nil) 112 | 113 | r, err := ring.Create("test_ring", 1024, ring.OptSC) 114 | assert(err == nil, err) 115 | assert(r != nil) 116 | defer r.Free() 117 | 118 | // create pipeline ports 119 | pRing, err := pl.PortInCreate(&PortInParams{ 120 | Params: &port.RingRx{Ring: r, Multi: false}, 121 | BurstSize: 32, 122 | }) 123 | assert(err == nil, err) 124 | 125 | pSink1, err := pl.PortOutCreate(&PortOutParams{ 126 | Params: &port.Sink{ 127 | Filename: "/dev/null", 128 | MaxPackets: 32, 129 | }, 130 | }) 131 | 132 | assert(pSink1 == 0) 133 | assert(nil == err, err) 134 | 135 | // create pipeline tables 136 | tStub, err := pl.TableCreate(&TableParams{ 137 | Params: &table.StubParams{}, 138 | }) 139 | assert(err == nil, err) 140 | 141 | entry := NewTableEntry(0) 142 | entry.SetAction(ActionPortMeta) 143 | 144 | defaultEntry, err := pl.TableDefaultEntryAdd(tStub, entry) 145 | assert(err == nil, err) 146 | assert(*defaultEntry == *entry) 147 | 148 | // connect input port. 149 | err = pl.ConnectToTable(pRing, tStub) 150 | assert(err == nil, err) 151 | 152 | // check the pipeline 153 | assert(nil == pl.Check()) 154 | assert(nil == pl.Flush()) 155 | 156 | // destroy the tables, ports and pipeline 157 | assert(nil == pl.Free()) 158 | }) 159 | assert(err == nil, err) 160 | } 161 | -------------------------------------------------------------------------------- /eal/eal_test.go: -------------------------------------------------------------------------------- 1 | package eal 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "testing" 9 | 10 | "github.com/yerden/go-dpdk/common" 11 | "golang.org/x/sys/unix" 12 | ) 13 | 14 | func TestEALInit(t *testing.T) { 15 | assert := common.Assert(t, true) 16 | var set unix.CPUSet 17 | assert(unix.SchedGetaffinity(0, &set) == nil) 18 | 19 | _, err := Init([]string{"test", "--some-invalid-option"}) 20 | assert(err != nil) 21 | 22 | n, err := Init([]string{"test", 23 | "-c", common.NewMap(&set).String(), 24 | "-m", "128", 25 | "--no-huge", 26 | "--no-pci", 27 | "--main-lcore", "0"}) 28 | assert(n == 8, n) 29 | assert(err == nil) 30 | 31 | ch := make(chan uint, set.Count()) 32 | assert(LcoreCount() == uint(set.Count())) 33 | for _, id := range Lcores() { 34 | ExecOnLcore(id, func(id uint) func(*LcoreCtx) { 35 | return func(ctx *LcoreCtx) { 36 | assert(id == LcoreID()) 37 | ch <- LcoreID() 38 | } 39 | }(id)) 40 | } 41 | 42 | ExecOnMain(func(*LcoreCtx) { 43 | assert(HasPCI() == false) 44 | assert(HasHugePages() == false) 45 | }) 46 | 47 | var myset unix.CPUSet 48 | for i := 0; i < set.Count(); i++ { 49 | myset.Set(int(<-ch)) 50 | } 51 | 52 | select { 53 | case <-ch: 54 | assert(false) 55 | default: 56 | } 57 | 58 | assert(myset == set) 59 | 60 | // test panic 61 | PanicAsErr = true 62 | 63 | for _, id := range Lcores() { 64 | err := ExecOnLcore(id, func(ctx *LcoreCtx) { 65 | panic("emit panic") 66 | }) 67 | e, ok := err.(*ErrLcorePanic) 68 | assert(ok) 69 | assert(e.LcoreID == id) 70 | assert(len(e.Pc) > 0) 71 | } 72 | for _, id := range Lcores() { 73 | ok := false 74 | err := ExecOnLcore(id, func(ctx *LcoreCtx) { 75 | // lcore is fine 76 | ok = true 77 | }) 78 | assert(ok && err == nil, err) 79 | } 80 | 81 | // test panic returning arbitrary error 82 | err = ExecOnMain(func(*LcoreCtx) { 83 | panic(flag.ErrHelp) 84 | }) 85 | assert(err != nil) 86 | e, ok := err.(*ErrLcorePanic) 87 | assert(ok) 88 | assert(e.Unwrap() == flag.ErrHelp) 89 | 90 | // invalid lcore 91 | assert(ExecOnLcore(uint(1024), func(ctx *LcoreCtx) {}) == ErrLcoreInvalid) 92 | 93 | // stop all lcores 94 | StopLcores() 95 | 96 | err = Cleanup() 97 | assert(err == nil, err) 98 | } 99 | 100 | func TestParseCmd(t *testing.T) { 101 | assert := common.Assert(t, true) 102 | 103 | res, err := parseCmd("hello bitter world") 104 | assert(err == nil, err) 105 | assert(res[0] == "hello") 106 | assert(res[1] == "bitter") 107 | assert(res[2] == "world") 108 | 109 | res, err = parseCmd("hello --bitter world") 110 | assert(err == nil, err) 111 | assert(res[0] == "hello") 112 | assert(res[1] == "--bitter", res[1]) 113 | assert(res[2] == "world") 114 | } 115 | 116 | func ExampleInit_flags() { 117 | // If your executable is to be launched like a DPDK example: 118 | // /path/to/exec -c 3f -m 1024 -- 119 | // then you may do the following: 120 | n, err := Init(os.Args) 121 | if err != nil { 122 | panic(err) 123 | } 124 | 125 | // to be able to do further flag processing, i.e. pretend the cmd was: 126 | // /path/to/exec 127 | os.Args[n], os.Args = os.Args[0], os.Args[n:] 128 | flag.Parse() 129 | } 130 | 131 | func ExampleExecOnLcore() { 132 | // Lcore ID 1 133 | lid := uint(1) 134 | 135 | err := ExecOnLcore(lid, func(ctx *LcoreCtx) { 136 | log.Printf("this is lcore #%d\n", LcoreID()) 137 | }) 138 | 139 | if err == ErrLcoreInvalid { 140 | // lid doesn't exist 141 | log.Fatalf("invalid lcore %d\n", lid) 142 | } 143 | 144 | if e, ok := err.(*ErrLcorePanic); ok { 145 | // lcore panicked 146 | log.Fatalln(e) 147 | } 148 | } 149 | 150 | func ExampleExecOnLcore_error() { 151 | // Lcore ID 1 152 | lid := uint(1) 153 | 154 | someErr := fmt.Errorf("lcore error") 155 | err := ExecOnLcore(lid, func(ctx *LcoreCtx) { 156 | if 2+2 != 4 { 157 | panic(someErr) 158 | } 159 | }) 160 | 161 | if e, ok := err.(*ErrLcorePanic); ok { 162 | // lcore panicked 163 | if err := e.Unwrap(); err == someErr { 164 | log.Fatalln("check the math") 165 | } 166 | } 167 | 168 | // or, as of Go 1.13 169 | // if errors.Is(err, someErr) { ... 170 | } 171 | -------------------------------------------------------------------------------- /ethdev/rxtx.go: -------------------------------------------------------------------------------- 1 | package ethdev 2 | 3 | /* 4 | #include 5 | 6 | #include 7 | #include 8 | */ 9 | import "C" 10 | 11 | import ( 12 | "reflect" 13 | "unsafe" 14 | 15 | "github.com/yerden/go-dpdk/mbuf" 16 | ) 17 | 18 | // TxBuffer is a structure used to buffer packets for future TX Used by APIs 19 | // rte_eth_tx_buffer and rte_eth_tx_buffer_flush. 20 | type TxBuffer C.struct_rte_eth_dev_tx_buffer 21 | 22 | func fracCeilRound(x int, y int) int { 23 | n := x / y 24 | if y*n != x { 25 | n++ 26 | } 27 | return n 28 | } 29 | 30 | // NewTxBuffer creates new TxBuffer with cnt mbufs. 31 | func NewTxBuffer(cnt int) *TxBuffer { 32 | amount := TxBufferSize(cnt) 33 | ptrSize := unsafe.Sizeof(&mbuf.Mbuf{}) 34 | data := make([]*mbuf.Mbuf, fracCeilRound(int(amount), int(ptrSize))) 35 | buf := (*TxBuffer)(unsafe.Pointer(&data[0])) 36 | buf.Init(cnt) 37 | return buf 38 | } 39 | 40 | // TxBufferSize is the occupied memory for TxBuffer of length mbufCnt. 41 | func TxBufferSize(mbufCnt int) uintptr { 42 | return unsafe.Sizeof(TxBuffer{}) + uintptr(mbufCnt)*unsafe.Sizeof(&mbuf.Mbuf{}) 43 | } 44 | 45 | // RxBurst receives packets for port pid and queue qid. Returns number of 46 | // packets retrieved into pkts. 47 | func (pid Port) RxBurst(qid uint16, pkts []*mbuf.Mbuf) uint16 { 48 | return uint16(C.rte_eth_rx_burst(C.uint16_t(pid), C.uint16_t(qid), 49 | (**C.struct_rte_mbuf)(unsafe.Pointer(&pkts[0])), C.uint16_t(len(pkts)))) 50 | } 51 | 52 | // TxBurst sends packets over port pid and queue qid. Returns number of 53 | // packets sent from pkts. 54 | func (pid Port) TxBurst(qid uint16, pkts []*mbuf.Mbuf) uint16 { 55 | return uint16(C.rte_eth_tx_burst(C.uint16_t(pid), C.uint16_t(qid), 56 | (**C.struct_rte_mbuf)(unsafe.Pointer(&pkts[0])), C.uint16_t(len(pkts)))) 57 | } 58 | 59 | // TxBufferFlush Send any packets queued up for transmission on a port 60 | // and HW queue. 61 | // 62 | // This causes an explicit flush of packets previously buffered via the 63 | // rte_eth_tx_buffer() function. It returns the number of packets 64 | // successfully sent to the NIC, and calls the error callback for any 65 | // unsent packets. Unless explicitly set up otherwise, the default 66 | // callback simply frees the unsent packets back to the owning mempool. 67 | func (pid Port) TxBufferFlush(qid uint16, buf *TxBuffer) uint16 { 68 | return uint16(C.rte_eth_tx_buffer_flush(C.uint16_t(pid), C.uint16_t(qid), 69 | (*C.struct_rte_eth_dev_tx_buffer)(unsafe.Pointer(buf)))) 70 | } 71 | 72 | // TxBuffer buffers a single packet for future transmission on a port 73 | // and queue. 74 | // 75 | // This function takes a single mbuf/packet and buffers it for later 76 | // transmission on the particular port and queue specified. Once the 77 | // buffer is full of packets, an attempt will be made to transmit all 78 | // the buffered packets. In case of error, where not all packets can 79 | // be transmitted, a callback is called with the unsent packets as a 80 | // parameter. If no callback is explicitly set up, the unsent packets 81 | // are just freed back to the owning mempool. The function returns the 82 | // number of packets actually sent i.e. 0 if no buffer flush occurred, 83 | // otherwise the number of packets successfully flushed 84 | func (pid Port) TxBuffer(qid uint16, buf *TxBuffer, m *mbuf.Mbuf) uint16 { 85 | return uint16(C.rte_eth_tx_buffer(C.uint16_t(pid), C.uint16_t(qid), 86 | (*C.struct_rte_eth_dev_tx_buffer)(unsafe.Pointer(buf)), 87 | (*C.struct_rte_mbuf)(unsafe.Pointer(m)))) 88 | } 89 | 90 | // Mbufs returns a slice of packets contained in TxBuffer. 91 | func (buf *TxBuffer) Mbufs() []*mbuf.Mbuf { 92 | var d []*mbuf.Mbuf 93 | b := (*C.struct_rte_eth_dev_tx_buffer)(unsafe.Pointer(buf)) 94 | sh := (*reflect.SliceHeader)(unsafe.Pointer(&d)) 95 | sh.Data = uintptr(unsafe.Pointer(b)) + unsafe.Sizeof(*b) 96 | sh.Len = int(b.length) 97 | sh.Cap = int(b.size) 98 | return d 99 | } 100 | 101 | // Init initializes pre-allocated TxBuffer which must have enough 102 | // memory to contain cnt mbufs. 103 | func (buf *TxBuffer) Init(cnt int) { 104 | b := (*C.struct_rte_eth_dev_tx_buffer)(unsafe.Pointer(buf)) 105 | C.rte_eth_tx_buffer_init(b, C.uint16_t(cnt)) 106 | } 107 | -------------------------------------------------------------------------------- /lpm/lpm.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package lpm wraps RTE LPM library. 3 | 4 | Please refer to DPDK Programmer's Guide for reference and caveats. 5 | */ 6 | package lpm 7 | 8 | /* 9 | #include 10 | #include 11 | #include 12 | */ 13 | import "C" 14 | 15 | import ( 16 | "net/netip" 17 | "unsafe" 18 | 19 | "github.com/yerden/go-dpdk/common" 20 | ) 21 | 22 | // LPM is an RTE Longest Prefix Match lookup object. 23 | type LPM C.struct_rte_lpm 24 | 25 | // Config is used to configure LPM object while creation. 26 | type Config struct { 27 | MaxRules uint32 28 | NumberTbl8s uint32 29 | Flags int 30 | } 31 | 32 | // FindExisting finds an existing LPM object and return a pointer to 33 | // it. 34 | // 35 | // Specify pointer to *LPM or *LPM6 in ptr to find respective object 36 | // by its memzone name. 37 | func FindExisting(name string, ptr interface{}) error { 38 | s := C.CString(name) 39 | defer C.free(unsafe.Pointer(s)) 40 | var found bool 41 | 42 | switch r := ptr.(type) { 43 | case **LPM: 44 | *r = (*LPM)(C.rte_lpm_find_existing(s)) 45 | found = *r != nil 46 | case **LPM6: 47 | *r = (*LPM6)(C.rte_lpm6_find_existing(s)) 48 | found = *r != nil 49 | default: 50 | panic("incompatible value") 51 | } 52 | 53 | if !found { 54 | return common.RteErrno() 55 | } 56 | 57 | return nil 58 | } 59 | 60 | // Create an LPM object. 61 | // 62 | // Specify name as an LPM object name, socket_id as a NUMA socket ID 63 | // for LPM table memory allocation and config as a structure 64 | // containing the configuration. 65 | // 66 | // Returns handle to LPM object on success, and errno value: 67 | // 68 | // E_RTE_NO_CONFIG - function could not get pointer to rte_config structure 69 | // E_RTE_SECONDARY - function was called from a secondary process instance 70 | // EINVAL - invalid parameter passed to function 71 | // ENOSPC - the maximum number of memzones has already been allocated 72 | // EEXIST - a memzone with the same name already exists 73 | // ENOMEM - no appropriate memory area found in which to create memzone 74 | func Create(name string, socket int, cfg *Config) (*LPM, error) { 75 | s := C.CString(name) 76 | defer C.free(unsafe.Pointer(s)) 77 | config := &C.struct_rte_lpm_config{ 78 | max_rules: C.uint32_t(cfg.MaxRules), 79 | number_tbl8s: C.uint32_t(cfg.NumberTbl8s), 80 | flags: C.int(cfg.Flags), 81 | } 82 | 83 | if r := (*LPM)(C.rte_lpm_create(s, C.int(socket), config)); r != nil { 84 | return r, nil 85 | } 86 | 87 | return nil, common.RteErrno() 88 | } 89 | 90 | // Free an LPM object. 91 | func (r *LPM) Free() { 92 | C.rte_lpm_free((*C.struct_rte_lpm)(r)) 93 | } 94 | 95 | // Add a rule to LPM object. Panics if ipnet is not IPv4 subnet. 96 | // 97 | // ip/prefix is an IP address/subnet to add, nextHop is a value 98 | // associated with added IP subnet. 99 | func (r *LPM) Add(ipnet netip.Prefix, nextHop uint32) error { 100 | ip, prefix := cvtIPv4Net(ipnet) 101 | rc := C.rte_lpm_add((*C.struct_rte_lpm)(r), C.uint32_t(ip), C.uint8_t(prefix), C.uint32_t(nextHop)) 102 | return common.IntToErr(rc) 103 | } 104 | 105 | // Delete a rule from LPM object. Panics if ipnet is not IPv4 subnet. 106 | func (r *LPM) Delete(ipnet netip.Prefix) error { 107 | ip, prefix := cvtIPv4Net(ipnet) 108 | rc := C.rte_lpm_delete((*C.struct_rte_lpm)(r), C.uint32_t(ip), C.uint8_t(prefix)) 109 | return common.IntToErr(rc) 110 | } 111 | 112 | // Lookup an IP in LPM object. Panics if ip is not IPv4 subnet. 113 | func (r *LPM) Lookup(ip netip.Addr) (uint32, error) { 114 | b := cvtIPv4(ip) 115 | var res uint32 116 | rc := C.rte_lpm_lookup((*C.struct_rte_lpm)(r), C.uint32_t(b), (*C.uint32_t)(&res)) 117 | return res, common.IntToErr(rc) 118 | } 119 | 120 | // DeleteAll removes all rules from LPM object. 121 | func (r *LPM) DeleteAll() { 122 | C.rte_lpm_delete_all((*C.struct_rte_lpm)(r)) 123 | } 124 | 125 | // IsRulePresent checks if a rule present in the LPM and returns 126 | // nextHop if it is. Panics if ipnet is not IPv4 subnet. 127 | func (r *LPM) IsRulePresent(ipnet netip.Prefix, nextHop *uint32) (bool, error) { 128 | ip, prefix := cvtIPv4Net(ipnet) 129 | rc := C.rte_lpm_is_rule_present((*C.struct_rte_lpm)(r), C.uint32_t(ip), C.uint8_t(prefix), (*C.uint32_t)(nextHop)) 130 | n, err := common.IntOrErr(rc) 131 | return n != 0, err 132 | } 133 | -------------------------------------------------------------------------------- /pipeline/port.go: -------------------------------------------------------------------------------- 1 | package pipeline 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | 8 | import ( 9 | "unsafe" 10 | 11 | "github.com/yerden/go-dpdk/common" 12 | "github.com/yerden/go-dpdk/port" 13 | ) 14 | 15 | // PortIn represents an input port created in a pipeline. 16 | type PortIn C.uint32_t 17 | 18 | // PortOut represents an output port created in a pipeline. 19 | type PortOut C.uint32_t 20 | 21 | // PortInAction is the pipeline input port action handler. 22 | // 23 | // The action handler can decide to drop packets by resetting the 24 | // associated packet bit in the pkts_mask parameter. In this case, the 25 | // action handler is required not to free the packet buffer, which 26 | // will be freed eventually by the pipeline. 27 | type PortInAction C.rte_pipeline_port_in_action_handler 28 | 29 | // PortOutAction is the pipeline output port action handler. 30 | // 31 | // The action handler can decide to drop packets by resetting the 32 | // associated packet bit in the pkts_mask parameter. In this case, the 33 | // action handler is required not to free the packet buffer, which 34 | // will be freed eventually by the pipeline. 35 | type PortOutAction C.rte_pipeline_port_out_action_handler 36 | 37 | // PortInParams configures input port creation for pipeline. 38 | type PortInParams struct { 39 | // Port configuration. See port package. 40 | Params port.InParams 41 | 42 | // Input port action. Implemented in C. 43 | Action PortInAction 44 | // Input port action argument. 45 | ActionArg unsafe.Pointer 46 | 47 | // Amount of packet burst. 48 | BurstSize int 49 | } 50 | 51 | // PortOutParams configures output port creation for pipeline. 52 | type PortOutParams struct { 53 | // Port configuration. See port package. 54 | Params port.OutParams 55 | 56 | // Output port action. Implemented in C. 57 | Action PortOutAction 58 | // Output port action argument. 59 | ActionArg unsafe.Pointer 60 | } 61 | 62 | // PortInCreate creates new input port in the pipeline with specified 63 | // configuration. Returns id of the created port or an error. 64 | func (pl *Pipeline) PortInCreate(p *PortInParams) (id PortIn, err error) { 65 | params := &C.struct_rte_pipeline_port_in_params{} 66 | params.ops = (*C.struct_rte_port_in_ops)(unsafe.Pointer(p.Params.InOps())) 67 | 68 | { 69 | x, dtor := p.Params.Transform(alloc) 70 | defer dtor(x) 71 | params.arg_create = x 72 | } 73 | 74 | params.f_action = (C.rte_pipeline_port_in_action_handler)(p.Action) 75 | params.arg_ah = p.ActionArg 76 | params.burst_size = C.uint(p.BurstSize) 77 | 78 | rc := C.rte_pipeline_port_in_create( 79 | (*C.struct_rte_pipeline)(pl), 80 | params, 81 | (*C.uint32_t)(&id)) 82 | 83 | return id, common.IntErr(int64(rc)) 84 | } 85 | 86 | // PortOutCreate creates new output port in the pipeline with specified 87 | // configuration. Returns id of the created port or an error. 88 | func (pl *Pipeline) PortOutCreate(p *PortOutParams) (id PortOut, err error) { 89 | params := &C.struct_rte_pipeline_port_out_params{} 90 | params.ops = (*C.struct_rte_port_out_ops)(unsafe.Pointer(p.Params.OutOps())) 91 | 92 | { 93 | x, dtor := p.Params.Transform(alloc) 94 | defer dtor(x) 95 | params.arg_create = x 96 | } 97 | 98 | params.f_action = (C.rte_pipeline_port_out_action_handler)(p.Action) 99 | params.arg_ah = p.ActionArg 100 | 101 | rc := C.rte_pipeline_port_out_create( 102 | (*C.struct_rte_pipeline)(pl), 103 | params, 104 | (*C.uint32_t)(&id)) 105 | 106 | return id, common.IntErr(int64(rc)) 107 | } 108 | 109 | // ConnectToTable connects specified portID to created tableID. 110 | func (pl *Pipeline) ConnectToTable(port PortIn, table Table) error { 111 | return common.IntErr(int64(C.rte_pipeline_port_in_connect_to_table( 112 | (*C.struct_rte_pipeline)(pl), 113 | C.uint32_t(port), 114 | C.uint32_t(table)))) 115 | } 116 | 117 | // Disable input port in the pipeline. 118 | func (pl *Pipeline) Disable(port PortIn) error { 119 | return common.IntErr(int64(C.rte_pipeline_port_in_disable( 120 | (*C.struct_rte_pipeline)(pl), 121 | C.uint32_t(port)))) 122 | } 123 | 124 | // Enable input port in the pipeline. 125 | func (pl *Pipeline) Enable(port PortIn) error { 126 | return common.IntErr(int64(C.rte_pipeline_port_in_enable( 127 | (*C.struct_rte_pipeline)(pl), 128 | C.uint32_t(port)))) 129 | } 130 | --------------------------------------------------------------------------------