Skip to content

Commit

Permalink
Initial MPLS implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
DanG100 committed Nov 8, 2024
1 parent 7a63438 commit 837b131
Show file tree
Hide file tree
Showing 9 changed files with 689 additions and 254 deletions.
26 changes: 23 additions & 3 deletions dataplane/forwarding/protocol/attr.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,15 @@ var FieldAttr = map[fwdpb.PacketFieldNum]struct {
fwdpb.PacketFieldNum_PACKET_FIELD_NUM_POLICER_ID: {
Sizes: []int{SizeUint64},
},
fwdpb.PacketFieldNum_PACKET_FIELD_NUM_MPLS_TC: {
Sizes: []int{SizeUint8},
},
fwdpb.PacketFieldNum_PACKET_FIELD_NUM_MPLS_LABEL: {
Sizes: []int{SizeUint32},
},
fwdpb.PacketFieldNum_PACKET_FIELD_NUM_MPLS_TTL: {
Sizes: []int{SizeUint8},
},
}

// GroupAttr contains attributes for each packet header group.
Expand Down Expand Up @@ -254,8 +263,19 @@ var GroupAttr = map[fwdpb.PacketHeaderGroup]struct {
fwdpb.PacketFieldNum_PACKET_FIELD_NUM_VLAN_PRIORITY,
},
},
fwdpb.PacketHeaderGroup_PACKET_HEADER_GROUP_L3: {
fwdpb.PacketHeaderGroup_PACKET_HEADER_GROUP_L2_5: {
Position: 2,
headers: []fwdpb.PacketHeaderId{
fwdpb.PacketHeaderId_PACKET_HEADER_ID_MPLS,
},
fields: []fwdpb.PacketFieldNum{
fwdpb.PacketFieldNum_PACKET_FIELD_NUM_MPLS_LABEL,
fwdpb.PacketFieldNum_PACKET_FIELD_NUM_MPLS_TC,
fwdpb.PacketFieldNum_PACKET_FIELD_NUM_MPLS_TTL,
},
},
fwdpb.PacketHeaderGroup_PACKET_HEADER_GROUP_L3: {
Position: 3,
headers: []fwdpb.PacketHeaderId{
fwdpb.PacketHeaderId_PACKET_HEADER_ID_IP4,
fwdpb.PacketHeaderId_PACKET_HEADER_ID_IP,
Expand All @@ -277,7 +297,7 @@ var GroupAttr = map[fwdpb.PacketHeaderGroup]struct {
},
},
fwdpb.PacketHeaderGroup_PACKET_HEADER_GROUP_L4: {
Position: 3,
Position: 4,
headers: []fwdpb.PacketHeaderId{
fwdpb.PacketHeaderId_PACKET_HEADER_ID_TCP,
fwdpb.PacketHeaderId_PACKET_HEADER_ID_UDP,
Expand All @@ -289,7 +309,7 @@ var GroupAttr = map[fwdpb.PacketHeaderGroup]struct {
},
},
fwdpb.PacketHeaderGroup_PACKET_HEADER_GROUP_PAYLOAD: {
Position: 4,
Position: 5,
headers: []fwdpb.PacketHeaderId{
fwdpb.PacketHeaderId_PACKET_HEADER_ID_ICMP4,
fwdpb.PacketHeaderId_PACKET_HEADER_ID_ICMP6,
Expand Down
24 changes: 13 additions & 11 deletions dataplane/forwarding/protocol/ethernet/ethernet.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,23 +58,26 @@ var tagOffsets = []int{14, 18}

// Constants defined for various ether types in network byte order.
const (
nextSingle = 0x8100 // Single tagged frame.
nextDouble = 0x9100 // Double tagged frame.
nextARP = 0x0806 // ARP payload.
nextIP4 = 0x0800 // IP4 payload.
nextIP6 = 0x86DD // IP6 payload.
nextReserved = 0xFFFF // Opaque data.
Reserved = nextReserved // Opaque data.
nextSingle = 0x8100 // Single tagged frame.
nextDouble = 0x9100 // Double tagged frame.
nextARP = 0x0806 // ARP payload.
nextIP4 = 0x0800 // IP4 payload.
nextIP6 = 0x86DD // IP6 payload.
nextMPLSUnicast = 0x8847 // MPLS Unicast
nextMPLSMulticast = 0x8848 // MPLS Mutlicast // TODO: Add support for this.
nextReserved = 0xFFFF // Opaque data.
Reserved = nextReserved // Opaque data.
)

// NextHeader maps ether-types to packet header.
var NextHeader = map[uint16]fwdpb.PacketHeaderId{}

// HeaderNext maps packet headers to ether-types.
var HeaderNext = map[fwdpb.PacketHeaderId]uint16{
fwdpb.PacketHeaderId_PACKET_HEADER_ID_IP4: nextIP4,
fwdpb.PacketHeaderId_PACKET_HEADER_ID_IP6: nextIP6,
fwdpb.PacketHeaderId_PACKET_HEADER_ID_ARP: nextARP,
fwdpb.PacketHeaderId_PACKET_HEADER_ID_IP4: nextIP4,
fwdpb.PacketHeaderId_PACKET_HEADER_ID_IP6: nextIP6,
fwdpb.PacketHeaderId_PACKET_HEADER_ID_ARP: nextARP,
fwdpb.PacketHeaderId_PACKET_HEADER_ID_MPLS: nextMPLSUnicast,
}

// An Ethernet presents a ethernet header. It can parse, query, add, remove
Expand Down Expand Up @@ -125,7 +128,6 @@ func (eth *Ethernet) field(id fwdpacket.FieldID) frame.Field {

case fwdpb.PacketFieldNum_PACKET_FIELD_NUM_VLAN_TAG, fwdpb.PacketFieldNum_PACKET_FIELD_NUM_VLAN_PRIORITY:
if int(id.Instance) >= len(eth.vlans) {
// TOOD(neeleshb): We currently ignore the vlan priority due to b/31199367.
if id.Num == fwdpb.PacketFieldNum_PACKET_FIELD_NUM_VLAN_PRIORITY {
return make([]byte, vlanBytes)
}
Expand Down
1 change: 1 addition & 0 deletions dataplane/forwarding/protocol/mpls/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ go_library(
importpath = "github.com/openconfig/lemming/dataplane/forwarding/protocol/mpls",
visibility = ["//visibility:public"],
deps = [
"//dataplane/forwarding/infra/fwdpacket",
"//dataplane/forwarding/protocol",
"//dataplane/forwarding/util/frame",
"//proto/forwarding",
Expand Down
178 changes: 168 additions & 10 deletions dataplane/forwarding/protocol/mpls/mpls.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,196 @@
package mpls

import (
"bytes"
"encoding/binary"
"fmt"

"github.com/openconfig/lemming/dataplane/forwarding/infra/fwdpacket"
"github.com/openconfig/lemming/dataplane/forwarding/protocol"
"github.com/openconfig/lemming/dataplane/forwarding/util/frame"
fwdpb "github.com/openconfig/lemming/proto/forwarding"
)

type mplsLabel struct {
hdr frame.Header
}

type mpls struct {
protocol.Handler
desc *protocol.Desc
desc *protocol.Desc
labels []*mplsLabel
}

func (m *mpls) Header() []byte {
b := make([][]byte, len(m.labels))
for p, h := range m.labels {
b[p] = h.hdr
}
return bytes.Join(b, []byte{})
}

func (m *mpls) Trailer() []byte {
return nil
}

func (m *mpls) ID(instance int) fwdpb.PacketHeaderId {
return fwdpb.PacketHeaderId_PACKET_HEADER_ID_MPLS
}

const (
labelOffset = 0
labelByteLen = 4 // To make dealing with label easier, encode it as uint32.
labelBitOffset = 12 // The label is the leftmost 20 bits of the header, so shift 12 bits.
labelBitLen = 20
tcOffset = 2
tcByteLen = 1
tcBitOffset = 1
tcBitLen = 3
ttlOffset = 3
ttlByteLen = 1
botOffset = 2
botByteLen = 1
botBitOffset = 0
botBitLen = 1
)

func (m *mpls) field(id fwdpacket.FieldID) frame.Field {
l := m.labels[id.Instance]
if id.IsUDF {
return protocol.UDF(l.hdr, id)
}
switch id.Num {
case fwdpb.PacketFieldNum_PACKET_FIELD_NUM_MPLS_LABEL:
return l.hdr.Field(labelOffset, labelByteLen)
case fwdpb.PacketFieldNum_PACKET_FIELD_NUM_MPLS_TC:
return l.hdr.Field(tcOffset, tcByteLen)
case fwdpb.PacketFieldNum_PACKET_FIELD_NUM_MPLS_TTL:
return l.hdr.Field(ttlOffset, ttlByteLen)
default:
return nil
}
}

func (m *mpls) Field(id fwdpacket.FieldID) ([]byte, error) {
field := m.field(id)
if field == nil {
return nil, fmt.Errorf("ethernet: Field failed, field %v does not exist", id)
}
if id.IsUDF {
return field.Copy(), nil
}
switch id.Num {
case fwdpb.PacketFieldNum_PACKET_FIELD_NUM_MPLS_LABEL:
return field.BitField(labelBitOffset, labelBitLen), nil
case fwdpb.PacketFieldNum_PACKET_FIELD_NUM_MPLS_TC:
return field.BitField(tcBitOffset, tcBitLen), nil
default:
return field.Copy(), nil
}
}

func (m *mpls) UpdateField(id fwdpacket.FieldID, op int, arg []byte) (bool, error) {
field := m.field(id)
if field == nil {
return false, fmt.Errorf("mpls: UpdateField failed, field %v does not exist", id)
}
if op == fwdpacket.OpDec && id.Num == fwdpb.PacketFieldNum_PACKET_FIELD_NUM_MPLS_TTL {
pttl := uint8(field.Value())
nttl := pttl - 1
field.SetValue(uint(nttl))
return false, nil
}
if op != fwdpacket.OpSet {
return false, fmt.Errorf("only SET is supported")
}

if id.IsUDF && op == fwdpacket.OpSet {
return true, field.Set(arg)
}
switch id.Num {
case fwdpb.PacketFieldNum_PACKET_FIELD_NUM_MPLS_LABEL:
field.SetBits(labelBitOffset, labelBitLen, uint64(binary.BigEndian.Uint32(arg)))
return true, nil
case fwdpb.PacketFieldNum_PACKET_FIELD_NUM_MPLS_TC:
field.SetBits(tcBitOffset, tcBitLen, uint64(arg[0]))
return true, nil
default:
return true, field.Set(arg)
}
}

func (m *mpls) Remove(id fwdpb.PacketHeaderId) error {
if len(m.labels) > 1 {
m.labels = m.labels[1:]
return nil
}
m.labels = nil
return nil
}

func (m *mpls) Modify(id fwdpb.PacketHeaderId) error {
m.labels = append([]*mplsLabel{{hdr: make(frame.Header, 4)}}, m.labels...)
return nil
}

func (m *mpls) Rebuild() error {
if len(m.labels) == 0 {
return nil
}
// Set the bottom of stack bit 0 on all but the last label. Set that it to 1.
for i := 0; i < len(m.labels)-1; i++ {
m.labels[i].hdr.Field(botOffset, botByteLen).SetBits(botBitOffset, botBitLen, 0)
}
m.labels[len(m.labels)-1].hdr.Field(botOffset, botByteLen).SetBits(botBitOffset, botBitLen, 1)
return nil
}

const (
ipv4ExplicitNull = 0
ipv6ExplicitNull = 3
)

func parseMPLS(f *frame.Frame, desc *protocol.Desc) (protocol.Handler, fwdpb.PacketHeaderId, error) {
h := &mpls{
m := &mpls{
desc: desc,
}

var label frame.Field
for {
label, err := f.Peek(0, 3)
hdr, err := f.ReadHeader(4)
if err != nil {
return nil, 0, err
return nil, fwdpb.PacketHeaderId_PACKET_HEADER_ID_NONE, fmt.Errorf("failed to parse MPLS: %v", err)
}
tc := label.BitField(20, 3)
bot := label.BitField(23, 1)
if bot.Value() == 1 {
botStack := hdr.Field(botOffset, botByteLen).BitField(botBitOffset, botBitLen) // bottom of stack is the last bit of the 3 byte.

m.labels = append(m.labels, &mplsLabel{
hdr: hdr,
})
label = hdr.Field(labelOffset, labelByteLen).BitField(labelBitOffset, labelBitLen)
if botStack.Value() == 1 {
break
}
}
// If the last label is explicit null, then use that to parse the next header, otherwise treat the rest of the packet as opaque.
next := fwdpb.PacketHeaderId_PACKET_HEADER_ID_OPAQUE
switch label.Value() {
case ipv4ExplicitNull:
next = fwdpb.PacketHeaderId_PACKET_HEADER_ID_IP4
case ipv6ExplicitNull:
next = fwdpb.PacketHeaderId_PACKET_HEADER_ID_IP6
}

return h, fwdpb.PacketHeaderId_PACKET_HEADER_ID_OPAQUE, nil
return m, next, nil
}

func add(id fwdpb.PacketHeaderId, _ *protocol.Desc) (protocol.Handler, error) {
return nil, nil
func add(id fwdpb.PacketHeaderId, desc *protocol.Desc) (protocol.Handler, error) {
if id != fwdpb.PacketHeaderId_PACKET_HEADER_ID_MPLS {
return nil, fmt.Errorf("unsupport type: %v", id)
}
return &mpls{
desc: desc,
labels: []*mplsLabel{{hdr: make(frame.Header, 4)}},
}, nil
}

func init() {
Expand Down
4 changes: 4 additions & 0 deletions dataplane/forwarding/protocol/packet/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ go_test(
"ip6_test.go",
"metadata_test.go",
"mirror_test.go",
"mpls_test.go",
"tcp_test.go",
"tunnel_test.go",
"udp_test.go",
Expand All @@ -24,11 +25,14 @@ go_test(
"//dataplane/forwarding/protocol/icmp",
"//dataplane/forwarding/protocol/ip",
"//dataplane/forwarding/protocol/metadata",
"//dataplane/forwarding/protocol/mpls",
"//dataplane/forwarding/protocol/opaque",
"//dataplane/forwarding/protocol/packettestutil",
"//dataplane/forwarding/protocol/tcp",
"//dataplane/forwarding/protocol/udp",
"//dataplane/forwarding/util/frame",
"//proto/forwarding",
"@com_github_google_gopacket//:gopacket",
"@com_github_google_gopacket//layers",
],
)
Loading

0 comments on commit 837b131

Please sign in to comment.