From 1b768fcead5e8eb5e464a87ae7bdb2ea0d724289 Mon Sep 17 00:00:00 2001 From: Denis Machard <5562930+dmachard@users.noreply.github.com> Date: Fri, 26 Apr 2024 21:14:30 +0200 Subject: [PATCH] fix panic on defrag (#691) * fix panic on defrag * add test for ipv6 fragment * decode properly ip fragment --- README.md | 4 +- collectors/file_ingestor.go | 4 +- collectors/sniffer_afpacket_linux.go | 14 ++- docs/collectors/collector_afpacket.md | 4 + netutils/bpf.go | 20 +-- netutils/ipdefrag.go | 13 +- netutils/ipdefrag_test.go | 119 +++++++++++++----- netutils/packetproccesor.go | 15 ++- netutils/packetprocessor_test.go | 4 +- pkgconfig/collectors.go | 2 + .../pcap/dnsdump_ip4_fragmented_query.pcap | Bin 0 -> 410 bytes .../pcap/dnsdump_ip6_fragmented_query.pcap | Bin 0 -> 277 bytes 12 files changed, 145 insertions(+), 54 deletions(-) create mode 100644 testsdata/pcap/dnsdump_ip4_fragmented_query.pcap create mode 100644 testsdata/pcap/dnsdump_ip6_fragmented_query.pcap diff --git a/README.md b/README.md index 7c7399fa..0b7985d1 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@
- + - +
diff --git a/collectors/file_ingestor.go b/collectors/file_ingestor.go
index f7f76044..5d48be8f 100644
--- a/collectors/file_ingestor.go
+++ b/collectors/file_ingestor.go
@@ -114,9 +114,9 @@ func (c *FileIngestor) ProcessPcap(filePath string) {
packetSource.NoCopy = true
// defrag ipv4
- go netutils.IPDefragger(fragIP4Chan, udpChan, tcpChan)
+ go netutils.IPDefragger(fragIP4Chan, udpChan, tcpChan, c.GetConfig().Collectors.FileIngestor.PcapDNSPort)
// defrag ipv6
- go netutils.IPDefragger(fragIP6Chan, udpChan, tcpChan)
+ go netutils.IPDefragger(fragIP6Chan, udpChan, tcpChan, c.GetConfig().Collectors.FileIngestor.PcapDNSPort)
// tcp assembly
go netutils.TCPAssembler(tcpChan, dnsChan, c.GetConfig().Collectors.FileIngestor.PcapDNSPort)
// udp processor
diff --git a/collectors/sniffer_afpacket_linux.go b/collectors/sniffer_afpacket_linux.go
index 3ba278a2..cd9bad85 100644
--- a/collectors/sniffer_afpacket_linux.go
+++ b/collectors/sniffer_afpacket_linux.go
@@ -102,9 +102,9 @@ func (c *AfpacketSniffer) Run() {
netDecoder := &netutils.NetDecoder{}
// defrag ipv4
- go netutils.IPDefragger(fragIP4Chan, udpChan, tcpChan)
+ go netutils.IPDefragger(fragIP4Chan, udpChan, tcpChan, c.GetConfig().Collectors.AfpacketLiveCapture.Port)
// defrag ipv6
- go netutils.IPDefragger(fragIP6Chan, udpChan, tcpChan)
+ go netutils.IPDefragger(fragIP6Chan, udpChan, tcpChan, c.GetConfig().Collectors.AfpacketLiveCapture.Port)
// tcp assembly
go netutils.TCPAssembler(tcpChan, dnsChan, 0)
// udp processor
@@ -198,18 +198,24 @@ func (c *AfpacketSniffer) Run() {
// ipv4 fragmented packet ?
if packet.NetworkLayer().LayerType() == layers.LayerTypeIPv4 {
+ if !c.GetConfig().Collectors.AfpacketLiveCapture.FragmentSupport {
+ continue
+ }
ip4 := packet.NetworkLayer().(*layers.IPv4)
if ip4.Flags&layers.IPv4MoreFragments == 1 || ip4.FragOffset > 0 {
- // fragIP4Chan <- packet
+ fragIP4Chan <- packet
continue
}
}
// ipv6 fragmented packet ?
if packet.NetworkLayer().LayerType() == layers.LayerTypeIPv6 {
+ if !c.GetConfig().Collectors.AfpacketLiveCapture.FragmentSupport {
+ continue
+ }
v6frag := packet.Layer(layers.LayerTypeIPv6Fragment)
if v6frag != nil {
- // fragIP6Chan <- packet
+ fragIP6Chan <- packet
continue
}
}
diff --git a/docs/collectors/collector_afpacket.md b/docs/collectors/collector_afpacket.md
index 99910a02..6d009041 100644
--- a/docs/collectors/collector_afpacket.md
+++ b/docs/collectors/collector_afpacket.md
@@ -21,6 +21,9 @@ Options:
* `device` (str)
> Interface name to sniff. If value is empty, bind on all interfaces.
+* `enable-fragment-support` (bool)
+ > Enable IP defrag support
+
* `chan-buffer-size` (int)
> Specifies the maximum number of packets that can be buffered before dropping additional packets.
@@ -31,5 +34,6 @@ Defaults:
afpacket-sniffer:
port: 53
device: wlp2s0
+ enable-fragment-support: true
chan-buffer-size: 65535
```
diff --git a/netutils/bpf.go b/netutils/bpf.go
index a132e275..c224c79d 100644
--- a/netutils/bpf.go
+++ b/netutils/bpf.go
@@ -23,34 +23,35 @@ func GetBpfFilter(port int) []bpf.Instruction {
// Load eth.type (2 bytes at offset 12) and push-it in register A
bpf.LoadAbsolute{Off: 12, Size: 2},
// if eth.type == IPv4 continue with the next instruction
- bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x0800, SkipTrue: 0, SkipFalse: 10},
+ bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x0800, SkipTrue: 0, SkipFalse: 11},
// Load ip.proto (1 byte at offset 23) and push-it in register A
bpf.LoadAbsolute{Off: 23, Size: 1},
// ip.proto == UDP ?
bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x11, SkipTrue: 1, SkipFalse: 0},
// ip.proto == TCP ?
- bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x6, SkipTrue: 0, SkipFalse: 16},
- // load flags and fragment offset (2 bytes at offset 20) to ignore fragmented packet
- bpf.LoadAbsolute{Off: 20, Size: 2},
+ bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x6, SkipTrue: 0, SkipFalse: 17},
+ // load flags and fragment offset (2 bytes at offset 20)
// Only look at the last 13 bits of the data saved in regiter A
// 0x1fff == 0001 1111 1111 1111 (fragment offset)
- // If any of the data in fragment offset is true, ignore the packet
+ bpf.LoadAbsolute{Off: 20, Size: 2},
bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: 0x1fff, SkipTrue: 14, SkipFalse: 0},
- // Load ip.length
// Register X = ip header len * 4
bpf.LoadMemShift{Off: 14},
// Load source port in tcp or udp (2 bytes at offset x+14)
bpf.LoadIndirect{Off: 14, Size: 2},
// source port equal to 53 ?
- bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 10, SkipFalse: 0},
+ bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 11, SkipFalse: 0},
// Load destination port in tcp or udp (2 bytes at offset x+16)
bpf.LoadIndirect{Off: 16, Size: 2},
// destination port equal to 53 ?
- bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 8, SkipFalse: 9},
+ bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 9, SkipFalse: 10},
+
// if eth.type == IPv6 continue with the next instruction
- bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x86dd, SkipTrue: 0, SkipFalse: 8},
+ bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x86dd, SkipTrue: 0, SkipFalse: 9},
// Load ipv6.nxt (2 bytes at offset 12) and push-it in register A
bpf.LoadAbsolute{Off: 20, Size: 1},
+ // fragment ?
+ bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x2c, SkipTrue: 6, SkipFalse: 0},
// ip.proto == UDP ?
bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x11, SkipTrue: 1, SkipFalse: 0},
// ip.proto == TCP ?
@@ -63,6 +64,7 @@ func GetBpfFilter(port int) []bpf.Instruction {
bpf.LoadAbsolute{Off: 56, Size: 2},
// destination port equal to 53 ?
bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 0, SkipFalse: 1},
+
// Keep the packet and send up to 65k of the packet to userspace
bpf.RetConstant{Val: 0xFFFF},
// Ignore packet
diff --git a/netutils/ipdefrag.go b/netutils/ipdefrag.go
index f1bd5e34..8080f369 100644
--- a/netutils/ipdefrag.go
+++ b/netutils/ipdefrag.go
@@ -56,11 +56,14 @@ func (f *fragments) insert(in gopacket.Packet) (gopacket.Packet, error) {
var fragOffset uint16
- frag6 := packet.Layer(layers.LayerTypeIPv6Fragment).(*layers.IPv6Fragment)
- ip4, _ := e.Value.(*layers.IPv4)
- if frag6 != nil {
- fragOffset = frag6.FragmentOffset * 8
- } else {
+ switch packet.NetworkLayer().LayerType() {
+ case layers.LayerTypeIPv6:
+ if frag6Layer := packet.Layer(layers.LayerTypeIPv6Fragment); frag6Layer != nil {
+ frag6 := frag6Layer.(*layers.IPv6Fragment)
+ fragOffset = frag6.FragmentOffset * 8
+ }
+ case layers.LayerTypeIPv4:
+ ip4, _ := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
fragOffset = ip4.FragOffset * 8
}
diff --git a/netutils/ipdefrag_test.go b/netutils/ipdefrag_test.go
index 665574c0..ea7b7e61 100644
--- a/netutils/ipdefrag_test.go
+++ b/netutils/ipdefrag_test.go
@@ -1,40 +1,101 @@
package netutils
-// TODO
-/*
-func createIPv6FragmentPacketWithNilLayer() gopacket.Packet {
- // IPv6 layer
- ipLayer := &layers.IPv6{
- Version: 6,
- NextHeader: layers.IPProtocolIPv6Fragment, // Next header is Fragmentation Header
- HopLimit: 64,
- SrcIP: net.ParseIP("2001:db8::1"),
- DstIP: net.ParseIP("2001:db8::2"),
- }
-
- // Create a packet with nil IPv6Fragment layer
- builder := gopacket.NewSerializeBuffer()
- ipLayer.SerializeTo(builder, gopacket.SerializeOptions{})
- // Set the IPv6 layer manually
- packet := gopacket.NewPacket(builder.Bytes(), layers.LayerTypeIPv6, gopacket.Default)
- // Remove IPv6Fragment layer
- packet.Layer(layers.LayerTypeIPv6Fragment).(*layers.IPv6Fragment).Payload = nil
-
- return packet
-}
+import (
+ "testing"
+
+ "github.com/google/gopacket"
+ "github.com/google/gopacket/layers"
+)
+func TestIpDefrag_IPv4Fragment(t *testing.T) {
+ // fragmented packet, see the packet in testsdata/pcap/dnsdump_ip4_fragmented_query.pcap
+ frag1Bytes := []byte{0x58, 0x1d, 0xd8, 0x12, 0x84, 0x10, 0xb0, 0x35, 0x9f, 0xd4, 0x03, 0x91, 0x08, 0x00,
+ 0x45, 0x00, 0x00, 0x4c, 0x00, 0x01, 0x20, 0x00, 0x40, 0x11, 0xcb, 0x8f, 0x7f, 0x00, 0x00, 0x01,
+ 0x08, 0x08, 0x08, 0x08, 0x30, 0x39, 0x00, 0x35, 0x00, 0x5d, 0xf9, 0x10, 0xaa, 0xaa, 0x01, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+ 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+ 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41}
+ frag1 := gopacket.NewPacket(frag1Bytes, layers.LayerTypeEthernet, gopacket.Default)
+ frag2Bytes := []byte{0x58, 0x1d, 0xd8, 0x12, 0x84, 0x10, 0xb0, 0x35, 0x9f, 0xd4, 0x03, 0x91, 0x08, 0x00,
+ 0x45, 0x00, 0x00, 0x39, 0x00, 0x01, 0x00, 0x07, 0x40, 0x11, 0xeb, 0x9b, 0x7f, 0x00, 0x00, 0x01,
+ 0x08, 0x08, 0x08, 0x08, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+ 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x03, 0x63, 0x6f, 0x6d,
+ 0x00, 0x00, 0x01, 0x00, 0x01}
+ frag2 := gopacket.NewPacket(frag2Bytes, layers.LayerTypeEthernet, gopacket.Default)
-func TestIpDefrag_WithNilIPv6Fragment(t *testing.T) {
defragger := NewIPDefragmenter()
+ _, err := defragger.DefragIP(frag1)
+ if err != nil {
+ t.Errorf("Unexpected error on the 1er defrag: %v", err)
+ }
- // Create an IPv6 packet with nil IPv6Fragment layer
- packet := createIPv6FragmentPacketWithNilLayer()
+ pkt, err := defragger.DefragIP(frag2)
+ if err != nil {
+ t.Errorf("Unexpected error on the 2nd defrag: %v", err)
+ }
+ if pkt.Metadata().Length != 113 {
+ t.Errorf("Invalid reassembled packet size: %v", err)
+ }
+}
- // This packet has a nil IPv6Fragment layer, which should trigger an error
+func TestIpDefrag_IPv4FragmentWithRetransmission(t *testing.T) {
+
+ // fragmented packet, see the packet in testsdata/pcap/dnsdump_ip4_fragmented_query.pcap
+ packetBytes := []byte{
+ 0x58, 0x1d, 0xd8, 0x12, 0x84, 0x10, 0xb0, 0x35, 0x9f, 0xd4, 0x03, 0x91, 0x08, 0x00, 0x45, 0x00,
+ 0x00, 0x39, 0x00, 0x01, 0x00, 0x07, 0x40, 0x11, 0xeb, 0x9b, 0x7f, 0x00, 0x00, 0x01, 0x08, 0x08,
+ 0x08, 0x08, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+ 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x03, 0x63,
+ 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
+ }
+ packet := gopacket.NewPacket(packetBytes, layers.LayerTypeEthernet, gopacket.Default)
+
+ // This packet is just a fragment
+ defragger := NewIPDefragmenter()
_, err := defragger.DefragIP(packet)
- if err == nil {
- t.Errorf("Expected error, got nil")
+ if err != nil {
+ t.Errorf("Unexpected error on the 1er defrag: %v", err)
+ }
+
+ // Try to defrag the same packet
+ _, err = defragger.DefragIP(packet)
+ if err != nil {
+ t.Errorf("Unexpected error for the 2nd defrag: %v", err)
+ }
+}
+
+func TestIpDefrag_IPv6Fragment(t *testing.T) {
+ // fragmented packet, see the packet in testsdata/pcap/dnsdump_ip4_fragmented_query.pcap
+ frag1Bytes := []byte{0x33, 0x33, 0x00, 0x00, 0x00, 0x01, 0xb0, 0x35, 0x9f, 0xd4, 0x03, 0x91, 0x86, 0xdd, 0x60, 0x00,
+ 0x00, 0x00, 0x00, 0x38, 0x2c, 0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5b, 0x20,
+ 0xbf, 0xa1, 0x87, 0x8c, 0x3a, 0x68, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x11, 0x00, 0x00, 0x01, 0xee, 0x8f, 0x5f, 0xf8, 0x30, 0x39,
+ 0x00, 0x35, 0x00, 0x61, 0x98, 0xc4, 0xaa, 0xaa, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x3f, 0x63, 0x6f, 0x6d, 0x41, 0x41,
+ 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41}
+ frag1 := gopacket.NewPacket(frag1Bytes, layers.LayerTypeEthernet, gopacket.Default)
+ frag2Bytes := []byte{0x33, 0x33, 0x00, 0x00, 0x00, 0x01, 0xb0, 0x35, 0x9f, 0xd4, 0x03, 0x91, 0x86, 0xdd, 0x60, 0x00,
+ 0x00, 0x00, 0x00, 0x39, 0x2c, 0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5b, 0x20,
+ 0xbf, 0xa1, 0x87, 0x8c, 0x3a, 0x68, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x11, 0x00, 0x00, 0x30, 0xee, 0x8f, 0x5f, 0xf8, 0x41, 0x41,
+ 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+ 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+ 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x01, 0x00, 0x01}
+ frag2 := gopacket.NewPacket(frag2Bytes, layers.LayerTypeEthernet, gopacket.Default)
+
+ defragger := NewIPDefragmenter()
+ _, err := defragger.DefragIP(frag1)
+ if err != nil {
+ t.Errorf("Unexpected error on the 1er defrag: %v", err)
+ }
+
+ pkt, err := defragger.DefragIP(frag2)
+ if err != nil {
+ t.Errorf("Unexpected error on the 2nd defrag: %v", err)
+ }
+
+ if pkt.Metadata().Length != 137 {
+ t.Errorf("Invalid reassembled packet size: %v", err)
}
}
-*/
diff --git a/netutils/packetproccesor.go b/netutils/packetproccesor.go
index a582bf2e..dd6b1129 100644
--- a/netutils/packetproccesor.go
+++ b/netutils/packetproccesor.go
@@ -86,7 +86,7 @@ FLUSHALL:
assembler.FlushAll()
}
-func IPDefragger(ipInput chan gopacket.Packet, udpOutput chan gopacket.Packet, tcpOutput chan gopacket.Packet) {
+func IPDefragger(ipInput chan gopacket.Packet, udpOutput chan gopacket.Packet, tcpOutput chan gopacket.Packet, port int) {
defragger := NewIPDefragmenter()
for fragment := range ipInput {
reassembled, err := defragger.DefragIP(fragment)
@@ -96,10 +96,23 @@ func IPDefragger(ipInput chan gopacket.Packet, udpOutput chan gopacket.Packet, t
if reassembled == nil {
continue
}
+
if reassembled.TransportLayer() != nil && reassembled.TransportLayer().LayerType() == layers.LayerTypeUDP {
+ // ignore packet regarding udp port
+ pkt := reassembled.TransportLayer().(*layers.UDP)
+ if pkt.DstPort != layers.UDPPort(port) && pkt.SrcPort != layers.UDPPort(port) {
+ continue
+ }
+ // valid reassembled packet
udpOutput <- reassembled
}
if reassembled.TransportLayer() != nil && reassembled.TransportLayer().LayerType() == layers.LayerTypeTCP {
+ // ignore packet regarding udp port
+ pkt := reassembled.TransportLayer().(*layers.TCP)
+ if pkt.DstPort != layers.TCPPort(port) && pkt.SrcPort != layers.TCPPort(port) {
+ continue
+ }
+ // valid reassembled packet
tcpOutput <- reassembled
}
}
diff --git a/netutils/packetprocessor_test.go b/netutils/packetprocessor_test.go
index e16445b5..628bbc7f 100644
--- a/netutils/packetprocessor_test.go
+++ b/netutils/packetprocessor_test.go
@@ -50,9 +50,9 @@ func Test_IpDefrag(t *testing.T) {
outputChan := make(chan gopacket.Packet, 2)
// defrag ipv4
- go IPDefragger(fragIP4Chan, outputChan, outputChan)
+ go IPDefragger(fragIP4Chan, outputChan, outputChan, 53)
// defrag ipv6
- go IPDefragger(fragIP6Chan, outputChan, outputChan)
+ go IPDefragger(fragIP6Chan, outputChan, outputChan, 53)
packetSource := gopacket.NewPacketSource(pcapHandler, pcapHandler.LinkType())
packetSource.DecodeOptions.Lazy = true
diff --git a/pkgconfig/collectors.go b/pkgconfig/collectors.go
index 13141c50..18b1a100 100644
--- a/pkgconfig/collectors.go
+++ b/pkgconfig/collectors.go
@@ -49,6 +49,7 @@ type ConfigCollectors struct {
Port int `yaml:"port"`
Device string `yaml:"device"`
ChannelBufferSize int `yaml:"chan-buffer-size"`
+ FragmentSupport bool `yaml:"enable-defrag-ip"`
} `yaml:"afpacket-sniffer"`
XdpLiveCapture struct {
Enable bool `yaml:"enable"`
@@ -127,6 +128,7 @@ func (c *ConfigCollectors) SetDefault() {
c.AfpacketLiveCapture.Port = 53
c.AfpacketLiveCapture.Device = ""
c.AfpacketLiveCapture.ChannelBufferSize = 65535
+ c.AfpacketLiveCapture.FragmentSupport = true
c.PowerDNS.Enable = false
c.PowerDNS.ListenIP = AnyIP
diff --git a/testsdata/pcap/dnsdump_ip4_fragmented_query.pcap b/testsdata/pcap/dnsdump_ip4_fragmented_query.pcap
new file mode 100644
index 0000000000000000000000000000000000000000..65aa7eda9a955fa46c2ca2a74b9a3bd732160d5d
GIT binary patch
literal 410
zcmca|c+)~A1{MYcU}0bcasroWq=mLHFhl{_ARHlkL#RbygX#P$%o8~nTp1XA7#I~8
z90X7I*E29MasYvWC4(tL>`#GJtAP4JMgW1mBVhotiM@k?!5yd`gfVQg1e(U~AozMV
h%qDzhGAHNff~;o@0vfCMfs+m%0NEt