You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by sr...@apache.org on 2022/08/12 13:44:39 UTC
[plc4x] 01/05: feat(plc4go): added net util for finding IPs
This is an automated email from the ASF dual-hosted git repository.
sruehl pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/plc4x.git
commit 1a041e895908cede4e35ef73ae4bb68f402856bd
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Fri Aug 12 14:30:38 2022 +0200
feat(plc4go): added net util for finding IPs
---
plc4go/spi/utils/net.go | 247 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 247 insertions(+)
diff --git a/plc4go/spi/utils/net.go b/plc4go/spi/utils/net.go
new file mode 100644
index 000000000..91fad7729
--- /dev/null
+++ b/plc4go/spi/utils/net.go
@@ -0,0 +1,247 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package utils
+
+import (
+ "bytes"
+ "context"
+ "net"
+ "time"
+
+ "github.com/google/gopacket"
+ "github.com/google/gopacket/layers"
+ "github.com/google/gopacket/pcap"
+ "github.com/pkg/errors"
+ "github.com/rs/zerolog/log"
+)
+
+func GetIPAddresses(ctx context.Context, netInterface net.Interface, useArpBasedScan bool) (chan net.IP, error) {
+ foundIps := make(chan net.IP, 65536)
+ addrs, err := netInterface.Addrs()
+ if err != nil {
+ return nil, errors.Wrap(err, "Error getting addresses")
+ }
+ go func() {
+ for _, address := range addrs {
+ // Check if context has been cancelled before continuing
+ select {
+ case <-ctx.Done():
+ return
+ default:
+ }
+
+ var ipnet *net.IPNet
+ switch v := address.(type) {
+ case *net.IPAddr:
+ ipnet = &net.IPNet{IP: v.IP, Mask: v.IP.DefaultMask()}
+ case *net.IPNet:
+ ipnet = v
+ default:
+ continue
+ }
+
+ // Skip loop-back and IPv6
+ if ipnet.IP.IsLoopback() || ipnet.IP.To4() == nil {
+ continue
+ }
+
+ log.Debug().Stringer("IP", ipnet.IP).Stringer("Mask", ipnet.Mask).Msg("Expanding local subnet")
+ if useArpBasedScan {
+ if err := lockupIpsUsingArp(ctx, netInterface, ipnet, foundIps); err != nil {
+ log.Error().Err(err).Msg("failing to resolve using arp scan. Falling back to ip based scan")
+ useArpBasedScan = false
+ }
+ }
+ if !useArpBasedScan {
+ if err := lookupIps(ctx, ipnet, foundIps); err != nil {
+ log.Error().Err(err).Msg("error looking up ips")
+ }
+ }
+ }
+ }()
+ return foundIps, nil
+}
+
+// As PING operations might be blocked by a firewall, responding to ARP packets is mandatory for IP based
+// systems. So we are using an ARP scan to resolve the ethernet hardware addresses of each possible ip in range
+// Only for devices that respond will we schedule a discovery.
+func lockupIpsUsingArp(ctx context.Context, netInterface net.Interface, ipNet *net.IPNet, foundIps chan net.IP) error {
+ log.Debug().Msgf("Scanning for alive IP addresses for interface '%s' and net: %s", netInterface.Name, ipNet)
+ // First find the pcap device name for the given interface.
+ allDevs, _ := pcap.FindAllDevs()
+ var devName string
+ for _, dev := range allDevs {
+ for _, devAddress := range dev.Addresses {
+ if devAddress.IP.Equal(ipNet.IP) {
+ devName = dev.Name
+ break
+ }
+ }
+ }
+ if len(devName) == 0 {
+ log.Error().Interface("allDevs", allDevs).Str("ip", ipNet.IP.String()).Msg("Device for discovery not found")
+ return errors.New("Device for discovery not found")
+ }
+
+ // Open up a pcap handle for packet reads/writes.
+ handle, err := pcap.OpenLive(devName, 65536, true, pcap.BlockForever)
+ if err != nil {
+ return errors.Wrap(err, "Error opening network interface")
+ }
+
+ // Start up a goroutine to read in packet data.
+ stop := make(chan struct{})
+ // Handler for processing incoming ARP responses.
+ go func(handle *pcap.Handle, iface net.Interface, stop chan struct{}) {
+ src := gopacket.NewPacketSource(handle, layers.LayerTypeEthernet)
+ in := src.Packets()
+ for {
+ var packet gopacket.Packet
+ select {
+ case <-stop:
+ return
+ case packet = <-in:
+ if packet == nil {
+ continue
+ }
+ arpLayer := packet.Layer(layers.LayerTypeARP)
+ if arpLayer == nil {
+ continue
+ }
+ arp := arpLayer.(*layers.ARP)
+ // Filter our messages originating from us.
+ if arp.Operation != layers.ARPReply || bytes.Equal(iface.HardwareAddr, arp.SourceHwAddress) {
+ continue
+ }
+ // Schedule a discovery operation for this ip.
+ ip := net.IP(arp.SourceProtAddress)
+ log.Trace().Msgf("Scheduling discovery for IP %s", ip)
+ go func() {
+ select {
+ case <-ctx.Done():
+ case foundIps <- ip:
+ case <-time.After(2 * time.Second):
+ }
+ }()
+ }
+ }
+ }(handle, netInterface, stop)
+ // Make sure we clean up after 10 seconds.
+ defer func() {
+ go func() {
+ time.Sleep(10 * time.Second)
+ handle.Close()
+ close(stop)
+ }()
+ }()
+ writeArp := func(handle *pcap.Handle, iface net.Interface, addr net.IPNet) error {
+ // Set up all the layers' fields we can.
+ eth := layers.Ethernet{
+ SrcMAC: iface.HardwareAddr,
+ DstMAC: net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+ EthernetType: layers.EthernetTypeARP,
+ }
+ arp := layers.ARP{
+ AddrType: layers.LinkTypeEthernet,
+ Protocol: layers.EthernetTypeIPv4,
+ HwAddressSize: 6,
+ ProtAddressSize: 4,
+ Operation: layers.ARPRequest,
+ SourceHwAddress: []byte(iface.HardwareAddr),
+ SourceProtAddress: []byte(addr.IP.To4()),
+ DstHwAddress: []byte{0, 0, 0, 0, 0, 0},
+ }
+ // Set up buffer and options for serialization.
+ buf := gopacket.NewSerializeBuffer()
+ opts := gopacket.SerializeOptions{
+ FixLengths: true,
+ ComputeChecksums: true,
+ }
+ log.Debug().Msgf("Sending ARP requests to all devices in network: %s", addr.String())
+ // Send one ARP packet for every possible address.
+ for ip := incrementIP(addr.IP.Mask(ipNet.Mask)); addr.Contains(ip) && addr.Contains(incrementIP(duplicateIP(ip))); ip = incrementIP(ip) {
+ // Check if context has been cancelled before continuing
+ select {
+ case <-ctx.Done():
+ return nil
+ default:
+ }
+ arp.DstProtAddress = ip
+ if err := gopacket.SerializeLayers(buf, opts, ð, &arp); err != nil {
+ return err
+ }
+ if err := handle.WritePacketData(buf.Bytes()); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+ // Write our scan packets out to the handle.
+ if err := writeArp(handle, netInterface, *ipNet); err != nil {
+ log.Printf("error writing packets on %v: %v", netInterface.Name, err)
+ return err
+ }
+ return nil
+}
+
+// Simply takes the IP address and the netmask and schedules one discovery task for every possible IP
+func lookupIps(ctx context.Context, ipnet *net.IPNet, foundIps chan net.IP) error {
+ log.Debug().Msgf("Scanning all IP addresses for network: %s", ipnet)
+ // expand CIDR-block into one target for each IP
+ // Remark: The last IP address a network contains is a special broadcast address. We don't want to check that one.
+ for ip := incrementIP(ipnet.IP.Mask(ipnet.Mask)); ipnet.Contains(ip) && ipnet.Contains(incrementIP(duplicateIP(ip))); ip = incrementIP(ip) {
+ // Check if context has been cancelled before continuing
+ select {
+ case <-ctx.Done():
+ return nil
+ default:
+ }
+
+ go func() {
+ select {
+ case <-ctx.Done():
+ case foundIps <- ip:
+ case <-time.After(2 * time.Second):
+ }
+ }()
+ log.Trace().Stringer("IP", ip).Msg("Expanded CIDR")
+ }
+
+ log.Debug().Stringer("net", ipnet).Msg("Done expanding CIDR")
+
+ return nil
+}
+
+func incrementIP(ip net.IP) net.IP {
+ for j := len(ip) - 1; j >= 0; j-- {
+ ip[j]++
+ if ip[j] > 0 {
+ break
+ }
+ }
+
+ return ip
+}
+
+func duplicateIP(ip net.IP) net.IP {
+ dup := make(net.IP, len(ip))
+ copy(dup, ip)
+ return dup
+}