Skip to content

Commit 7a2a409

Browse files
committed
Add DumpPortMappings API to retrieve ofport and MAC address for interfaces
1 parent 045aa1d commit 7a2a409

File tree

2 files changed

+295
-0
lines changed

2 files changed

+295
-0
lines changed

ovs/openflow.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"errors"
2222
"fmt"
2323
"io"
24+
"regexp"
25+
"strconv"
2426
)
2527

2628
var (
@@ -69,6 +71,15 @@ type flowDirective struct {
6971
flow string
7072
}
7173

74+
// PortMapping contains the ofport number and MAC address for an interface.
75+
type PortMapping struct {
76+
// OfPort specifies the OpenFlow port number.
77+
OfPort int
78+
79+
// MACAddress specifies the MAC address of the interface.
80+
MACAddress string
81+
}
82+
7283
// Possible flowDirective directive values.
7384
const (
7485
dirAdd = "add"
@@ -324,6 +335,48 @@ func (o *OpenFlowService) DumpAggregate(bridge string, flow *MatchFlow) (*FlowSt
324335
return stats, nil
325336
}
326337

338+
// DumpPortMappings retrieves port mappings (ofport and MAC address) for all
339+
// interfaces from the specified bridge. The output is parsed from 'ovs-ofctl show' command.
340+
func (o *OpenFlowService) DumpPortMappings(bridge string) (map[string]*PortMapping, error) {
341+
args := []string{"show", bridge}
342+
args = append(args, o.c.ofctlFlags...)
343+
344+
out, err := o.exec(args...)
345+
if err != nil {
346+
return nil, err
347+
}
348+
349+
mappings := make(map[string]*PortMapping)
350+
scanner := bufio.NewScanner(bytes.NewReader(out))
351+
352+
for scanner.Scan() {
353+
line := scanner.Bytes()
354+
matches := portMappingPattern.FindSubmatch(line)
355+
if len(matches) != 4 {
356+
// Skip lines that don't match the pattern
357+
continue
358+
}
359+
360+
interfaceName := string(matches[2])
361+
ofport, err := strconv.Atoi(string(matches[1]))
362+
if err != nil {
363+
// Skip malformed ofport numbers
364+
continue
365+
}
366+
367+
mappings[interfaceName] = &PortMapping{
368+
OfPort: ofport,
369+
MACAddress: string(matches[3]),
370+
}
371+
}
372+
373+
if err := scanner.Err(); err != nil {
374+
return nil, fmt.Errorf("failed to parse port mappings: %w", err)
375+
}
376+
377+
return mappings, nil
378+
}
379+
327380
var (
328381
// dumpPortsPrefix is a sentinel value returned at the beginning of
329382
// the output from 'ovs-ofctl dump-ports'.
@@ -343,6 +396,11 @@ var (
343396
// dumpAggregatePrefix is a sentinel value returned at the beginning of
344397
// the output from "ovs-ofctl dump-aggregate"
345398
//dumpAggregatePrefix = []byte("NXST_AGGREGATE reply")
399+
400+
// portMappingPattern matches port lines from 'ovs-ofctl show' output.
401+
// Pattern matches: " 7(interface1): addr:fe:4f:76:09:88:2b"
402+
// or " 65534(LOCAL): addr:fe:4f:76:09:88:2b"
403+
portMappingPattern = regexp.MustCompile(`^\s*(\d+)\(([^)]+)\):\s*addr:([a-fA-F0-9:]+)`)
346404
)
347405

348406
// dumpPorts calls 'ovs-ofctl dump-ports' with the specified arguments and

ovs/openflow_test.go

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,3 +1415,240 @@ func mustVerifyFlowBundle(t *testing.T, stdin io.Reader, flows []*Flow, matchFlo
14151415
}
14161416
}
14171417
}
1418+
1419+
func TestClientOpenFlowDumpPortMappingsOK(t *testing.T) {
1420+
want := map[string]*PortMapping{
1421+
"interface1": {
1422+
OfPort: 7,
1423+
MACAddress: "fe:4f:76:09:88:2b",
1424+
},
1425+
"interface2": {
1426+
OfPort: 8,
1427+
MACAddress: "fe:be:7b:0d:53:d8",
1428+
},
1429+
"interface3": {
1430+
OfPort: 9,
1431+
MACAddress: "fe:b6:4c:d5:40:79",
1432+
},
1433+
"interface4": {
1434+
OfPort: 20,
1435+
MACAddress: "fe:cf:a6:90:30:29",
1436+
},
1437+
"LOCAL": {
1438+
OfPort: 65534,
1439+
MACAddress: "fe:74:0f:80:cf:9a",
1440+
},
1441+
"eth0": {
1442+
OfPort: 1,
1443+
MACAddress: "aa:bb:cc:dd:ee:ff",
1444+
},
1445+
}
1446+
1447+
bridge := "br0"
1448+
1449+
c := testClient([]OptionFunc{Timeout(1)}, func(cmd string, args ...string) ([]byte, error) {
1450+
// Verify correct command and arguments passed, including option flags
1451+
if want, got := "ovs-ofctl", cmd; want != got {
1452+
t.Fatalf("incorrect command:\n- want: %v\n- got: %v",
1453+
want, got)
1454+
}
1455+
1456+
wantArgs := []string{"--timeout=1", "show", bridge}
1457+
if want, got := wantArgs, args; !reflect.DeepEqual(want, got) {
1458+
t.Fatalf("incorrect arguments\n- want: %v\n- got: %v",
1459+
want, got)
1460+
}
1461+
1462+
return []byte(`
1463+
OFPT_FEATURES_REPLY (xid=0x2): dpid:0000000000000001
1464+
n_tables:254, n_buffers:256
1465+
capabilities: FLOW_STATS TABLE_STATS PORT_STATS QUEUE_STATS ARP_MATCH_IP
1466+
actions: output enqueue set_vlan_vid set_vlan_pcp strip_vlan mod_dl_src mod_dl_dst mod_nw_src mod_nw_dst mod_nw_tos mod_tp_src mod_tp_dst
1467+
7(interface1): addr:fe:4f:76:09:88:2b
1468+
config: 0
1469+
state: 0
1470+
current: 10GB-FD COPPER
1471+
speed: 10000 Mbps now, 0 Mbps max
1472+
8(interface2): addr:fe:be:7b:0d:53:d8
1473+
config: 0
1474+
state: 0
1475+
current: 10GB-FD COPPER
1476+
speed: 10000 Mbps now, 0 Mbps max
1477+
9(interface3): addr:fe:b6:4c:d5:40:79
1478+
config: 0
1479+
state: 0
1480+
current: 10GB-FD COPPER
1481+
speed: 10000 Mbps now, 0 Mbps max
1482+
20(interface4): addr:fe:cf:a6:90:30:29
1483+
config: 0
1484+
state: 0
1485+
current: 10GB-FD COPPER
1486+
speed: 10000 Mbps now, 0 Mbps max
1487+
65534(LOCAL): addr:fe:74:0f:80:cf:9a
1488+
config: 0
1489+
state: 0
1490+
current: 10GB-FD COPPER
1491+
speed: 10000 Mbps now, 0 Mbps max
1492+
1(eth0): addr:aa:bb:cc:dd:ee:ff
1493+
config: 0
1494+
state: 0
1495+
`), nil
1496+
})
1497+
1498+
got, err := c.OpenFlow.DumpPortMappings(bridge)
1499+
if err != nil {
1500+
t.Fatalf("unexpected error: %v", err)
1501+
}
1502+
1503+
if len(want) != len(got) {
1504+
t.Fatalf("unexpected number of mappings:\n- want: %d\n- got: %d",
1505+
len(want), len(got))
1506+
}
1507+
1508+
for name, wantMapping := range want {
1509+
gotMapping, ok := got[name]
1510+
if !ok {
1511+
t.Fatalf("missing mapping for interface %q", name)
1512+
}
1513+
1514+
if wantMapping.OfPort != gotMapping.OfPort {
1515+
t.Fatalf("unexpected OfPort for %q:\n- want: %d\n- got: %d",
1516+
name, wantMapping.OfPort, gotMapping.OfPort)
1517+
}
1518+
1519+
if wantMapping.MACAddress != gotMapping.MACAddress {
1520+
t.Fatalf("unexpected MACAddress for %q:\n- want: %q\n- got: %q",
1521+
name, wantMapping.MACAddress, gotMapping.MACAddress)
1522+
}
1523+
}
1524+
1525+
// Verify all interfaces are included
1526+
if _, ok := got["eth0"]; !ok {
1527+
t.Fatalf("all interfaces should be included, eth0 missing")
1528+
}
1529+
}
1530+
1531+
func TestClientOpenFlowDumpPortMappingsEmptyOutput(t *testing.T) {
1532+
bridge := "br0"
1533+
1534+
c := testClient(nil, func(cmd string, args ...string) ([]byte, error) {
1535+
return []byte(""), nil
1536+
})
1537+
1538+
got, err := c.OpenFlow.DumpPortMappings(bridge)
1539+
if err != nil {
1540+
t.Fatalf("unexpected error: %v", err)
1541+
}
1542+
1543+
if len(got) != 0 {
1544+
t.Fatalf("unexpected mappings for empty output:\n- want: 0\n- got: %d", len(got))
1545+
}
1546+
}
1547+
1548+
func TestClientOpenFlowDumpPortMappingsAllInterfaces(t *testing.T) {
1549+
want := map[string]*PortMapping{
1550+
"eth0": {
1551+
OfPort: 1,
1552+
MACAddress: "aa:bb:cc:dd:ee:ff",
1553+
},
1554+
"eth1": {
1555+
OfPort: 2,
1556+
MACAddress: "11:22:33:44:55:66",
1557+
},
1558+
}
1559+
1560+
bridge := "br0"
1561+
1562+
c := testClient(nil, func(cmd string, args ...string) ([]byte, error) {
1563+
return []byte(`
1564+
OFPT_FEATURES_REPLY (xid=0x2): dpid:0000000000000001
1565+
1(eth0): addr:aa:bb:cc:dd:ee:ff
1566+
2(eth1): addr:11:22:33:44:55:66
1567+
`), nil
1568+
})
1569+
1570+
got, err := c.OpenFlow.DumpPortMappings(bridge)
1571+
if err != nil {
1572+
t.Fatalf("unexpected error: %v", err)
1573+
}
1574+
1575+
if len(want) != len(got) {
1576+
t.Fatalf("unexpected number of mappings:\n- want: %d\n- got: %d", len(want), len(got))
1577+
}
1578+
1579+
for name, wantMapping := range want {
1580+
gotMapping, ok := got[name]
1581+
if !ok {
1582+
t.Fatalf("missing mapping for interface %q", name)
1583+
}
1584+
1585+
if wantMapping.OfPort != gotMapping.OfPort {
1586+
t.Fatalf("unexpected OfPort for %q:\n- want: %d\n- got: %d",
1587+
name, wantMapping.OfPort, gotMapping.OfPort)
1588+
}
1589+
1590+
if wantMapping.MACAddress != gotMapping.MACAddress {
1591+
t.Fatalf("unexpected MACAddress for %q:\n- want: %q\n- got: %q",
1592+
name, wantMapping.MACAddress, gotMapping.MACAddress)
1593+
}
1594+
}
1595+
}
1596+
1597+
func TestClientOpenFlowDumpPortMappingsOnlyLOCAL(t *testing.T) {
1598+
want := map[string]*PortMapping{
1599+
"LOCAL": {
1600+
OfPort: 65534,
1601+
MACAddress: "fe:74:0f:80:cf:9a",
1602+
},
1603+
}
1604+
1605+
bridge := "br0"
1606+
1607+
c := testClient(nil, func(cmd string, args ...string) ([]byte, error) {
1608+
return []byte(`
1609+
OFPT_FEATURES_REPLY (xid=0x2): dpid:0000000000000001
1610+
65534(LOCAL): addr:fe:74:0f:80:cf:9a
1611+
`), nil
1612+
})
1613+
1614+
got, err := c.OpenFlow.DumpPortMappings(bridge)
1615+
if err != nil {
1616+
t.Fatalf("unexpected error: %v", err)
1617+
}
1618+
1619+
if len(want) != len(got) {
1620+
t.Fatalf("unexpected number of mappings:\n- want: %d\n- got: %d",
1621+
len(want), len(got))
1622+
}
1623+
1624+
for name, wantMapping := range want {
1625+
gotMapping, ok := got[name]
1626+
if !ok {
1627+
t.Fatalf("missing mapping for interface %q", name)
1628+
}
1629+
1630+
if wantMapping.OfPort != gotMapping.OfPort {
1631+
t.Fatalf("unexpected OfPort for %q:\n- want: %d\n- got: %d",
1632+
name, wantMapping.OfPort, gotMapping.OfPort)
1633+
}
1634+
1635+
if wantMapping.MACAddress != gotMapping.MACAddress {
1636+
t.Fatalf("unexpected MACAddress for %q:\n- want: %q\n- got: %q",
1637+
name, wantMapping.MACAddress, gotMapping.MACAddress)
1638+
}
1639+
}
1640+
}
1641+
1642+
func TestClientOpenFlowDumpPortMappingsCommandError(t *testing.T) {
1643+
bridge := "br0"
1644+
wantErr := errors.New("command failed")
1645+
1646+
c := testClient(nil, func(cmd string, args ...string) ([]byte, error) {
1647+
return nil, wantErr
1648+
})
1649+
1650+
_, err := c.OpenFlow.DumpPortMappings(bridge)
1651+
if err == nil {
1652+
t.Fatalf("expected error, got nil")
1653+
}
1654+
}

0 commit comments

Comments
 (0)