Skip to content

Commit d1a22ff

Browse files
committed
ovsnl: add Datapath type, Client.Datapath.List method
1 parent b51fa6c commit d1a22ff

File tree

6 files changed

+463
-4
lines changed

6 files changed

+463
-4
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,13 @@ if err != nil {
3232
// Be sure to close the generic netlink connection!
3333
defer c.Close()
3434

35-
// TODO(mdlayher): expand upon this example!
35+
// List available OVS datapaths.
36+
dps, err := c.Datapath.List()
37+
if err != nil {
38+
log.Fatalf("failed to list datapaths: %v", err)
39+
}
40+
41+
for _, d := range dps {
42+
log.Printf("datapath: %q, flows: %d", d.Name, d.Stats.Flows)
43+
}
3644
```

ovsnl/client.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,25 @@ import (
1818
"fmt"
1919
"os"
2020
"strings"
21+
"unsafe"
2122

2223
"github.com/digitalocean/go-openvswitch/ovsnl/internal/ovsh"
2324
"github.com/mdlayher/genetlink"
2425
)
2526

27+
// Sizes of various structures, used in unsafe casts.
28+
const (
29+
sizeofHeader = int(unsafe.Sizeof(ovsh.Header{}))
30+
31+
sizeofDPStats = int(unsafe.Sizeof(ovsh.DPStats{}))
32+
sizeofDPMegaflowStats = int(unsafe.Sizeof(ovsh.DPMegaflowStats{}))
33+
)
34+
2635
// A Client is a Linux Open vSwitch generic netlink client.
2736
type Client struct {
37+
// Datapath provides access to DatapathService methods.
38+
Datapath *DatapathService
39+
2840
c *genetlink.Conn
2941
}
3042

@@ -100,7 +112,13 @@ func (c *Client) init(families []genetlink.Family) error {
100112
// initFamily initializes a single generic netlink family service.
101113
func (c *Client) initFamily(f genetlink.Family) error {
102114
switch f.Name {
103-
case ovsh.DatapathFamily, ovsh.FlowFamily, ovsh.PacketFamily, ovsh.VportFamily:
115+
case ovsh.DatapathFamily:
116+
c.Datapath = &DatapathService{
117+
f: f,
118+
c: c,
119+
}
120+
return nil
121+
case ovsh.FlowFamily, ovsh.PacketFamily, ovsh.VportFamily:
104122
// TODO(mdlayher): populate.
105123
return nil
106124
}

ovsnl/client_linux_integration_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ func TestLinuxClientIntegration(t *testing.T) {
3434
}
3535
defer c.Close()
3636

37-
// TODO(mdlayher): fill in after Client has more methods.
38-
_ = c
37+
dps, err := c.Datapath.List()
38+
if err != nil {
39+
t.Fatalf("failed to list datapaths: %v", err)
40+
}
41+
42+
for _, d := range dps {
43+
t.Logf("datapath: %q, flows: %d", d.Name, d.Stats.Flows)
44+
}
3945
}

ovsnl/client_linux_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,23 @@ func familyMessages(families []string) []genetlink.Message {
117117
return msgs
118118
}
119119

120+
// ovsFamilies creates a genltest.Func which intercepts "list family" requests
121+
// and returns all the OVS families. Other requests are passed through to fn.
122+
func ovsFamilies(fn genltest.Func) genltest.Func {
123+
return func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) {
124+
if nreq.Header.Type == unix.GENL_ID_CTRL && greq.Header.Command == unix.CTRL_CMD_GETFAMILY {
125+
return familyMessages([]string{
126+
ovsh.DatapathFamily,
127+
ovsh.FlowFamily,
128+
ovsh.PacketFamily,
129+
ovsh.VportFamily,
130+
}), nil
131+
}
132+
133+
return fn(greq, nreq)
134+
}
135+
}
136+
120137
func mustMarshalAttributes(attrs []netlink.Attribute) []byte {
121138
b, err := netlink.MarshalAttributes(attrs)
122139
if err != nil {

ovsnl/datapath.go

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
// Copyright 2017 DigitalOcean.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package ovsnl
16+
17+
import (
18+
"fmt"
19+
"unsafe"
20+
21+
"github.com/digitalocean/go-openvswitch/ovsnl/internal/ovsh"
22+
"github.com/mdlayher/genetlink"
23+
"github.com/mdlayher/netlink"
24+
"github.com/mdlayher/netlink/nlenc"
25+
)
26+
27+
// A DatapathService provides access to methods which interact with the
28+
// "ovs_datapath" generic netlink family.
29+
type DatapathService struct {
30+
c *Client
31+
f genetlink.Family
32+
}
33+
34+
// A Datapath is an Open vSwitch in-kernel datapath.
35+
type Datapath struct {
36+
Index int
37+
Name string
38+
Features DatapathFeatures
39+
Stats DatapathStats
40+
MegaflowStats DatapathMegaflowStats
41+
}
42+
43+
// DatapathFeatures is a set of bit flags that specify features for a datapath.
44+
type DatapathFeatures uint32
45+
46+
// Possible DatapathFeatures flag values.
47+
const (
48+
DatapathFeaturesUnaligned DatapathFeatures = ovsh.DpFUnaligned
49+
DatapathFeaturesVPortPIDs DatapathFeatures = ovsh.DpFVportPids
50+
)
51+
52+
// String returns the string representation of a DatapathFeatures.
53+
func (f DatapathFeatures) String() string {
54+
names := []string{
55+
"unaligned",
56+
"vportpids",
57+
}
58+
59+
var s string
60+
for i, name := range names {
61+
if f&(1<<uint(i)) != 0 {
62+
if s != "" {
63+
s += "|"
64+
}
65+
66+
s += name
67+
}
68+
}
69+
70+
if s == "" {
71+
s = "0"
72+
}
73+
74+
return s
75+
}
76+
77+
// DatapathStats contains statistics about packets that have passed
78+
// through a Datapath.
79+
type DatapathStats struct {
80+
// Number of flow table matches.
81+
Hit uint64
82+
// Number of flow table misses.
83+
Missed uint64
84+
// Number of misses not sent to userspace.
85+
Lost uint64
86+
// Number of flows present.
87+
Flows uint64
88+
}
89+
90+
// DatapathMegaflowStats contains statistics about mega flow mask
91+
// usage for a Datapath.
92+
type DatapathMegaflowStats struct {
93+
// Number of masks used for flow lookups.
94+
MaskHits uint64
95+
// Number of masks for the datapath.
96+
Masks uint32
97+
}
98+
99+
// List lists all Datapaths in the kernel.
100+
func (s *DatapathService) List() ([]Datapath, error) {
101+
req := genetlink.Message{
102+
Header: genetlink.Header{
103+
Command: ovsh.DpCmdGet,
104+
Version: uint8(s.f.Version),
105+
},
106+
// Query all datapaths
107+
Data: nlenc.Uint32Bytes(0),
108+
}
109+
110+
flags := netlink.HeaderFlagsRequest | netlink.HeaderFlagsDump
111+
msgs, err := s.c.c.Execute(req, s.f.ID, flags)
112+
if err != nil {
113+
return nil, err
114+
}
115+
116+
return parseDatapaths(msgs)
117+
}
118+
119+
// parseDatapaths parses a slice of Datapaths from a slice of generic netlink
120+
// messages.
121+
func parseDatapaths(msgs []genetlink.Message) ([]Datapath, error) {
122+
dps := make([]Datapath, 0, len(msgs))
123+
124+
for _, m := range msgs {
125+
if l := len(m.Data); l < sizeofHeader {
126+
return nil, fmt.Errorf("not enough data for OVS message header: %d bytes", l)
127+
}
128+
129+
var dp Datapath
130+
131+
// Fetch the header at the beginning of the message.
132+
h := *(*ovsh.Header)(unsafe.Pointer(&m.Data[:sizeofHeader][0]))
133+
dp.Index = int(h.Ifindex)
134+
135+
// Skip the header to parse attributes.
136+
attrs, err := netlink.UnmarshalAttributes(m.Data[sizeofHeader:])
137+
if err != nil {
138+
return nil, err
139+
}
140+
141+
for _, a := range attrs {
142+
switch a.Type {
143+
case ovsh.DpAttrName:
144+
dp.Name = nlenc.String(a.Data)
145+
case ovsh.DpAttrUserFeatures:
146+
dp.Features = DatapathFeatures(nlenc.Uint32(a.Data))
147+
case ovsh.DpAttrStats:
148+
dp.Stats, err = parseDPStats(a.Data)
149+
if err != nil {
150+
return nil, err
151+
}
152+
case ovsh.DpAttrMegaflowStats:
153+
dp.MegaflowStats, err = parseDPMegaflowStats(a.Data)
154+
if err != nil {
155+
return nil, err
156+
}
157+
}
158+
}
159+
160+
dps = append(dps, dp)
161+
}
162+
163+
return dps, nil
164+
}
165+
166+
// parseDPStats converts a byte slice into DatapathStats.
167+
func parseDPStats(b []byte) (DatapathStats, error) {
168+
// Verify that the byte slice is the correct length before doing
169+
// unsafe casts.
170+
if want, got := sizeofDPStats, len(b); want != got {
171+
return DatapathStats{}, fmt.Errorf("unexpected datapath stats structure size, want %d, got %d", want, got)
172+
}
173+
174+
s := *(*ovsh.DPStats)(unsafe.Pointer(&b[0]))
175+
return DatapathStats{
176+
Hit: s.Hit,
177+
Missed: s.Missed,
178+
Lost: s.Lost,
179+
Flows: s.Flows,
180+
}, nil
181+
}
182+
183+
// parseDPMegaflowStats converts a byte slice into DatapathMegaflowStats.
184+
func parseDPMegaflowStats(b []byte) (DatapathMegaflowStats, error) {
185+
// Verify that the byte slice is the correct length before doing
186+
// unsafe casts.
187+
if want, got := sizeofDPMegaflowStats, len(b); want != got {
188+
return DatapathMegaflowStats{}, fmt.Errorf("unexpected datapath megaflow stats structure size, want %d, got %d", want, got)
189+
}
190+
191+
s := *(*ovsh.DPMegaflowStats)(unsafe.Pointer(&b[0]))
192+
193+
return DatapathMegaflowStats{
194+
MaskHits: s.Mask_hit,
195+
Masks: s.Masks,
196+
}, nil
197+
}

0 commit comments

Comments
 (0)