From adc2718d173fa6b07cedaba99c58348530e7ca96 Mon Sep 17 00:00:00 2001 From: Yuya Tajima Date: Thu, 21 Sep 2023 15:21:04 +0000 Subject: [PATCH 01/12] Add func to get timestamp from packets into xdp --- src/main.c | 132 ++++++++++++++++++++++++++++++++--------------- src/xdp_consts.h | 1 - src/xdp_map.h | 6 +-- src/xdp_struct.h | 18 ++----- 4 files changed, 97 insertions(+), 60 deletions(-) diff --git a/src/main.c b/src/main.c index f32fd4a..b65766e 100644 --- a/src/main.c +++ b/src/main.c @@ -3,6 +3,8 @@ * Copyright (c) 2023 Takeru Hayasaka */ +#include "xdp_consts.h" +#include "xdp_struct.h" #define KBUILD_MODNAME "xdp_probe" #include #include @@ -12,19 +14,54 @@ #include #include #include +#include #include #include #include "xdp_map.h" +static inline int parse_ioam6_trace_header(struct ioam6_trace_hdr *ith, int hdr_len, struct metadata *key, void *data_end) +{ + __u8 second_index, subsecond_index; + __u32 second, subsecond; + + if ((void *)(ith + 1) > data_end) + return -1; + + second_index = hdr_len - 8; + subsecond_index = hdr_len - 4; + + if ((void *)ith + second_index + 4 > data_end) return -1; + second = bpf_ntohl(*(__u32 *)((void *)ith + second_index)); + + if ((void *)ith + subsecond_index + 4 > data_end) return -1; + subsecond = bpf_ntohl(*(__u32 *)((void *)ith + subsecond_index)); + + key->sent_second = second; + key->sent_subsecond = subsecond; + + return 0; +} + SEC("xdp") int xdp_prog(struct xdp_md *ctx) { void *data_end = (void *)(long)ctx->data_end; void *data = (void *)(long)ctx->data; - __u32 probe_key = XDP_PASS; + __u64 packet_size = data_end - data; + __u8 *p; + struct metadata md = {}; + int ret, hoplen; + struct ethhdr *eth = data; + struct ipv6hdr *ipv6; + struct srhhdr *srh; + struct ipv6_hopopt_hdr *hopopth; + struct ioam6_hdr *ioam6h; + struct ioam6_trace_hdr *ioam6_trace_h; + + md.received_nanosecond = bpf_ktime_get_ns(); if ((void *)(eth + 1) > data_end) return XDP_PASS; @@ -32,59 +69,72 @@ int xdp_prog(struct xdp_md *ctx) if (eth->h_proto != bpf_htons(ETH_P_IPV6)) return XDP_PASS; - struct ipv6hdr *ipv6 = (void *)(eth + 1); + ipv6 = (void *)(eth + 1); if ((void *)(ipv6 + 1) > data_end) return XDP_PASS; - // is srv6 - if (ipv6->nexthdr != IPPROTO_IPV6ROUTE) + if (ipv6->nexthdr != IPPROTO_HOPOPTS) return XDP_PASS; - struct srhhdr *srh = (void *)(ipv6 + 1); - if ((void *)(srh + 1) > data_end) + hopopth = (struct ipv6_hopopt_hdr *)(ipv6 + 1); + if ((void *)(hopopth + 1) > data_end) return XDP_PASS; - if (srh->routingType != IPV6_SRCRT_TYPE_4) // IPV6_SRCRT_TYPE_4 = SRH + hoplen = (hopopth->hdrlen + 1) << 3; + + p = (__u8 *)(hopopth + 1); + + if ((void *)(p + 1) > data_end) + return XDP_PASS; + + if (*p == IPV6_TLV_PAD1) { + p += 1; + } + + if ((void *)(p + 1) > data_end) + return XDP_PASS; + + if (*p == IPV6_TLV_PAD1) { + p += 1; + } + + ioam6h = (struct ioam6_hdr *)p; + if ((void *)(ioam6h + 1) > data_end) { + return XDP_PASS; + } + + if (ioam6h->opt_type != IPV6_TLV_IOAM) { return XDP_PASS; + } + + if (ioam6h->type != IOAM6_TYPE_PREALLOC) { + return XDP_PASS; + } - struct probe_data key = {}; - __u64 zero = 0, *value; - __builtin_memcpy(&key.h_source, ð->h_source, ETH_ALEN); - __builtin_memcpy(&key.h_dest, ð->h_dest, ETH_ALEN); - key.h_proto = eth->h_proto; - key.v6_srcaddr = ipv6->saddr; - key.v6_dstaddr = ipv6->daddr; - - key.nextHdr = srh->nextHdr; - key.hdrExtLen = srh->hdrExtLen; - key.routingType = srh->routingType; - key.segmentsLeft = srh->segmentsLeft; - key.lastEntry = srh->lastEntry; - key.flags = srh->flags; - key.tag = srh->tag; - - for (int i = 0; i < MAX_SEGMENTLIST_ENTRIES; i++) - { - if (!(i < key.lastEntry + 1)) - break; - - if ((void *)(data + sizeof(struct ethhdr) + sizeof(struct ipv6hdr) + sizeof(struct srhhdr) + sizeof(struct in6_addr) * (i + 1) + 1) > data_end) - break; - - __builtin_memcpy(&key.segments[i], &srh->segments[i], sizeof(struct in6_addr)); + ioam6_trace_h = (struct ioam6_trace_hdr *)(ioam6h + 1); + if ((void *)(ioam6_trace_h + 1) > data_end) { + return XDP_PASS; } - value = bpf_map_lookup_elem(&ipfix_probe_map, &key); - if (!value) - { - bpf_map_update_elem(&ipfix_probe_map, &key, &zero, BPF_NOEXIST); - value = bpf_map_lookup_elem(&ipfix_probe_map, &key); - if (!value) - return XDP_PASS; + ret = parse_ioam6_trace_header(ioam6_trace_h, ioam6h->opt_len - 2, &md, data_end); + if (ret != 0) { + return XDP_PASS; } - (*value)++; + + if (hopopth->nexthdr != IPPROTO_IPV6ROUTE) + return XDP_PASS; + + srh = (struct srhhdr *)((void *)hopopth + hoplen); + if ((void *)(srh + 1) > data_end) + return XDP_PASS; + + if (srh->routingType != IPV6_SRCRT_TYPE_4) // IPV6_SRCRT_TYPE_4 = SRH + return -1; + + __u64 flags = BPF_F_CURRENT_CPU | (packet_size << 32); + bpf_perf_event_output(ctx, &packet_probe_perf, flags, &md, sizeof(md)); return XDP_PASS; } -char _license[] SEC("license") = "MIT"; +char _license[] SEC("license") = "Dual MIT/GPL"; diff --git a/src/xdp_consts.h b/src/xdp_consts.h index d7b8e73..a1b52ec 100644 --- a/src/xdp_consts.h +++ b/src/xdp_consts.h @@ -7,7 +7,6 @@ #define __XDP_CONSTS_H #define MAX_MAP_ENTRIES 1024 -#define MAX_SEGMENTLIST_ENTRIES 10 #define IPPROTO_IPV6ROUTE 43 #endif diff --git a/src/xdp_map.h b/src/xdp_map.h index 0e6736a..03d8435 100644 --- a/src/xdp_map.h +++ b/src/xdp_map.h @@ -14,10 +14,8 @@ #include "xdp_struct.h" struct { - __uint(type, BPF_MAP_TYPE_LRU_HASH); + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); __uint(max_entries, MAX_MAP_ENTRIES); - __type(key, struct probe_data); - __type(value, __u64); -} ipfix_probe_map SEC(".maps"); +} packet_probe_perf SEC(".maps"); #endif diff --git a/src/xdp_struct.h b/src/xdp_struct.h index 2165448..5770b81 100644 --- a/src/xdp_struct.h +++ b/src/xdp_struct.h @@ -25,21 +25,11 @@ struct srhhdr struct in6_addr segments[0]; }; -struct probe_data +struct metadata { - __u8 h_dest[ETH_ALEN]; - __u8 h_source[ETH_ALEN]; - __be16 h_proto; - struct in6_addr v6_srcaddr; - struct in6_addr v6_dstaddr; - __u8 nextHdr; - __u8 hdrExtLen; - __u8 routingType; - __u8 segmentsLeft; - __u8 lastEntry; - __u8 flags; - __u16 tag; - struct in6_addr segments[MAX_SEGMENTLIST_ENTRIES]; + __u64 received_nanosecond; + __u32 sent_second; + __u32 sent_subsecond; }; #endif From e8db30c1b89764bfefb31bc25225ae48debcfb86 Mon Sep 17 00:00:00 2001 From: Yuya Tajima Date: Wed, 1 Nov 2023 13:17:23 +0900 Subject: [PATCH 02/12] Add ipfix path delay definitions --- pkg/{packet => }/ipfix/field_value.go | 96 +++++++++++++++++++++++++++ pkg/{packet => }/ipfix/ipfix.go | 0 2 files changed, 96 insertions(+) rename pkg/{packet => }/ipfix/field_value.go (94%) rename pkg/{packet => }/ipfix/ipfix.go (100%) diff --git a/pkg/packet/ipfix/field_value.go b/pkg/ipfix/field_value.go similarity index 94% rename from pkg/packet/ipfix/field_value.go rename to pkg/ipfix/field_value.go index 9fa0b14..b7c6b92 100644 --- a/pkg/packet/ipfix/field_value.go +++ b/pkg/ipfix/field_value.go @@ -294,6 +294,102 @@ func (fv *SRHSegmentIPv6EndpointBehavior) FieldSpecifier() *FieldSpecifier { return fs } +type PathDelayMeanDeltaMicroseconds struct { + Val uint32 +} + +func (fv *PathDelayMeanDeltaMicroseconds) ElementID() uint16 { + return IEID_PATH_DELAY_MEAN_DALTA_MICROSECONDS +} + +func (fv *PathDelayMeanDeltaMicroseconds) Serialize() []uint8 { + ret := make([]uint8, 4) + binary.BigEndian.PutUint32(ret, fv.Val) + return ret +} + +func (fv *PathDelayMeanDeltaMicroseconds) Len() uint16 { + return 4 +} + +func (fv *PathDelayMeanDeltaMicroseconds) FieldSpecifier() *FieldSpecifier { + templateLen := fv.Len() + fs := NewFieldSpecifier(false, fv.ElementID(), templateLen, ENTERPRISE_NUMBER_NTTCOM) + return fs +} + +type PathDelayMinDeltaMicroseconds struct { + Val uint32 +} + +func (fv *PathDelayMinDeltaMicroseconds) ElementID() uint16 { + return IEID_PATH_DELAY_MIN_DALTA_MICROSECONDS +} + +func (fv *PathDelayMinDeltaMicroseconds) Serialize() []uint8 { + ret := make([]uint8, 4) + binary.BigEndian.PutUint32(ret, fv.Val) + return ret +} + +func (fv *PathDelayMinDeltaMicroseconds) Len() uint16 { + return 4 +} + +func (fv *PathDelayMinDeltaMicroseconds) FieldSpecifier() *FieldSpecifier { + templateLen := fv.Len() + fs := NewFieldSpecifier(false, fv.ElementID(), templateLen, ENTERPRISE_NUMBER_NTTCOM) + return fs +} + +type PathDelayMaxDeltaMicroseconds struct { + Val uint32 +} + +func (fv *PathDelayMaxDeltaMicroseconds) ElementID() uint16 { + return IEID_PATH_DELAY_MAX_DALTA_MICROSECONDS +} + +func (fv *PathDelayMaxDeltaMicroseconds) Serialize() []uint8 { + ret := make([]uint8, 4) + binary.BigEndian.PutUint32(ret, fv.Val) + return ret +} + +func (fv *PathDelayMaxDeltaMicroseconds) Len() uint16 { + return 4 +} + +func (fv *PathDelayMaxDeltaMicroseconds) FieldSpecifier() *FieldSpecifier { + templateLen := fv.Len() + fs := NewFieldSpecifier(false, fv.ElementID(), templateLen, ENTERPRISE_NUMBER_NTTCOM) + return fs +} + +type PathDelaySumDeltaMicroseconds struct { + Val uint32 +} + +func (fv *PathDelaySumDeltaMicroseconds) ElementID() uint16 { + return IEID_PATH_DELAY_SUM_DALTA_MICROSECONDS +} + +func (fv *PathDelaySumDeltaMicroseconds) Serialize() []uint8 { + ret := make([]uint8, 4) + binary.BigEndian.PutUint32(ret, fv.Val) + return ret +} + +func (fv *PathDelaySumDeltaMicroseconds) Len() uint16 { + return 4 +} + +func (fv *PathDelaySumDeltaMicroseconds) FieldSpecifier() *FieldSpecifier { + templateLen := fv.Len() + fs := NewFieldSpecifier(false, fv.ElementID(), templateLen, ENTERPRISE_NUMBER_NTTCOM) + return fs +} + type UndefinedFieldValue struct { ElemID uint16 Value []uint8 diff --git a/pkg/packet/ipfix/ipfix.go b/pkg/ipfix/ipfix.go similarity index 100% rename from pkg/packet/ipfix/ipfix.go rename to pkg/ipfix/ipfix.go From 1a17ef6c656a8b35eea471a157709439eab0c523 Mon Sep 17 00:00:00 2001 From: Yuya Tajima Date: Wed, 1 Nov 2023 00:37:35 +0000 Subject: [PATCH 03/12] Add func to parse packets received from xdp --- pkg/bpf/srv6.go | 12 ++++--- pkg/packet/parse.go | 88 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 pkg/packet/parse.go diff --git a/pkg/bpf/srv6.go b/pkg/bpf/srv6.go index 2936829..ce7496b 100644 --- a/pkg/bpf/srv6.go +++ b/pkg/bpf/srv6.go @@ -3,7 +3,7 @@ package bpf import ( "encoding/binary" "errors" - "net" + "net/netip" "github.com/google/gopacket" "github.com/google/gopacket/layers" @@ -18,7 +18,7 @@ type Srv6Layer struct { LastEntry uint8 Flags uint8 Tag uint16 - Segments []net.IP + Segments []netip.Addr } var Srv6LayerType = gopacket.RegisterLayerType( @@ -50,10 +50,11 @@ func (i *Srv6Layer) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) err startBit := 8 + 16*j endBit := 24 + 16*j var addr []byte - for k := endBit; k >= startBit; k-- { + for k := startBit; k < endBit; k++ { addr = append(addr, data[k]) } - i.Segments = append(i.Segments, addr) + seg, _ := netip.AddrFromSlice(addr[:16]) + i.Segments = append(i.Segments, seg) } i.BaseLayer = layers.BaseLayer{ Contents: data[:8], @@ -76,7 +77,8 @@ func (i *Srv6Layer) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.Serial bytes[5] = i.Flags binary.BigEndian.PutUint16(bytes[6:], i.Tag) - for i2, address := range i.Segments { + for i2, seg := range i.Segments { + address := seg.AsSlice() lsb := binary.BigEndian.Uint64(address[:8]) msb := binary.BigEndian.Uint64(address[8:]) binary.BigEndian.PutUint64(bytes[8+16*i2:], lsb) diff --git a/pkg/packet/parse.go b/pkg/packet/parse.go new file mode 100644 index 0000000..a1e76b0 --- /dev/null +++ b/pkg/packet/parse.go @@ -0,0 +1,88 @@ +package packet + +import ( + "errors" + "fmt" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/nttcom/fluvia/pkg/bpf" +) + +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, errors.New("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, errors.New("Could not parse a packet with IPv6") + } + + pd.V6Srcaddr = ipv6.SrcIP.String() + pd.V6Dstaddr = ipv6.DstIP.String() + + if ipv6.NextHeader != layers.IPProtocolIPv6HopByHop { + return nil, errors.New(fmt.Sprintf("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, errors.New("Could not parse a packet with ipv6 hop-by-hop option") + } + + if hbh.NextHeader != layers.IPProtocolIPv6Routing { + return nil, errors.New(fmt.Sprintf("Next header is not SRv6: %d", hbh.NextHeader)) + } + + packet = gopacket.NewPacket(ipv6HBHLayer.LayerPayload(), bpf.Srv6LayerType, gopacket.Lazy) + srv6Layer := packet.Layer(bpf.Srv6LayerType) + srv6, ok := srv6Layer.(*bpf.Srv6Layer) + if !ok { + return nil, errors.New("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 +} From 2a48e3223d94b30adf4777aea4bdafa64b288ab9 Mon Sep 17 00:00:00 2001 From: Yuya Tajima Date: Wed, 25 Oct 2023 13:40:24 +0000 Subject: [PATCH 04/12] Add getting IOAM timestamp from xdp into meter --- cmd/fluvia/main.go | 7 +- go.mod | 8 +- go.sum | 3 +- internal/config/config.go | 1 + pkg/bpf/bpf.go | 88 ++++++++------ pkg/bpf/xdp_bpfeb.go | 6 +- pkg/bpf/xdp_bpfeb.o | Bin 7368 -> 6240 bytes pkg/bpf/xdp_bpfel.go | 6 +- pkg/bpf/xdp_bpfel.o | Bin 7368 -> 6288 bytes pkg/client/client.go | 18 ++- pkg/client/exporter.go | 2 +- pkg/client/meter.go | 249 ++++++++++++++++++++++++++++++-------- 12 files changed, 286 insertions(+), 102 deletions(-) diff --git a/cmd/fluvia/main.go b/cmd/fluvia/main.go index 1541761..f0851bd 100644 --- a/cmd/fluvia/main.go +++ b/cmd/fluvia/main.go @@ -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) } diff --git a/go.mod b/go.mod index c3d0b70..e390a5b 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,9 @@ go 1.20 require ( github.com/cilium/ebpf v0.11.0 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 diff --git a/go.sum b/go.sum index e5f5bf2..18d73c7 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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= diff --git a/internal/config/config.go b/internal/config/config.go index 69de831..de82e21 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -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 { diff --git a/pkg/bpf/bpf.go b/pkg/bpf/bpf.go index 4936e2a..fe5a2db 100644 --- a/pkg/bpf/bpf.go +++ b/pkg/bpf/bpf.go @@ -7,45 +7,78 @@ package bpf import ( - "fmt" + "errors" "net" "github.com/cilium/ebpf" - "github.com/pkg/errors" + "github.com/cilium/ebpf/link" + "github.com/cilium/ebpf/perf" ) //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -no-global-types -cc $BPF_CLANG -cflags $BPF_CFLAGS xdp ../../src/main.c -- -I../../src -type XdpProbeData struct { - H_dest [6]uint8 - H_source [6]uint8 - H_proto uint16 - _ [2]byte - V6Srcaddr struct{ In6U struct{ U6Addr8 [16]uint8 } } - V6Dstaddr struct{ In6U struct{ U6Addr8 [16]uint8 } } - NextHdr uint8 - HdrExtLen uint8 - RoutingType uint8 - SegmentsLeft uint8 - LastEntry uint8 - Flags uint8 - Tag uint16 - Segments [10]struct{ In6U struct{ U6Addr8 [16]uint8 } } +type XdpMetaData struct { + ReceivedNano uint64 + SentSec uint32 + SentSubsec uint32 } -func ReadXdpObjects(ops *ebpf.CollectionOptions) (*xdpObjects, error) { +type Xdp struct { + objs *xdpObjects + link link.Link +} + +func ReadXdpObjects(ops *ebpf.CollectionOptions) (*Xdp, error) { obj := &xdpObjects{} err := loadXdpObjects(obj, ops) if err != nil { - return nil, errors.WithStack(err) + return nil, err } // TODO: BPF log level remove hardcoding. yaml in config if err != nil { - return nil, errors.WithStack(err) + return nil, err + } + + return &Xdp{ + objs: obj, + }, nil +} + +func (x *Xdp) Attach(iface *net.Interface) error { + l, err := link.AttachXDP(link.XDPOptions{ + Program: x.objs.XdpProg, + Interface: iface.Index, + Flags: link.XDPGenericMode, + }) + if err != nil { + return err } - return obj, nil + x.link = l + + return nil +} + +func (x *Xdp) NewPerfReader() (*perf.Reader, error) { + return perf.NewReader(x.objs.PacketProbePerf, 4096) +} + +func (x *Xdp) Close() error { + errs := []error{} + if err := x.objs.Close(); err != nil { + errs = append(errs, err) + } + + if err := x.link.Close(); err != nil { + errs = append(errs, err) + } + + if len(errs) > 0 { + return errors.Join(errs...) + } + + return nil } const ( @@ -55,16 +88,3 @@ const ( XDP_TX XDP_REDIRECT ) - -func PrintEntrys(entry XdpProbeData, count uint64) { - mac := func(mac [6]uint8) string { - return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]) - } - saddr := net.IP(entry.V6Srcaddr.In6U.U6Addr8[:]).String() - daddr := net.IP(entry.V6Dstaddr.In6U.U6Addr8[:]).String() - - fmt.Printf( - "H_dest: %s, H_source: %v, H_proto: %v, V6Dstaddr: %v, V6Srcaddr: %v -> count: %v\n", - mac(entry.H_dest), mac(entry.H_source), entry.H_proto, daddr, saddr, count) - -} diff --git a/pkg/bpf/xdp_bpfeb.go b/pkg/bpf/xdp_bpfeb.go index 993d162..b6c41db 100644 --- a/pkg/bpf/xdp_bpfeb.go +++ b/pkg/bpf/xdp_bpfeb.go @@ -60,7 +60,7 @@ type xdpProgramSpecs struct { // // It can be passed ebpf.CollectionSpec.Assign. type xdpMapSpecs struct { - IpfixProbeMap *ebpf.MapSpec `ebpf:"ipfix_probe_map"` + PacketProbePerf *ebpf.MapSpec `ebpf:"packet_probe_perf"` } // xdpObjects contains all objects after they have been loaded into the kernel. @@ -82,12 +82,12 @@ func (o *xdpObjects) Close() error { // // It can be passed to loadXdpObjects or ebpf.CollectionSpec.LoadAndAssign. type xdpMaps struct { - IpfixProbeMap *ebpf.Map `ebpf:"ipfix_probe_map"` + PacketProbePerf *ebpf.Map `ebpf:"packet_probe_perf"` } func (m *xdpMaps) Close() error { return _XdpClose( - m.IpfixProbeMap, + m.PacketProbePerf, ) } diff --git a/pkg/bpf/xdp_bpfeb.o b/pkg/bpf/xdp_bpfeb.o index 027530192909721a863c2c4a86f0aca0718c3329..93101db9105c26eb1e4cbd49ace4078fda38f439 100644 GIT binary patch literal 6240 zcmbtYOKepR()DEK+B1sh z;dxk2(^5;MRS}iQ0;EWw);tzrffQEF0%^QpQ6v^#MhKAE6|wOGl?oM^?>~>3GsiJm zaO89U^ZoDtzvte2#&1tdUGja8n!}@iVQn#*5-bi1iQ@XYkGw`&t||SlrpvU4S0D8% zeCOxepYiG)1v~DnZEx|i(We+)O7Gc^6!c@yQNYWkKH4W~l=d=R5PYTX=@%p=qdzO? z2GP5Jar_M%IsDDC#Zr_GFg1MGB^ftH_)63@D9MPC$Ij3z%xOd=pSpykXPfdP^eyHr zjY~cqjH{kCzOHcNq@vod*R(rB(qd`MvD5m-UdP+hv^zrQ*uum!{Mk621$zkm2+J-0 zFyn^KuPe@o_8NKYGRcUrllQujl5*-wNc!nDMYl~IJ>OLF#!Q=DE5BZ*=u2$C+i)9$*F2W?lh1Uvm!1XL^R*R5*nee7L|<>a{`8XMQ$y|s z?4!7pZ#BNG_n*v!QfmlIPg`p1Xzw&|zH zTf-xZ+4+5z$pX*|EIp}(IzR=>oSClNFf!LSYuC@H5OWV6H)tc@T9jXnpRPbYs=s10D9mdW*Uyt{bYI zbqKYovI8Ho&N~)=KX^R$QNCECAh?iBUbqp=TzPdONZm+G1Oe4lwgw<-0pU)Sw;R;n&f~9&` z4+90mc4-jQPo1QCv6^2fhFQUBWoj!j20||If(A|u$iHeObE1&W7Y8z&zTiAqCt9sl z>KTO#S#cEh{Uk7!OL;)fQ=Au3h5Dn5rQ$+AbYi0{dD+qAVs_VNsj|>9xndarfzOLO zZz*9RJ5UKTVSYKx2E}x-R1GsF>Go!MKDbrO7s6lxJuOzF{mLLeFQUx^!&*)p6GQ!C zRIqD~sp?@Z2xFr;uDezeFPsP@Hl^78QRURfF&Eru(# zT(-h)uOt%5>C|*EIh~kJq-LJrFjp#<%Kt0VX6W+nagUb8c@B1UI7m%h4H6eFqU<~J zmbA4bUrHB-cXq{6RT{BTyPZ*5P8{UwTHCDF>%`79RgX_D+o8Fz1MHHsoMcg?;5}{dRh#7PZw*Y++y@-R6fn( zXn%7v@65elZ~3YThw^z7Kh1?vR~;~3ZiTmFqgA}C0^476t!lNDOwy`7tybvW{KRK~ zw`X#CHZ`H1f@&p)l+8;Ah#wiN9&z@u>mv?fY_w9U*YGAy*;{!gIi5_Z zZF;(2oH(H#9Gi>8W|EWg`N6FiJ}u@K(+gE_#}b!g3-}FJa%$-Fh<$5iOS0?uIj?cky6+>@x_EnmX7P5SU-l9w{U7=tx zp9zcAkOm6ra+M4+X9pp_NF>k2I4wnfhq+fcv%HhDS5+)o?#we(kD@9Fw|_*-H9k(T~l#&HMhe)u1dcOPdjFQ?^k5r?*xBU$tC7^?JVgHU9gyq#J43WiZbq&6M1pg!b<}OD)UeWg>CXrXExyNb&A{F;`X0wO z<3%*k@gKPk;aS`WB;57$xKDPgL2X8od)4^NqSe9-1^Lib*aPXjm;|@+Zc+SCP2d_H# zo`csM{LsPc4&HF^rh~WIvHbOD)i2*8HWrTlpo8NMPB?hZ!DR=pI{2Q0*Bt!N!Rrp* zaPX#sx7x9MjI4U)d%(uR(I0eh+`$P4&pEj4;8h3TbMTsjA3Av5!5a?VG`OdCKMmCI zyNK|MZgF5Nb!k8Xd6Hok|2{Q8L}_4gak&trv)KxM&OYInbL%98pO3ZlO&X})E=a1K z)7045U@)S{$st9DhLFwwKT>evy#I{T13%TPyN07F-%~SqZLs@Zj2k6~n)e^G`F!;h zd-*=LU36Q3&p-Gk$r0C7limkr0m}b#F?AaIUhr^UXvQ~kuy{o7D=N>6dMSyqXE23n z@^|e`{C(1@bus$P$zj^KrA3iP3Ur$@t&!RN%YwOE{o0-cbhv*0_V6(IbN&3ktX<+i z^G{YrAGQ>2UhVkobGP`9l|^J7l;aycJN~iK_LS_~Diqqs0n>k{uWk9h4jcV%yLM}$ Ro=-Gq9h7S{-*Xec@4ql@RW$$r literal 7368 zcmb_gYiL}@6+Tyc^+@xu+}f?2^l|N`W@Sn1m0}dNvdzlwDprxLwIm1IaeB4VUP)U^ zyX$=z$xf&m`YQrC9WD$N@#A2Ewi@ipAvpBw5MbWHU={ zS2|kl)YuqR_*6$1-D2rNCq1dKkDg$hit(G8hK?$vQ!)A-(f9viwC_gy->?PR?_fL? zr5k`Y!|pKbe#P~$>tTf5b%1?K$3YWsmLISl_>tg-;D>_m3%)0~F8HqCeZhAG?+Lyw zcvtX_;F{oDg0}^43a$wLT9D)A_?88i1g{FdA^20lMZwnvuLyoua6#}D!8yUAU`}vG za9S`WI3{>b@U-BlU_x+E@R;D&1!IB-1$zV!2tH)I{ASr?tnjZ&{Y%2XCS0vIZqe_EG0X2z9&XBf~)let`J&|x9Tji5MaRcgs(ep_b&BXI7FWNe6^&!sepO{h_lNO1X8Tn& zn?$!_s+}eN>bA1;``byR*73Z=BXL`oC7CaU|H4(Ai!#|K8~gI}t~E!sG=j4tpr z4lq}nNcU+;7dFu0Iu!^D+Klthd%NDZgY4k(eKfHAXd7-;vEe!01r6`1`*@zkqs|pz zo5(zayvKMSj^W{T#%pB(&kI^s5e!2{JQnMD^|5>fm=bvrm=pO8;Hx4p0e>L!b>NTi zARcQ4_+vbugS-a(g<625&Ox0)R6wuPfpKz9FeJ4OZ%7*+-%XKu{rp~X#_4Xx^G7sb z70Z7X1R%LOg{@ zZ0Fz!w*L$$>-h=lc}`fTz7w*JcEQ&+@%uF7oic7##qe+gZsVXX43uiC12TrrHWr^l zynPoS9|H2aU4wj7ZA7AHz*Rfx6X&os0;jzGU~BPO;PIhYE?z7q)sO>ou^To#PCSiW689c(m^Stzanh7N z4;>c=`o4hv%Sy}b)0rZ*9)oH+m5Gc`J&^QhoW&3c() zVbGf_(oC{YjAx4Zd77L_P8Fz_oT?Zgs8SD=GKKV1#!ER9)5$zk<6dVs*Wg{LDokhd zMU11{RsFM2qLOCtpapKArRTbo9|a}6D}Hn)O5F#ls+nwN%HbCkIr!P+oQvS|X|F)% zy?MHroGE#f&P}H0I3>u}Y%)i4shm5TB9%IRp^0YhtcPqO6M3&taMP3NOv;<1{G5BC zrTL#IaLRhNZ4A_y!sZT@1n zJ2HH9cvLUz1Fg>f{cvJvk%p1sV`?mgV!kv{bgHZ0!9=6}@~b){SbNn(BVk zu0&YDxzMh;zPejsq7z%-4O3$wo48_;sxJEtX{kiO()C&RL^FgM6a>mz!ekWDL)=uP$hdDrUf zbHZu8g*M%t92XAGO;)C#&6m)+OlIV9&48>1lLc}#Y`k&+C z#(~&~d06l7*0#1@-0$>9XeK@3WeOfeFzN#N3?Edv2jg3-3V-MEgzyaxMMa12xOD@v z|9y5K<36Qy^2@{eE%+Uxb9}>Rd*=Tn8sqOf+k)5O?vvwtp=0o{@EFl6d@VQ@;B21| zo+Wx6_fQ%OaIT*g{&S+MeE-S?IM*)<{{zu&_K$n7iibJZU)MZ@Ys?+^uZsz9C%TJ! zc%8%(0=|v=Slyb`nQsS|_(Ffj^_BB_Ti4sT2fYh!Hwj1X z-a~s^@?|qedmB2A*X}2JA33z00LMMl4hzSxu@Bi_G{A9>wR?pBglGfzH9HpIxHH=r zm-7D>(MRk*5#a3qwD5JJ2W&6nvza5`wv5kyi0d=fj-3;G=D73QbDI14wXbMizk@6r z`LnMJ?MC-!u5S!8dBT+Gc_I4>cS0j=`e_kNJFC z^LC09@k4?ld2c3iWVAn`i28=Jiho-fx0dP;ugJ{I#aZ?95OQZb^EtsTL5}hNQHJ5fIEQ?d Z7B=dDt8L3{$BO7U4$is1KK=e%{tM~-T!#Pv diff --git a/pkg/bpf/xdp_bpfel.go b/pkg/bpf/xdp_bpfel.go index 12b957d..ceb20b7 100644 --- a/pkg/bpf/xdp_bpfel.go +++ b/pkg/bpf/xdp_bpfel.go @@ -60,7 +60,7 @@ type xdpProgramSpecs struct { // // It can be passed ebpf.CollectionSpec.Assign. type xdpMapSpecs struct { - IpfixProbeMap *ebpf.MapSpec `ebpf:"ipfix_probe_map"` + PacketProbePerf *ebpf.MapSpec `ebpf:"packet_probe_perf"` } // xdpObjects contains all objects after they have been loaded into the kernel. @@ -82,12 +82,12 @@ func (o *xdpObjects) Close() error { // // It can be passed to loadXdpObjects or ebpf.CollectionSpec.LoadAndAssign. type xdpMaps struct { - IpfixProbeMap *ebpf.Map `ebpf:"ipfix_probe_map"` + PacketProbePerf *ebpf.Map `ebpf:"packet_probe_perf"` } func (m *xdpMaps) Close() error { return _XdpClose( - m.IpfixProbeMap, + m.PacketProbePerf, ) } diff --git a/pkg/bpf/xdp_bpfel.o b/pkg/bpf/xdp_bpfel.o index f9363568f4bbeb303be957f9a8a7813fdb5f1832..872c945eaa6535b37fc1391b359edcf1745ea870 100644 GIT binary patch literal 6288 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 Q9XU#NcwMQ-@0H{K0|&QPE&u=k literal 7368 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 RVNvcYknJd?x_=HY{teM&HADaa diff --git a/pkg/client/client.go b/pkg/client/client.go index ddde3f4..0175ad0 100755 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -7,11 +7,12 @@ package client import ( "net" + "time" - "github.com/nttcom/fluvia/pkg/packet/ipfix" + "github.com/nttcom/fluvia/pkg/ipfix" ) -func New(ingressIfName string, raddr *net.UDPAddr) ClientError { +func New(ingressIfName string, raddr *net.UDPAddr, interval int) ClientError { ch := make(chan []ipfix.FieldValue) errChan := make(chan ClientError) @@ -25,7 +26,18 @@ func New(ingressIfName string, raddr *net.UDPAddr) ClientError { } } }() - go NewMeter(ingressIfName, ch) + + m := NewMeter(ingressIfName) + go func() { + err := m.Run(ch, time.Duration(interval)) + if err != nil { + errChan <- ClientError{ + Component: "meter", + Error: err, + } + } + m.Close() + }() for { clientError := <-errChan diff --git a/pkg/client/exporter.go b/pkg/client/exporter.go index 69a41e4..fa2fdfb 100644 --- a/pkg/client/exporter.go +++ b/pkg/client/exporter.go @@ -10,7 +10,7 @@ import ( "net" "os" - "github.com/nttcom/fluvia/pkg/packet/ipfix" + "github.com/nttcom/fluvia/pkg/ipfix" ) const OBSERVATION_ID uint32 = 61166 diff --git a/pkg/client/meter.go b/pkg/client/meter.go index fe79628..6cbffce 100644 --- a/pkg/client/meter.go +++ b/pkg/client/meter.go @@ -7,91 +7,240 @@ package client import ( - "fmt" + "bytes" + "context" + "encoding/binary" + "errors" "log" "net" "net/netip" + "sync" "time" + "unsafe" - "github.com/cilium/ebpf/link" + "github.com/cilium/ebpf" "github.com/nttcom/fluvia/pkg/bpf" - "github.com/nttcom/fluvia/pkg/packet/ipfix" + "github.com/nttcom/fluvia/pkg/ipfix" + "github.com/nttcom/fluvia/pkg/packet" + "golang.org/x/sync/errgroup" + "golang.org/x/sys/unix" ) -func NewMeter(ingressIfName string, ch chan []ipfix.FieldValue) { +type Stats struct { + Count int64 + DelayMean int64 + DelayMin int64 + DelayMax int64 + DelaySum int64 +} + +type StatsMap struct { + Mu sync.RWMutex + Db map[packet.ProbeData]*Stats +} + +type Meter struct { + statsMap *StatsMap + bootTime time.Time + xdp *bpf.Xdp +} + +func NewMeter(ingressIfName string) *Meter { + bootTime, err := getSystemBootTime() + if err != nil { + log.Fatalf("Could not get boot time: %s", err) + } + + statsMap := StatsMap{Db: make(map[packet.ProbeData]*Stats)} + iface, err := net.InterfaceByName(ingressIfName) if err != nil { log.Fatalf("lookup network iface %q: %s", ingressIfName, err) } // Load the XDP program - objs, err := bpf.ReadXdpObjects(nil) + xdp, err := bpf.ReadXdpObjects(&ebpf.CollectionOptions{ + Programs: ebpf.ProgramOptions{ + LogLevel: ebpf.LogLevelInstruction, + LogSize: ebpf.DefaultVerifierLogSize * 256, + }, + }) if err != nil { - log.Fatalf("Could not load XDP program: %s", err) + var ve *ebpf.VerifierError + if errors.As(err, &ve) { + log.Fatalf("Could not load XDP program: %+v\n", ve) + } } - defer objs.Close() // Attach the XDP program. - l, err := link.AttachXDP(link.XDPOptions{ - Program: objs.XdpProg, - Interface: iface.Index, - Flags: link.XDPGenericMode, - }) - if err != nil { + if err = xdp.Attach(iface); err != nil { log.Fatalf("Could not attach XDP program: %s", err) } - defer l.Close() log.Printf("Attached XDP program to iface %q (index %d)", iface.Name, iface.Index) log.Printf("Press Ctrl-C to exit and remove the program") - ticker := time.NewTicker(1 * time.Second) - defer ticker.Stop() - mapLogs := map[bpf.XdpProbeData]uint64{} - for range ticker.C { - var entry bpf.XdpProbeData - var count uint64 + return &Meter{ + statsMap: &statsMap, + bootTime: bootTime, + xdp: xdp, + } +} + +func (m *Meter) Run(flowChan chan []ipfix.FieldValue, interval time.Duration) error { + eg, ctx := errgroup.WithContext(context.Background()) + eg.Go(func() error { + return m.Read(ctx) + }) + eg.Go(func() error { + return m.Send(ctx, flowChan, interval) + }) + + if err := eg.Wait(); err != nil { + return err + } + + return nil +} + +func (m *Meter) Read(ctx context.Context) error { + perfEvent, err := m.xdp.NewPerfReader() + if err != nil { + log.Fatalf("Could not obtain perf reader: %s", err) + } + + var metadata bpf.XdpMetaData + for { + select { + case <-ctx.Done(): + return nil + default: + eventData, err := perfEvent.Read() + if err != nil { + log.Fatalf("Could not read from bpf perf map:") + } - iter := objs.IpfixProbeMap.Iterate() + reader := bytes.NewReader(eventData.RawSample) - for iter.Next(&entry, &count) { - if _, ok := mapLogs[entry]; !ok { - mapLogs[entry] = 0 + if err := binary.Read(reader, binary.LittleEndian, &metadata); err != nil { + log.Fatalf("Could not read from reader: %s", err) } - dCnt := uint64(count - mapLogs[entry]) + metadata_size := unsafe.Sizeof(metadata) + if len(eventData.RawSample)-int(metadata_size) <= 0 { + continue + } - mapLogs[entry] = count + receivedNano := m.bootTime.Add(time.Duration(metadata.ReceivedNano) * time.Nanosecond) + SentNano := time.Unix(int64(metadata.SentSec), int64(metadata.SentSubsec)) - sl := []ipfix.SRHSegmentIPv6{} - for _, binSeg := range entry.Segments { - ipSeg, _ := netip.AddrFromSlice(binSeg.In6U.U6Addr8[:]) + delay := receivedNano.Sub(SentNano) - // Ignore zero values received from bpf map - if ipSeg == netip.IPv6Unspecified() { - break - } - seg := ipfix.SRHSegmentIPv6{Val: ipSeg} - sl = append(sl, seg) + probeData, err := packet.Parse(eventData.RawSample[metadata_size:]) + if err != nil { + log.Fatalf("Could not parse the packet: %s", err) } - actSeg, _ := netip.AddrFromSlice(entry.Segments[entry.SegmentsLeft].In6U.U6Addr8[:]) - - f := []ipfix.FieldValue{ - &ipfix.PacketDeltaCount{Val: dCnt}, - &ipfix.SRHActiveSegmentIPv6{Val: actSeg}, - &ipfix.SRHSegmentsIPv6Left{Val: entry.SegmentsLeft}, - &ipfix.SRHFlagsIPv6{Val: entry.Flags}, - &ipfix.SRHTagIPv6{Val: entry.Tag}, - &ipfix.SRHSegmentIPv6BasicList{ - SegmentList: sl, - }, + delayMicro := delay.Microseconds() + + m.statsMap.Mu.Lock() + if value, ok := m.statsMap.Db[*probeData]; !ok { + m.statsMap.Db[*probeData] = &Stats{ + Count: 1, + DelayMean: delayMicro, + DelayMin: delayMicro, + DelayMax: delayMicro, + DelaySum: delayMicro, + } + } else { + value.Count = value.Count + 1 + + if delayMicro < value.DelayMin { + value.DelayMin = delayMicro + } + + if delayMicro > value.DelayMax { + value.DelayMax = delayMicro + } + + value.DelaySum = value.DelaySum + delayMicro + value.DelayMean = value.DelaySum / value.Count } - // Throw to channel - ch <- f + m.statsMap.Mu.Unlock() } - if err := iter.Err(); err != nil { - fmt.Printf("Failed to iterate map: %v\n", err) + } +} + +func (m *Meter) Send(ctx context.Context, flowChan chan []ipfix.FieldValue, intervalSec time.Duration) error { + ticker := time.NewTicker(intervalSec * time.Second) + defer ticker.Stop() + + for range ticker.C { + select { + case <-ctx.Done(): + return nil + default: + m.statsMap.Mu.Lock() + for probeData, stat := range m.statsMap.Db { + dCnt := uint64(stat.Count) + + sl := []ipfix.SRHSegmentIPv6{} + for _, seg := range probeData.Segments { + if seg == "" { + break + } + ipSeg, _ := netip.ParseAddr(seg) + + // Ignore zero values received from bpf map + if ipSeg == netip.IPv6Unspecified() { + break + } + seg := ipfix.SRHSegmentIPv6{Val: ipSeg} + sl = append(sl, seg) + } + + actSeg, _ := netip.ParseAddr(probeData.Segments[probeData.SegmentsLeft]) + + f := []ipfix.FieldValue{ + &ipfix.PacketDeltaCount{Val: dCnt}, + &ipfix.SRHActiveSegmentIPv6{Val: actSeg}, + &ipfix.SRHSegmentsIPv6Left{Val: probeData.SegmentsLeft}, + &ipfix.SRHFlagsIPv6{Val: probeData.Flags}, + &ipfix.SRHTagIPv6{Val: probeData.Tag}, + &ipfix.SRHSegmentIPv6BasicList{ + SegmentList: sl, + }, + &ipfix.PathDelayMeanDeltaMicroseconds{Val: uint32(stat.DelayMean)}, + &ipfix.PathDelayMinDeltaMicroseconds{Val: uint32(stat.DelayMin)}, + &ipfix.PathDelayMaxDeltaMicroseconds{Val: uint32(stat.DelayMax)}, + &ipfix.PathDelaySumDeltaMicroseconds{Val: uint32(stat.DelaySum)}, + } + // Throw to channel + flowChan <- f + + // Stats (e.g., DelayMean) are based on packets received over a fixed duration + // These need to be cleared out for the next calculation of statistics + delete(m.statsMap.Db, probeData) + } + m.statsMap.Mu.Unlock() } } + + return nil +} + +func (m *Meter) Close() error { + if err := m.xdp.Close(); err != nil { + return err + } + + return nil +} + +func getSystemBootTime() (time.Time, error) { + var ts unix.Timespec + if err := unix.ClockGettime(unix.CLOCK_MONOTONIC, &ts); err != nil { + return time.Time{}, err + } + return time.Now().Add(-time.Duration(ts.Nano())), nil } From 42d7c0bc4f195272610a3471565dcea3f6348132 Mon Sep 17 00:00:00 2001 From: Yuya Tajima Date: Sat, 4 Nov 2023 13:38:05 +0000 Subject: [PATCH 05/12] Move modules related to parse packets to internal --- {pkg/packet => internal/pkg/meter}/parse.go | 9 ++++----- {pkg/bpf => internal/pkg/meter}/srv6.go | 2 +- pkg/client/meter.go | 8 ++++---- 3 files changed, 9 insertions(+), 10 deletions(-) rename {pkg/packet => internal/pkg/meter}/parse.go (89%) rename {pkg/bpf => internal/pkg/meter}/srv6.go (99%) diff --git a/pkg/packet/parse.go b/internal/pkg/meter/parse.go similarity index 89% rename from pkg/packet/parse.go rename to internal/pkg/meter/parse.go index a1e76b0..67584a3 100644 --- a/pkg/packet/parse.go +++ b/internal/pkg/meter/parse.go @@ -1,4 +1,4 @@ -package packet +package meter import ( "errors" @@ -6,7 +6,6 @@ import ( "github.com/google/gopacket" "github.com/google/gopacket/layers" - "github.com/nttcom/fluvia/pkg/bpf" ) const MAX_SEGMENTLIST_ENTRIES = 10 @@ -62,9 +61,9 @@ func Parse(data []byte) (*ProbeData, error) { return nil, errors.New(fmt.Sprintf("Next header is not SRv6: %d", hbh.NextHeader)) } - packet = gopacket.NewPacket(ipv6HBHLayer.LayerPayload(), bpf.Srv6LayerType, gopacket.Lazy) - srv6Layer := packet.Layer(bpf.Srv6LayerType) - srv6, ok := srv6Layer.(*bpf.Srv6Layer) + packet = gopacket.NewPacket(ipv6HBHLayer.LayerPayload(), Srv6LayerType, gopacket.Lazy) + srv6Layer := packet.Layer(Srv6LayerType) + srv6, ok := srv6Layer.(*Srv6Layer) if !ok { return nil, errors.New("Could not parse a packet with SRv6") } diff --git a/pkg/bpf/srv6.go b/internal/pkg/meter/srv6.go similarity index 99% rename from pkg/bpf/srv6.go rename to internal/pkg/meter/srv6.go index ce7496b..ddb4042 100644 --- a/pkg/bpf/srv6.go +++ b/internal/pkg/meter/srv6.go @@ -1,4 +1,4 @@ -package bpf +package meter import ( "encoding/binary" diff --git a/pkg/client/meter.go b/pkg/client/meter.go index 6cbffce..6103b58 100644 --- a/pkg/client/meter.go +++ b/pkg/client/meter.go @@ -19,9 +19,9 @@ import ( "unsafe" "github.com/cilium/ebpf" + "github.com/nttcom/fluvia/internal/pkg/meter" "github.com/nttcom/fluvia/pkg/bpf" "github.com/nttcom/fluvia/pkg/ipfix" - "github.com/nttcom/fluvia/pkg/packet" "golang.org/x/sync/errgroup" "golang.org/x/sys/unix" ) @@ -36,7 +36,7 @@ type Stats struct { type StatsMap struct { Mu sync.RWMutex - Db map[packet.ProbeData]*Stats + Db map[meter.ProbeData]*Stats } type Meter struct { @@ -51,7 +51,7 @@ func NewMeter(ingressIfName string) *Meter { log.Fatalf("Could not get boot time: %s", err) } - statsMap := StatsMap{Db: make(map[packet.ProbeData]*Stats)} + statsMap := StatsMap{Db: make(map[meter.ProbeData]*Stats)} iface, err := net.InterfaceByName(ingressIfName) if err != nil { @@ -136,7 +136,7 @@ func (m *Meter) Read(ctx context.Context) error { delay := receivedNano.Sub(SentNano) - probeData, err := packet.Parse(eventData.RawSample[metadata_size:]) + probeData, err := meter.Parse(eventData.RawSample[metadata_size:]) if err != nil { log.Fatalf("Could not parse the packet: %s", err) } From b2d0456486e26d08fa1f1c6a0d3c1f4a7cec6dbe Mon Sep 17 00:00:00 2001 From: Yuya Tajima Date: Sat, 4 Nov 2023 14:15:09 +0000 Subject: [PATCH 06/12] Add SPDX license identifiers to xdp codes --- src/main.c | 1 + src/xdp_consts.h | 1 + src/xdp_map.h | 1 + src/xdp_struct.h | 1 + 4 files changed, 4 insertions(+) diff --git a/src/main.c b/src/main.c index b65766e..80241c1 100644 --- a/src/main.c +++ b/src/main.c @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ /* * Copyright (c) 2023 NTT Communications Corporation * Copyright (c) 2023 Takeru Hayasaka diff --git a/src/xdp_consts.h b/src/xdp_consts.h index a1b52ec..c9eb237 100644 --- a/src/xdp_consts.h +++ b/src/xdp_consts.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ /* * Copyright (c) 2023 NTT Communications Corporation * Copyright (c) 2023 Takeru Hayasaka diff --git a/src/xdp_map.h b/src/xdp_map.h index 03d8435..b5bab3f 100644 --- a/src/xdp_map.h +++ b/src/xdp_map.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ /* * Copyright (c) 2023 NTT Communications Corporation * Copyright (c) 2023 Takeru Hayasaka diff --git a/src/xdp_struct.h b/src/xdp_struct.h index 5770b81..99fbbc3 100644 --- a/src/xdp_struct.h +++ b/src/xdp_struct.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ /* * Copyright (c) 2023 NTT Communications Corporation * Copyright (c) 2023 Takeru Hayasaka From dcb4c98e5bda38d9b2296a1641f2db6063ffae8a Mon Sep 17 00:00:00 2001 From: Yuya Tajima Date: Sat, 4 Nov 2023 14:35:19 +0000 Subject: [PATCH 07/12] Update README --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d5d6afa..9b3e845 100644 --- a/README.md +++ b/README.md @@ -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 From dbfc2bec8b37372c8bce20d17b2fe77bceed32e2 Mon Sep 17 00:00:00 2001 From: Yuya Tajima Date: Sun, 12 Nov 2023 16:41:19 +0000 Subject: [PATCH 08/12] Update xdp test --- internal/pkg/meter/hbh.go | 197 ++++++++++++++++++++++++++++++++++++++ pkg/bpf/xdp_test.go | 145 ++++++++++++++++++++++++---- 2 files changed, 326 insertions(+), 16 deletions(-) create mode 100644 internal/pkg/meter/hbh.go diff --git a/internal/pkg/meter/hbh.go b/internal/pkg/meter/hbh.go new file mode 100644 index 0000000..aa1637d --- /dev/null +++ b/internal/pkg/meter/hbh.go @@ -0,0 +1,197 @@ +package meter + +import ( + "encoding/binary" + "errors" + + "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 errors.New("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) +} diff --git a/pkg/bpf/xdp_test.go b/pkg/bpf/xdp_test.go index eca31c6..e986c43 100644 --- a/pkg/bpf/xdp_test.go +++ b/pkg/bpf/xdp_test.go @@ -1,15 +1,27 @@ package bpf import ( + "bytes" + "encoding/binary" "fmt" "net" + "net/netip" "testing" + "unsafe" + "github.com/cilium/ebpf/perf" "github.com/cilium/ebpf/rlimit" "github.com/google/gopacket" "github.com/google/gopacket/layers" + "github.com/nttcom/fluvia/internal/pkg/meter" ) +type testData struct { + sentSec uint32 + sentSubsec uint32 + probeData meter.ProbeData +} + func generateInput(t *testing.T) []byte { t.Helper() opts := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true} @@ -23,10 +35,13 @@ func generateInput(t *testing.T) []byte { dstPort := layers.UDPPort(54321) // Define the SRv6 segment list - segmentList := []net.IP{ - net.ParseIP("2001:db8:dead:beef::1"), - net.ParseIP("2001:db8:dead:beef::2"), - } + segmentList := []netip.Addr{} + + addr, _ := netip.ParseAddr("2001:db8:dead:beef::1") + segmentList = append(segmentList, addr) + + addr, _ = netip.ParseAddr("2001:db8:dead:beef::2") + segmentList = append(segmentList, addr) // Create the Ethernet layer ethernetLayer := &layers.Ethernet{ @@ -38,14 +53,56 @@ func generateInput(t *testing.T) []byte { // Create the IPv6 layer ipv6Layer := &layers.IPv6{ Version: 6, - NextHeader: layers.IPProtocolIPv6Routing, + NextHeader: layers.IPProtocolIPv6HopByHop, HopLimit: 64, SrcIP: srcIP, DstIP: dstIP, } + // Create the IPv6 Hop-By-Hop option layer + hbhLayer := &meter.HBHLayer{ + NextHeader: uint8(layers.IPProtocolIPv6Routing), + Length: 5, + Options: []meter.IoamOption{ + { + Type: meter.IPV6_TLV_PAD1, + }, + { + Type: meter.IPV6_TLV_PAD1, + }, + { + Type: 0x31, + Length: 0x2a, + Reserved: 0x00, + OptionType: 0x00, // Pre-allocated Trace + TraceHeader: meter.IoamTrace{ + NameSpaceId: 1, + NodeLen: 4, + Flags: 0b0000, + RemainingLen: 0b0000001, + Type: [3]byte{0xf0, 0x00, 0x00}, + Reserved: 0x00, + NodeDataList: []meter.NodeData{ + { + HopLimitNodeId: [4]byte{0x00, 0x00, 0x00, 0x00}, + IngressEgressIds: [4]byte{0x00, 0x00, 0x00, 0x00}, + Second: [4]byte{0x00, 0x00, 0x00, 0x00}, + Subsecond: [4]byte{0x00, 0x00, 0x00, 0x00}, + }, + { + HopLimitNodeId: [4]byte{0x40, 0x00, 0x00, 0x01}, + IngressEgressIds: [4]byte{0x00, 0x05, 0x00, 0x04}, + Second: [4]byte{0x65, 0x38, 0xd5, 0xf6}, + Subsecond: [4]byte{0x3b, 0x53, 0x3d, 0x00}, + }, + }, + }, + }, + }, + } + // Create the SRv6 extension header layer - seg6layer := &Srv6Layer{ + seg6layer := &meter.Srv6Layer{ NextHeader: uint8(layers.IPProtocolUDP), HdrExtLen: uint8((8+16*len(segmentList))/8 - 1), RoutingType: 4, // SRH @@ -65,7 +122,7 @@ func generateInput(t *testing.T) []byte { } err := gopacket.SerializeLayers(buf, opts, - ethernetLayer, ipv6Layer, seg6layer, udpLayer, + ethernetLayer, ipv6Layer, hbhLayer, seg6layer, udpLayer, gopacket.Payload([]byte("Hello, SRv6!")), ) if err != nil { @@ -85,6 +142,36 @@ func TestXDPProg(t *testing.T) { } defer objs.Close() + fmt.Println("debug log") + perfEvent, err := perf.NewReader(objs.PacketProbePerf, 4096) + if err != nil { + t.Fatal(err) + } + + var metadata XdpMetaData + + expected := testData{ + sentSec: 0x6538d5f6, + sentSubsec: 0x3b533d00, + probeData: meter.ProbeData{ + H_source: "02:42:ac:11:00:02", + H_dest: "02:42:ac:11:00:03", + V6Srcaddr: "2001:db8::1", + V6Dstaddr: "2001:db8::2", + NextHdr: uint8(layers.IPProtocolUDP), + HdrExtLen: uint8((8+16*2)/8 - 1), + RoutingType: 4, + SegmentsLeft: 2, + LastEntry: 1, + Flags: 0, + Tag: 0, + Segments: [10]string{ + "2001:db8:dead:beef::1", + "2001:db8:dead:beef::2", + }, + }, + } + ret, _, err := objs.XdpProg.Test(generateInput(t)) if err != nil { t.Error(err) @@ -95,14 +182,40 @@ func TestXDPProg(t *testing.T) { t.Errorf("got %d want %d", ret, 2) } - fmt.Println("debug log") - var entry XdpProbeData - var count uint64 - iter := objs.IpfixProbeMap.Iterate() - for iter.Next(&entry, &count) { - PrintEntrys(entry, count) - } - if err := iter.Err(); err != nil { - fmt.Printf("Failed to iterate map: %v\n", err) + fmt.Println("before read") + + eventData, err := perfEvent.Read() + if err != nil { + t.Fatal(err) + } + + fmt.Println("Done read") + + reader := bytes.NewReader(eventData.RawSample) + + if err := binary.Read(reader, binary.LittleEndian, &metadata); err != nil { + t.Fatal(err) + } + + metadataSize := unsafe.Sizeof(metadata) + if len(eventData.RawSample) <= int(metadataSize) { + t.Fatalf("XDP did not send raw packet") + } + + probeData, err := meter.Parse(eventData.RawSample[metadataSize:]) + if err != nil { + t.Fatal(err) + } + + actual := testData{ + sentSec: metadata.SentSec, + sentSubsec: metadata.SentSubsec, + probeData: *probeData, + } + + if actual != expected { + t.Errorf("TEST FAILED\n") + t.Errorf("expected value: %+v\n", expected) + t.Errorf("actual value: %+v\n", actual) } } From 3f8b09ebb0b716ffcbe914c0d9f3a23ff9eb512f Mon Sep 17 00:00:00 2001 From: Yuya Tajima Date: Sun, 12 Nov 2023 16:50:51 +0000 Subject: [PATCH 09/12] Fix to use fmt.Errorf instead of errors.New --- internal/pkg/meter/hbh.go | 329 +++++++++++++++++++----------------- internal/pkg/meter/parse.go | 13 +- internal/pkg/meter/srv6.go | 4 +- 3 files changed, 180 insertions(+), 166 deletions(-) diff --git a/internal/pkg/meter/hbh.go b/internal/pkg/meter/hbh.go index aa1637d..f31ab5b 100644 --- a/internal/pkg/meter/hbh.go +++ b/internal/pkg/meter/hbh.go @@ -2,7 +2,7 @@ package meter import ( "encoding/binary" - "errors" + "fmt" "github.com/google/gopacket" "github.com/google/gopacket/layers" @@ -11,187 +11,202 @@ import ( const IPV6_TLV_PAD1 = 0 type HBHLayer struct { - layers.BaseLayer - NextHeader uint8 - Length uint8 - Options []IoamOption + layers.BaseLayer + NextHeader uint8 + Length uint8 + Options []IoamOption } type IoamOption struct { - Type uint8 - Length uint8 - Reserved uint8 - OptionType uint8 - TraceHeader IoamTrace + 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 + 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 + 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), - }, + 2002, + gopacket.LayerTypeMetadata{ + Name: "HBHLayerType", + Decoder: gopacket.DecodeFunc(decodeHBHLayer), + }, ) func (l *HBHLayer) LayerType() gopacket.LayerType { - return HBHLayerType + 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 errors.New("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 + 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 + 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 + 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) + 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) } diff --git a/internal/pkg/meter/parse.go b/internal/pkg/meter/parse.go index 67584a3..1e9bcd3 100644 --- a/internal/pkg/meter/parse.go +++ b/internal/pkg/meter/parse.go @@ -1,7 +1,6 @@ package meter import ( - "errors" "fmt" "github.com/google/gopacket" @@ -32,7 +31,7 @@ func Parse(data []byte) (*ProbeData, error) { ethLayer := packet.Layer(layers.LayerTypeEthernet) eth, ok := ethLayer.(*layers.Ethernet) if !ok { - return nil, errors.New("Could not parse a packet with Ethernet") + return nil, fmt.Errorf("Could not parse a packet with Ethernet") } pd.H_dest = eth.DstMAC.String() @@ -41,31 +40,31 @@ func Parse(data []byte) (*ProbeData, error) { ipv6Layer := packet.Layer(layers.LayerTypeIPv6) ipv6, ok := ipv6Layer.(*layers.IPv6) if !ok { - return nil, errors.New("Could not parse a packet with IPv6") + 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, errors.New(fmt.Sprintf("Next header is not IPv6 hop-by-hop(0): %d", ipv6.NextHeader)) + 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, errors.New("Could not parse a packet with ipv6 hop-by-hop option") + return nil, fmt.Errorf("Could not parse a packet with ipv6 hop-by-hop option") } if hbh.NextHeader != layers.IPProtocolIPv6Routing { - return nil, errors.New(fmt.Sprintf("Next header is not SRv6: %d", hbh.NextHeader)) + 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, errors.New("Could not parse a packet with SRv6") + return nil, fmt.Errorf("Could not parse a packet with SRv6") } pd.NextHdr = srv6.NextHeader diff --git a/internal/pkg/meter/srv6.go b/internal/pkg/meter/srv6.go index ddb4042..6d59e97 100644 --- a/internal/pkg/meter/srv6.go +++ b/internal/pkg/meter/srv6.go @@ -2,7 +2,7 @@ package meter import ( "encoding/binary" - "errors" + "fmt" "net/netip" "github.com/google/gopacket" @@ -36,7 +36,7 @@ func (l *Srv6Layer) LayerType() gopacket.LayerType { func (i *Srv6Layer) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error { if len(data) < 8 { df.SetTruncated() - return errors.New("SRV6 layer less then 8 bytes for SRV6 packet") + return fmt.Errorf("SRV6 layer less then 8 bytes for SRV6 packet") } i.NextHeader = data[0] i.HdrExtLen = data[1] From 1b7cdaa8e28009360a5899425c4570495bfc7779 Mon Sep 17 00:00:00 2001 From: Yuya Tajima Date: Sun, 12 Nov 2023 17:00:05 +0000 Subject: [PATCH 10/12] Update README about the configuration of interval --- docs/sources/getting-started.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/sources/getting-started.md b/docs/sources/getting-started.md index 9af472f..551a566 100644 --- a/docs/sources/getting-started.md +++ b/docs/sources/getting-started.md @@ -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. From 88babb272c158a38149a6c81abda27380be32dbe Mon Sep 17 00:00:00 2001 From: Yuya Tajima Date: Sun, 12 Nov 2023 17:17:49 +0000 Subject: [PATCH 11/12] Use last entry to parse srv6 segment list --- internal/pkg/meter/srv6.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/meter/srv6.go b/internal/pkg/meter/srv6.go index 6d59e97..78472c0 100644 --- a/internal/pkg/meter/srv6.go +++ b/internal/pkg/meter/srv6.go @@ -46,7 +46,7 @@ func (i *Srv6Layer) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) err i.Flags = data[5] i.Tag = binary.BigEndian.Uint16(data[6:8]) - for j := 0; j < int(i.HdrExtLen/2); j++ { + for j := 0; j < int(i.LastEntry+1); j++ { startBit := 8 + 16*j endBit := 24 + 16*j var addr []byte From 6e1766e9335e804f1d6c41cd35f089ee33130112 Mon Sep 17 00:00:00 2001 From: Yuya Tajima Date: Mon, 13 Nov 2023 01:58:06 +0000 Subject: [PATCH 12/12] Fix required modules in tools --- tools/exporter/exporter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/exporter/exporter.go b/tools/exporter/exporter.go index 300aa26..48d9f22 100644 --- a/tools/exporter/exporter.go +++ b/tools/exporter/exporter.go @@ -11,7 +11,7 @@ import ( "net/netip" "github.com/nttcom/fluvia/pkg/client" - "github.com/nttcom/fluvia/pkg/packet/ipfix" + "github.com/nttcom/fluvia/pkg/ipfix" ) func main() {