-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[pkg/ottl] Add "NetworkDirection" converter
- Loading branch information
1 parent
9a52558
commit ad18e1a
Showing
5 changed files
with
297 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
// Copyright The OpenTelemetry Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs" | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net" | ||
|
||
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" | ||
) | ||
|
||
const ( | ||
// direction | ||
DIRECTION_INTERNAL = "internal" | ||
DIRECTION_EXTERNAL = "external" | ||
DIRECTION_INBOUND = "inbound" | ||
DIRECTION_OUTBOUND = "outbound" | ||
|
||
// netwroks | ||
LOOPBACK_NAMED_NETWORK = "loopback" | ||
GLOBAL_UNICAST_NAMED_NETWORK = "global_unicast" | ||
UNICAST_NAMED_NETWORK = "unicast" | ||
LINK_LOCAL_UNICAST_NAMED_NETWORK = "link_local_unicast" | ||
INTERFACE_LOCAL_NAMED_NETWORK = "interface_local_multicast" | ||
LINK_LOCAL_MULTICAST_NAMED_NETWORK = "link_local_multicast" | ||
MULTICAST_NAMED_NETWORK = "multicast" | ||
UNSPECIFIED_NAMED_NETWORK = "unspecified" | ||
PRIVATE_NAMED_NETWORK = "private" | ||
PUBLIC_NAMED_NETWORK = "public" | ||
) | ||
|
||
type NetworkDirectionArguments[K any] struct { | ||
SourceIP ottl.StringGetter[K] | ||
DestinationIP ottl.StringGetter[K] | ||
InternalNetworks ottl.Optional[[]string] | ||
} | ||
|
||
func NewNetworkDirectionFactory[K any]() ottl.Factory[K] { | ||
return ottl.NewFactory("NetworkDirection", &NetworkDirectionArguments[K]{}, createNetworkDirectionFunction[K]) | ||
} | ||
|
||
func createNetworkDirectionFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { | ||
args, ok := oArgs.(*NetworkDirectionArguments[K]) | ||
if !ok { | ||
return nil, fmt.Errorf("URLFactory args must be of type *NetworkDirectionArguments[K]") | ||
} | ||
|
||
return networkDirection(args.SourceIP, args.DestinationIP, args.InternalNetworks), nil //revive:disable-line:var-naming | ||
} | ||
|
||
func networkDirection[K any](sourceIP ottl.StringGetter[K], destinationIP ottl.StringGetter[K], internalNetworksOptional ottl.Optional[[]string]) ottl.ExprFunc[K] { //revive:disable-line:var-naming | ||
var internalNetworks []string | ||
if !internalNetworksOptional.IsEmpty() { | ||
for _, network := range internalNetworksOptional.Get() { | ||
if len(network) > 0 { | ||
internalNetworks = append(internalNetworks, network) | ||
} | ||
} | ||
} | ||
|
||
return func(ctx context.Context, tCtx K) (any, error) { | ||
sourceIPString, err := sourceIP.Get(ctx, tCtx) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed parsing source IP: %w", err) | ||
} | ||
|
||
if sourceIPString == "" { | ||
return nil, fmt.Errorf("source IP cannot be empty") | ||
} | ||
|
||
destinationIPString, err := destinationIP.Get(ctx, tCtx) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed parsing destination IP: %w", err) | ||
} | ||
|
||
if destinationIPString == "" { | ||
return nil, fmt.Errorf("destination IP cannot be empty") | ||
} | ||
|
||
sourceAddress := net.ParseIP(sourceIPString) | ||
if sourceAddress == nil { | ||
return nil, fmt.Errorf("source IP %q is not a valid address", sourceIPString) | ||
} | ||
|
||
sourceInternal, err := isInternalIP(sourceAddress, internalNetworks) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed determining whether source IP is internal: %w", err) | ||
} | ||
|
||
destinationAddress := net.ParseIP(destinationIPString) | ||
if destinationAddress == nil { | ||
return nil, fmt.Errorf("destination IP %q is not a valid address", destinationIPString) | ||
} | ||
|
||
destinationInternal, err := isInternalIP(destinationAddress, internalNetworks) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed determining whether destination IP is internal: %w", err) | ||
} | ||
|
||
if sourceInternal && destinationInternal { | ||
return DIRECTION_INTERNAL, nil | ||
} | ||
if sourceInternal { | ||
return DIRECTION_OUTBOUND, nil | ||
} | ||
if destinationInternal { | ||
return DIRECTION_INBOUND, nil | ||
} | ||
return DIRECTION_EXTERNAL, nil | ||
} | ||
} | ||
|
||
func isInternalIP(addr net.IP, networks []string) (bool, error) { | ||
for _, network := range networks { | ||
inNetwork, err := isIPInNetwork(addr, network) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
if inNetwork { | ||
return true, nil | ||
} | ||
} | ||
return false, nil | ||
} | ||
|
||
func isIPInNetwork(addr net.IP, network string) (bool, error) { | ||
switch network { | ||
case LOOPBACK_NAMED_NETWORK: | ||
return addr.IsLoopback(), nil | ||
case GLOBAL_UNICAST_NAMED_NETWORK: | ||
return addr.IsGlobalUnicast(), nil | ||
case LINK_LOCAL_UNICAST_NAMED_NETWORK: | ||
return addr.IsLinkLocalUnicast(), nil | ||
case LINK_LOCAL_MULTICAST_NAMED_NETWORK: | ||
return addr.IsLinkLocalMulticast(), nil | ||
case INTERFACE_LOCAL_NAMED_NETWORK: | ||
return addr.IsInterfaceLocalMulticast(), nil | ||
case MULTICAST_NAMED_NETWORK: | ||
return addr.IsMulticast(), nil | ||
case PRIVATE_NAMED_NETWORK: | ||
return isPrivateNetwork(addr), nil | ||
case PUBLIC_NAMED_NETWORK: | ||
return isPublicNetwork(addr), nil | ||
case UNSPECIFIED_NAMED_NETWORK: | ||
return addr.IsUnspecified(), nil | ||
|
||
} | ||
|
||
// cidr range | ||
return isInRange(addr, network) | ||
} | ||
|
||
func isPrivateNetwork(addr net.IP) bool { | ||
isAddrInRange, _ := isInRange(addr, "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "fd00::/8") | ||
return isAddrInRange | ||
} | ||
|
||
func isPublicNetwork(addr net.IP) bool { | ||
if isPrivateNetwork(addr) || | ||
addr.IsLoopback() || | ||
addr.IsUnspecified() || | ||
addr.IsLinkLocalUnicast() || | ||
addr.IsLinkLocalMulticast() || | ||
addr.IsInterfaceLocalMulticast() || | ||
addr.Equal(net.IPv4bcast) { | ||
return false | ||
} | ||
|
||
return false | ||
} | ||
|
||
func isInRange(addr net.IP, networks ...string) (bool, error) { | ||
for _, network := range networks { | ||
_, mask, err := net.ParseCIDR(network) | ||
if err != nil { | ||
return false, fmt.Errorf("invalid network definition for %q", network) | ||
} | ||
|
||
if mask.Contains(addr) { | ||
return true, nil | ||
} | ||
} | ||
|
||
return false, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
// Copyright The OpenTelemetry Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package ottlfuncs | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func Test_NetworkDirection(t *testing.T) { | ||
testCases := []struct { | ||
sourceIP string | ||
destinationIP string | ||
internalNetworks []string | ||
expectedDirection string | ||
expectedError string | ||
}{ | ||
// cidr range tests | ||
{"10.0.1.1", "192.168.1.2", []string{"10.0.0.0/8"}, "outbound", ""}, | ||
{"192.168.1.2", "10.0.1.1", []string{"10.0.0.0/8"}, "inbound", ""}, | ||
|
||
// private network tests | ||
{"192.168.1.1", "192.168.1.2", []string{"private"}, "internal", ""}, | ||
{"10.0.1.1", "192.168.1.2", []string{"private"}, "internal", ""}, | ||
{"192.168.1.1", "172.16.0.1", []string{"private"}, "internal", ""}, | ||
{"192.168.1.1", "fd12:3456:789a:1::1", []string{"private"}, "internal", ""}, | ||
|
||
// public network tests | ||
{"192.168.1.1", "192.168.1.2", []string{"public"}, "external", ""}, | ||
{"10.0.1.1", "192.168.1.2", []string{"public"}, "external", ""}, | ||
{"192.168.1.1", "172.16.0.1", []string{"public"}, "external", ""}, | ||
{"192.168.1.1", "fd12:3456:789a:1::1", []string{"public"}, "external", ""}, | ||
|
||
// unspecified tests | ||
{"0.0.0.0", "0.0.0.0", []string{"unspecified"}, "internal", ""}, | ||
{"::", "::", []string{"unspecified"}, "internal", ""}, | ||
|
||
// invalid inputs tests | ||
{"invalid", "192.168.1.2", []string{}, "", `source IP "invalid" is not a valid address`}, | ||
{"192.168.1.1", "invalid", []string{}, "", `destination IP "invalid" is not a valid address`}, | ||
{"192.168.1.1", "192.168.1.2", []string{"10.0.0.0/8", "invalid"}, "", `failed determining whether source IP is internal: invalid network definition for "invalid"`}, | ||
} | ||
|
||
for i, testCase := range testCases { | ||
t.Run(fmt.Sprintf("test case #%d: %s -> %s", i, testCase.sourceIP, testCase.destinationIP), func(t *testing.T) { | ||
internalNetworksOptional := ottl.NewTestingOptional[[]string](testCase.internalNetworks) | ||
|
||
source := &ottl.StandardStringGetter[any]{ | ||
Getter: func(_ context.Context, _ any) (any, error) { | ||
return testCase.sourceIP, nil | ||
}, | ||
} | ||
|
||
destination := &ottl.StandardStringGetter[any]{ | ||
Getter: func(_ context.Context, _ any) (any, error) { | ||
return testCase.destinationIP, nil | ||
}, | ||
} | ||
exprFunc := networkDirection(source, destination, internalNetworksOptional) | ||
result, err := exprFunc(context.Background(), nil) | ||
if len(testCase.expectedError) > 0 { | ||
assert.Error(t, err) | ||
assert.Equal(t, testCase.expectedError, err.Error()) | ||
} else { | ||
assert.NoError(t, err) | ||
assert.Equal(t, testCase.expectedDirection, result) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters