Skip to content

Commit

Permalink
Merge pull request #21 from Yuya9786/feature/rebased-ioam6-timestamp
Browse files Browse the repository at this point in the history
Implement draft-ietf-opsawg-ipfix-on-path-telemetry
  • Loading branch information
watal authored Nov 13, 2023
2 parents 17c7997 + 6e1766e commit 8d1a6ee
Show file tree
Hide file tree
Showing 25 changed files with 933 additions and 192 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,16 @@ Fluvia Exporter is licensed under the [MIT license](https://en.wikipedia.org/wik
For the full license text, see [LICENSE](https://github.com/nttcom/fluvia/blob/master/LICENSE).

## Miscellaneous
Fluvia Exporter supports the following IETF Internet-Drafts:
- [Export of Segment Routing over IPv6 Information in IP Flow Information Export (IPFIX)](https://datatracker.ietf.org/doc/html/draft-ietf-opsawg-ipfix-srv6-srh-14)
- IPFIX Library: Supports all IEs.
- IPFIX Exporter: Implemented the following IEs.
Fluvia Exporter supports the following IEs:
- packetDeltaCount
- [draft-ietf-opsawg-ipfix-srv6-srh](https://datatracker.ietf.org/doc/draft-ietf-opsawg-ipfix-srv6-srh/)
- srhActiveSegmentIPv6
- srhSegmentsIPv6Left
- srhFlagsIPv6
- srhTagIPv6
- srhSegmentIPv6BasicList
- [draft-ietf-opsawg-ipfix-on-path-telemetry](https://datatracker.ietf.org/doc/draft-ietf-opsawg-ipfix-on-path-telemetry/)
- PathDelayMeanDeltaMicroseconds
- PathDelayMaxDeltaMicroseconds
- PathDelayMinDeltaMicroseconds
- PathDelaySumDeltaMicroseconds
7 changes: 6 additions & 1 deletion cmd/fluvia/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,10 @@ func main() {
ingressIfName = c.Ipfix.IngressInterface
}

client.New(ingressIfName, raddr)
interval := c.Ipfix.Interval
if interval <= 0 {
interval = 1
}

client.New(ingressIfName, raddr, interval)
}
3 changes: 3 additions & 0 deletions docs/sources/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ ipfix:
address: 192.0.2.1
port: 4739
ingress-interface: ens192
interval: 1
```
interval is the intervals between exports (seconds) and the default is 1 second.
### Run Fluvia Exporter using the fluvia command
Start the fluvia command. Specify the created configuration file with the -f option.
Expand Down
8 changes: 3 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ go 1.20
require (
github.com/cilium/ebpf v0.12.2
github.com/google/gopacket v1.1.19
github.com/pkg/errors v0.9.1
golang.org/x/sync v0.0.0-20190423024810-112230192c58
golang.org/x/sys v0.10.0
gopkg.in/yaml.v3 v3.0.1
)

require (
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect
golang.org/x/sys v0.10.0 // indirect
)
require golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect
3 changes: 1 addition & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
Expand All @@ -17,6 +15,7 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPI
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Ipfix struct {
Address string `yaml:"address"`
Port string `yaml:"port"`
IngressInterface string `yaml:"ingress-interface"`
Interval int `yaml:"interval"`
}

type Config struct {
Expand Down
212 changes: 212 additions & 0 deletions internal/pkg/meter/hbh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
package meter

import (
"encoding/binary"
"fmt"

"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)

const IPV6_TLV_PAD1 = 0

type HBHLayer struct {
layers.BaseLayer
NextHeader uint8
Length uint8
Options []IoamOption
}

type IoamOption struct {
Type uint8
Length uint8
Reserved uint8
OptionType uint8
TraceHeader IoamTrace
}

type IoamTrace struct {
NameSpaceId uint16
NodeLen uint8
Flags byte
RemainingLen uint8
Type [3]byte
Reserved byte
NodeDataList []NodeData
}

type NodeData struct {
HopLimitNodeId [4]byte
IngressEgressIds [4]byte
Second [4]byte
Subsecond [4]byte
}

var HBHLayerType = gopacket.RegisterLayerType(
2002,
gopacket.LayerTypeMetadata{
Name: "HBHLayerType",
Decoder: gopacket.DecodeFunc(decodeHBHLayer),
},
)

func (l *HBHLayer) LayerType() gopacket.LayerType {
return HBHLayerType
}

func (l *HBHLayer) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
p := 0

// Min length of each header
// HBHLayer = 2, IoamOption = 3, IoamTrae = 8
if len(data) < 2+3+8 {
df.SetTruncated()
return fmt.Errorf("HBH layer less than 2 bytes for HBH packet")
}

l.NextHeader = data[p]
p++
l.Length = data[p]
p++

optionIdx := 0
for {
if data[p] != IPV6_TLV_PAD1 {
break
}

l.Options[optionIdx].Type = IPV6_TLV_PAD1
optionIdx = optionIdx + 1
p = p + 1
}

ioamOption := l.Options[optionIdx]

ioamOption.Type = data[p]
p++
ioamOption.Length = data[p]
p++
ioamOption.Reserved = data[p]
p++
ioamOption.OptionType = data[p]
p++

trace := ioamOption.TraceHeader
trace.NameSpaceId = binary.BigEndian.Uint16(data[p : p+2])
p = p + 2
trace.NodeLen = data[p] >> 3
trace.Flags = ((data[p] & 0b00000111) << 1) | (data[p+1] >> 7)
p = p + 1
trace.RemainingLen = data[p] & 0b01111111
p = p + 1
copy(trace.Type[:], data[p:p+3])
p = p + 3
trace.Reserved = data[p]
p++

traceDataLen := ioamOption.Length - (2 + 8)
for i := 0; i < int(traceDataLen)/4/int(trace.NodeLen); i++ {
var (
hopLimitNodeId [4]byte
ingressEgressIds [4]byte
second [4]byte
subsecond [4]byte
)

copy(hopLimitNodeId[:], data[p+16*i:p+16*i+4])
copy(ingressEgressIds[:], data[p+16*i+4:p+16*i+8])
copy(second[:], data[p+16*i+8:p+16*i+12])
copy(subsecond[:], data[p+16*i+12:p+16*i+16])

nodeData := NodeData{
HopLimitNodeId: hopLimitNodeId,
IngressEgressIds: ingressEgressIds,
Second: second,
Subsecond: subsecond,
}

trace.NodeDataList = append(trace.NodeDataList, nodeData)
}

return nil
}

func (l *HBHLayer) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
length := l.Length*8 + 8
bytes, err := b.PrependBytes(int(length))
if err != nil {
return err
}

p := 0

bytes[p] = l.NextHeader
p++
bytes[p] = l.Length
p++

optionIdx := 0
for i, option := range l.Options {
if option.Type != IPV6_TLV_PAD1 {
optionIdx = i
break
}

bytes[p] = IPV6_TLV_PAD1
p++
}

ioamOption := l.Options[optionIdx]
bytes[p] = ioamOption.Type
p++
bytes[p] = ioamOption.Length
p++
bytes[p] = ioamOption.Reserved
p++
bytes[p] = ioamOption.OptionType
p++

traceOption := ioamOption.TraceHeader
binary.BigEndian.PutUint16(bytes[p:], traceOption.NameSpaceId)
p = p + 2
bytes[p] = traceOption.NodeLen << 3
bytes[p] = (bytes[p] & 0xf8) | ((traceOption.Flags >> 1) & 0x07)
p++
bytes[p] = (traceOption.Flags & 0x01) << 7
bytes[p] = (bytes[p] & 0x80) | (traceOption.RemainingLen & 0x7f)
p++
copy(bytes[p:p+3], traceOption.Type[:])
p = p + 3
bytes[p] = traceOption.Reserved
p++

traceDataLen := ioamOption.Length - (2 + 8)
for i := 0; i < int(traceDataLen)/4/int(traceOption.NodeLen); i++ {
nodeData := traceOption.NodeDataList[i]
copy(bytes[p+16*i:p+16*i+4], nodeData.HopLimitNodeId[:])
copy(bytes[p+16*i+4:p+16*i+8], nodeData.IngressEgressIds[:])
copy(bytes[p+16*i+8:p+16*i+12], nodeData.Second[:])
copy(bytes[p+16*i+12:p+16*i+16], nodeData.Subsecond[:])
}

return nil
}

func (l *HBHLayer) NextLayerType() gopacket.LayerType {
return gopacket.LayerTypePayload
}

func decodeHBHLayer(data []byte, p gopacket.PacketBuilder) error {
l := &HBHLayer{}
err := l.DecodeFromBytes(data, p)
if err != nil {
return nil
}
p.AddLayer(l)
next := l.NextLayerType()
if next == gopacket.LayerTypeZero {
return nil
}

return p.NextDecoder(next)
}
86 changes: 86 additions & 0 deletions internal/pkg/meter/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package meter

import (
"fmt"

"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)

const MAX_SEGMENTLIST_ENTRIES = 10

type ProbeData struct {
H_source string
H_dest string
V6Srcaddr string
V6Dstaddr string
NextHdr uint8
HdrExtLen uint8
RoutingType uint8
SegmentsLeft uint8
LastEntry uint8
Flags uint8
Tag uint16
Segments [MAX_SEGMENTLIST_ENTRIES]string
}

func Parse(data []byte) (*ProbeData, error) {
var pd ProbeData
packet := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.Default)

ethLayer := packet.Layer(layers.LayerTypeEthernet)
eth, ok := ethLayer.(*layers.Ethernet)
if !ok {
return nil, fmt.Errorf("Could not parse a packet with Ethernet")
}

pd.H_dest = eth.DstMAC.String()
pd.H_source = eth.SrcMAC.String()

ipv6Layer := packet.Layer(layers.LayerTypeIPv6)
ipv6, ok := ipv6Layer.(*layers.IPv6)
if !ok {
return nil, fmt.Errorf("Could not parse a packet with IPv6")
}

pd.V6Srcaddr = ipv6.SrcIP.String()
pd.V6Dstaddr = ipv6.DstIP.String()

if ipv6.NextHeader != layers.IPProtocolIPv6HopByHop {
return nil, fmt.Errorf("Next header is not IPv6 hop-by-hop(0): %d", ipv6.NextHeader)
}

ipv6HBHLayer := packet.Layer(layers.LayerTypeIPv6HopByHop)
hbh, ok := ipv6HBHLayer.(*layers.IPv6HopByHop)
if !ok {
return nil, fmt.Errorf("Could not parse a packet with ipv6 hop-by-hop option")
}

if hbh.NextHeader != layers.IPProtocolIPv6Routing {
return nil, fmt.Errorf("Next header is not SRv6: %d", hbh.NextHeader)
}

packet = gopacket.NewPacket(ipv6HBHLayer.LayerPayload(), Srv6LayerType, gopacket.Lazy)
srv6Layer := packet.Layer(Srv6LayerType)
srv6, ok := srv6Layer.(*Srv6Layer)
if !ok {
return nil, fmt.Errorf("Could not parse a packet with SRv6")
}

pd.NextHdr = srv6.NextHeader
pd.HdrExtLen = srv6.HdrExtLen
pd.RoutingType = srv6.RoutingType
pd.SegmentsLeft = srv6.SegmentsLeft
pd.LastEntry = srv6.LastEntry
pd.Flags = srv6.Flags
pd.Tag = srv6.Tag

for idx := 0; idx < MAX_SEGMENTLIST_ENTRIES; idx++ {
if idx >= len(srv6.Segments) {
break
}
pd.Segments[idx] = srv6.Segments[idx].String()
}

return &pd, nil
}
Loading

0 comments on commit 8d1a6ee

Please sign in to comment.