Skip to content

Commit

Permalink
changed method for retrieving Windows network statistics
Browse files Browse the repository at this point in the history
Currently, we are running a Powershell method (Get-NetAdapterStatistics) to retrieve the network statistics in case of awsvpc network mode. However, this can cause CPU spikes on smaller instance types. Therefore, we are switching the method to use native Win32 API for retrieving the same.

As part of this change, we have added two new APIs to existing NetworkUtils interface applicable for Windows.
- ConvertInterfaceAliasToLUID: Returns the LUID corresponding to the given interface alias.
- GetMIBIfEntryFromLUID: Returns the MIB_IF_ROW for the specified LUID.

Windows API reference:
https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-convertinterfacealiastoluid
https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-getifentry2ex
  • Loading branch information
Harsh Rawat authored and singholt committed Oct 18, 2022
1 parent add9b0b commit 8680732
Show file tree
Hide file tree
Showing 6 changed files with 314 additions and 157 deletions.
33 changes: 32 additions & 1 deletion agent/eni/networkutils/mocks/utils_windows.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

70 changes: 70 additions & 0 deletions agent/eni/networkutils/types_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//go:build windows
// +build windows

// 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 networkutils

import "golang.org/x/sys/windows"

const (
ifMaxStringSize = 256
ifMaxPhysAddressLength = 32
)

// MibIfRow2 structure stores information about a particular interface.
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_if_row2
type MibIfRow2 struct {
InterfaceLUID uint64
interfaceIndex uint32
interfaceGUID windows.GUID
alias [ifMaxStringSize + 1]uint16
description [ifMaxStringSize + 1]uint16
physicalAddressLength uint32
physicalAddress [ifMaxPhysAddressLength]byte
permanentPhysicalAddress [ifMaxPhysAddressLength]byte
mtu uint32
ifType uint32
tunnelType uint32
mediaType uint32
physicalMediumType uint32
accessType uint32
directionType uint32
interfaceAndOperStatusFlags uint8
operStatus uint32
adminStatus uint32
mediaConnectState uint32
networkGUID windows.GUID
connectionType uint32
transmitLinkSpeed uint64
receiveLinkSpeed uint64
InOctets uint64
InUcastPkts uint64
InNUcastPkts uint64
InDiscards uint64
InErrors uint64
inUnknownProtos uint64
inUcastOctets uint64
inMulticastOctets uint64
inBroadcastOctets uint64
OutOctets uint64
OutUcastPkts uint64
OutNUcastPkts uint64
OutDiscards uint64
OutErrors uint64
outUcastOctets uint64
outMulticastOctets uint64
outBroadcastOctets uint64
outQLen uint64
}
55 changes: 54 additions & 1 deletion agent/eni/networkutils/utils_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,18 @@ import (
// NetworkUtils is the interface used for accessing network related functionality on Windows.
// The methods declared in this package may or may not add any additional logic over the actual networking api calls.
type NetworkUtils interface {
// GetInterfaceMACByIndex returns the MAC address of the device with given interface index.
// We will retry with the given timeout and context before erroring out.
GetInterfaceMACByIndex(int, context.Context, time.Duration) (string, error)
// GetAllNetworkInterfaces returns all the network interfaces in the host namespace.
GetAllNetworkInterfaces() ([]net.Interface, error)
// GetDNSServerAddressList returns the DNS Server list associated to the interface with
// the given MAC address.
GetDNSServerAddressList(macAddress string) ([]string, error)
// ConvertInterfaceAliasToLUID converts an interface alias to it's LUID.
ConvertInterfaceAliasToLUID(interfaceAlias string) (uint64, error)
// GetMIBIfEntryFromLUID returns the MIB_IF_ROW2 for the interface with the given LUID.
GetMIBIfEntryFromLUID(ifaceLUID uint64) (*MibIfRow2, error)
}

type networkUtils struct {
Expand All @@ -54,14 +63,25 @@ type networkUtils struct {
ctx context.Context
// A wrapper over Golang's net package
netWrapper netwrapper.NetWrapper
// funcConvertInterfaceAliasToLuid is the system call to ConvertInterfaceAliasToLuid Win32 API.
funcConvertInterfaceAliasToLuid func(a ...uintptr) (r1 uintptr, r2 uintptr, lastErr error)
// funcGetIfEntry2Ex is the system call to GetIfEntry2Ex Win32 API.
funcGetIfEntry2Ex func(a ...uintptr) (r1 uintptr, r2 uintptr, lastErr error)
}

var funcGetAdapterAddresses = getAdapterAddresses

// New creates a new network utils.
func New() NetworkUtils {
// We would be using GetIfTable2Ex, GetIfEntry2Ex, and FreeMibTable Win32 APIs from IP Helper.
moduleIPHelper := windows.NewLazySystemDLL("iphlpapi.dll")
procConvertInterfaceAliasToLuid := moduleIPHelper.NewProc("ConvertInterfaceAliasToLuid")
procGetIfEntry2Ex := moduleIPHelper.NewProc("GetIfEntry2Ex")

return &networkUtils{
netWrapper: netwrapper.New(),
netWrapper: netwrapper.New(),
funcConvertInterfaceAliasToLuid: procConvertInterfaceAliasToLuid.Call,
funcGetIfEntry2Ex: procGetIfEntry2Ex.Call,
}
}

Expand Down Expand Up @@ -144,6 +164,39 @@ func (utils *networkUtils) GetDNSServerAddressList(macAddress string) ([]string,
return dnsServerAddressList, nil
}

// ConvertInterfaceAliasToLUID returns the LUID of the interface with given interface alias.
// Internally, it would invoke ConvertInterfaceAliasToLuid Win32 API to perform the conversion.
func (utils *networkUtils) ConvertInterfaceAliasToLUID(interfaceAlias string) (uint64, error) {
var luid uint64
alias := windows.StringToUTF16Ptr(interfaceAlias)

// ConvertInterfaceAliasToLuid function converts alias into LUID.
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-convertinterfacealiastoluid
retVal, _, _ := utils.funcConvertInterfaceAliasToLuid(uintptr(unsafe.Pointer(alias)), uintptr(unsafe.Pointer(&luid)))
if retVal != 0 {
return 0, errors.Errorf("error occured while calling ConvertInterfaceAliasToLuid: %s", syscall.Errno(retVal))
}

return luid, nil
}

// GetMIBIfEntryFromLUID returns the MIB_IF_ROW2 object for the interface with given LUID.
// Internally, this would invoke GetIfEntry2Ex Win32 API to retrieve the specific row.
func (utils *networkUtils) GetMIBIfEntryFromLUID(ifaceLUID uint64) (*MibIfRow2, error) {
row := &MibIfRow2{
InterfaceLUID: ifaceLUID,
}

// GetIfEntry2Ex function retrieves the MIB-II interface for the given interface index..
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-getifentry2ex
retVal, _, _ := utils.funcGetIfEntry2Ex(uintptr(0), uintptr(unsafe.Pointer(row)))
if retVal != 0 {
return nil, errors.Errorf("error occured while calling GetIfEntry2Ex: %s", syscall.Errno(retVal))
}

return row, nil
}

// parseMACAddress parses the physical address of windows.IpAdapterAddresses into net.HardwareAddr.
func (utils *networkUtils) parseMACAddress(adapterAddress *windows.IpAdapterAddresses) net.HardwareAddr {
hardwareAddr := make(net.HardwareAddr, adapterAddress.PhysicalAddressLength)
Expand Down
84 changes: 81 additions & 3 deletions agent/eni/networkutils/utils_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"net"
"syscall"
"testing"
"unsafe"

"golang.org/x/sys/windows"

Expand All @@ -32,9 +33,13 @@ import (
)

const (
interfaceIndex = 9
macAddress = "02:22:ea:8c:81:dc"
validDnsServer = "10.0.0.2"
deviceName = "Ethernet 2"
ifaceLUID uint64 = 1689399649632256
RxBytes uint64 = 1000
TxBytes uint64 = 5000
interfaceIndex = 9
macAddress = "02:22:ea:8c:81:dc"
validDnsServer = "10.0.0.2"
)

// This is a success test. We receive the appropriate MAC address corresponding to the interface index.
Expand Down Expand Up @@ -230,3 +235,76 @@ func TestGetDNSServerAddressList(t *testing.T) {
assert.Len(t, dnsServerList, 1)
assert.EqualValues(t, dnsServerList[0], validDnsServer)
}

// TestGetMIBIfEntryFromLUID tests the GetMIBIfEntryFromLUID method.
func TestGetMIBIfEntryFromLUID(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()

netUtils := &networkUtils{}

// Dummy function which is representative of system call (GetIfEntry2Ex)
netUtils.funcGetIfEntry2Ex = func(a ...uintptr) (uintptr, uintptr, error) {
row := (*MibIfRow2)(unsafe.Pointer(a[1]))
row.interfaceIndex = interfaceIndex
row.OutOctets = TxBytes
row.InOctets = RxBytes
return uintptr(0), uintptr(0), nil
}

ifRow, err := netUtils.GetMIBIfEntryFromLUID(ifaceLUID)
assert.NoError(t, err)
assert.Equal(t, uint32(interfaceIndex), ifRow.interfaceIndex)
assert.Equal(t, TxBytes, ifRow.OutOctets)
assert.Equal(t, RxBytes, ifRow.InOctets)
}

// TestGetMIBIfEntryFromLUIDError tests the GetMIBIfEntryFromLUID method in error case.
func TestGetMIBIfEntryFromLUIDError(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()

netUtils := &networkUtils{}

// Dummy function which is representative of system call (GetIfEntry2Ex)
netUtils.funcGetIfEntry2Ex = func(a ...uintptr) (uintptr, uintptr, error) {
// Return an error code.
return uintptr(1), uintptr(0), nil
}

_, err := netUtils.GetMIBIfEntryFromLUID(ifaceLUID)
assert.Error(t, err)
}

// TestConvertInterfaceAliasToLUID tests ConvertInterfaceAliasToLUID method.
func TestConvertInterfaceAliasToLUID(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()

netUtils := &networkUtils{}
// Dummy function which is representative of system call (GetIfEntry2Ex)
netUtils.funcConvertInterfaceAliasToLuid = func(a ...uintptr) (uintptr, uintptr, error) {
luid := (*uint64)(unsafe.Pointer(a[1]))
*luid = ifaceLUID
return uintptr(0), uintptr(0), nil
}

luid, err := netUtils.ConvertInterfaceAliasToLUID(deviceName)
assert.NoError(t, err)
assert.Equal(t, ifaceLUID, luid)
}

// TestConvertInterfaceAliasToLUID tests ConvertInterfaceAliasToLUID method in case of error.
func TestConvertInterfaceAliasToLUIDError(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()

netUtils := &networkUtils{}
// Dummy function which is representative of system call (GetIfEntry2Ex)
netUtils.funcConvertInterfaceAliasToLuid = func(a ...uintptr) (uintptr, uintptr, error) {
return uintptr(1), uintptr(0), nil
}

_, err := netUtils.ConvertInterfaceAliasToLUID(deviceName)
assert.Error(t, err)
}
Loading

0 comments on commit 8680732

Please sign in to comment.