agent/envoy_bootstrap/netinfo/network_info_collector.go (138 lines of code) (raw):
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file 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 netinfo
import (
"net"
"strconv"
"strings"
log "github.com/sirupsen/logrus"
)
const (
ipv4 = "ipv4"
ipv6 = "ipv6"
namespace = "aws.appmesh.task.interfaces"
)
// TODO: Since this interracts with real network interfaces we need to make
// an interface for this struct that we can mock it during UTs.
// InterfaceAddressMap holds a mapping of interface name to its addresses for a
// single IP version
// eg: "eth0" : [ "1.1.1.1", "2.2.2.2" ] is what's stored here
type interfaceAddressMap map[string][]string
// private function to add a new address to an interface
func addAddressToMap(addressMap interfaceAddressMap, interfaceName string, address string) {
if _, exists := addressMap[interfaceName]; !exists {
addressMap[interfaceName] = []string{address}
} else {
addressMap[interfaceName] = append(addressMap[interfaceName], address)
}
}
// AddressTypeMap holds a mapping of IP version to the interface and list of addresses
// corresponding to that IP Version
// eg: "ipv4" : { "eth0": [ "1.1.1.1", "2.2.2.2"] } }" is what this will look like
type addressTypeMap struct {
interfaceMapping map[string]interfaceAddressMap
}
func (addrTypeMap *addressTypeMap) addInterfaceAddress(
interfaceName string,
addrType string,
address string) {
// The TypeMap should have 2 keys. ipv4/ipv6.
// In each bucket, we will have the interface name and a list of addresses of that type.
if addressMap, exists := addrTypeMap.interfaceMapping[addrType]; !exists {
var addressMap interfaceAddressMap = make(interfaceAddressMap)
addAddressToMap(addressMap, interfaceName, address)
if addrTypeMap.interfaceMapping == nil {
addrTypeMap.interfaceMapping = make(map[string]interfaceAddressMap)
}
addrTypeMap.interfaceMapping[addrType] = addressMap
log.WithFields(log.Fields{
"addressMap": addressMap,
"address": address,
"intefaceName": interfaceName,
}).Debug("Adding new addressMap for interface")
} else {
addAddressToMap(addressMap, interfaceName, address)
log.WithFields(log.Fields{
"addressMap": addressMap,
"address": address,
"intefaceName": interfaceName,
}).Debug("Updating existing addressMap for interface")
}
}
// If there is a CIDR part of the address, validate the value
func isValidCidr(parts []string, cidrValue int) bool {
if len(parts) > 1 {
cidr, err := strconv.Atoi(parts[1])
if err != nil || cidr > cidrValue {
return false
}
}
return true
}
// IsIpv4 indicates whether the address spring is an IPv4 Address
func isIpv4(address string) bool {
parts := strings.Split(address, "/")
if !isValidCidr(parts, 32) {
return false
}
ip := net.ParseIP(parts[0])
return ip.To4() != nil
}
// IsIpv6 indicates whether the address string is an IPv6 address
func isIpv6(address string) bool {
parts := strings.Split(address, "/")
if !isValidCidr(parts, 128) {
return false
}
ip := net.ParseIP(parts[0])
return ip.To16() != nil
}
// Build a map of the interface addresses grouped by IP version and interface name
func getInterfaceAddressInfo(iface net.Interface, addressTypeMap *addressTypeMap) error {
addressData, err := iface.Addrs()
if err != nil {
log.WithFields(log.Fields{
"interface": iface.Name,
}).Error("Unable to obtain addresses for interface")
return err
}
log.WithFields(log.Fields{
"interface": iface.Name,
"addressData": addressData,
}).Debug("using addressData for interface")
for index := range addressData {
address := addressData[index].String()
var key string
if isIpv4(address) {
key = ipv4
} else if isIpv6(address) {
key = ipv6
} else {
continue
}
addressTypeMap.addInterfaceAddress(iface.Name, key, address)
}
return nil
}
// BuildMapWithInterfaceInfo generates a map containing the interfaces
// on the system grouped by IP Version then interface name
func BuildMapWithInterfaceInfo() (*map[string]interface{}, error) {
ifaces, err := net.Interfaces()
if err != nil {
log.Error("Unable to get network interfaces on host")
return nil, err
}
var addressMap addressTypeMap
for index := range ifaces {
iface := ifaces[index]
log.WithFields(log.Fields{
"interface": iface.Name,
}).Debug("Loading addresses for interface")
err := getInterfaceAddressInfo(iface, &addressMap)
if err != nil {
log.WithFields(log.Fields{
"interface": iface.Name,
}).Error("Unable to get address information for interface")
continue
}
}
log.WithFields(log.Fields{
"addressMap": addressMap,
}).Debug("Generated addressMap")
// TODO: Should either use the json encoding library to iteratively build a map of JSON values
// or iteratively construct the map[string]interface{}. These loops just convert
// the strongly typed interface mapping data into just maps and lists of interface{}
// which is what protobuf needs to make a struct.
mapping := make(map[string]interface{})
for ipver, iface := range addressMap.interfaceMapping {
mapping[ipver] = make(map[string]interface{})
for name, ips := range iface {
// convert the ip list to []interface{}
iplist := make([]interface{}, len(ips))
for i, ip := range ips {
iplist[i] = ip
}
mapping[ipver].(map[string]interface{})[name] = iplist
}
}
metadata := map[string]interface{}{
namespace: mapping,
}
return &metadata, nil
}