-
Notifications
You must be signed in to change notification settings - Fork 111
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
TCX support: introduce interface filter
This is a variant of the existing interface filter present in the 'agent' package.
- Loading branch information
1 parent
1192264
commit 176420c
Showing
2 changed files
with
185 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
// Copyright Red Hat / IBM | ||
// Copyright Grafana Labs | ||
// | ||
// Licensed 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 | ||
// | ||
// http://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. | ||
|
||
// This implementation is a derivation of the code in | ||
// https://github.com/netobserv/netobserv-ebpf-agent/tree/release-1.4 | ||
|
||
package tcmanager | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
"strings" | ||
) | ||
|
||
// InterfaceFilter allows filtering network interfaces that are accepted/excluded by the user, | ||
// according to the provided allowed and excluded interfaces from the configuration. It allows | ||
// matching by exact string or by regular expression | ||
|
||
type InterfaceFilter struct { | ||
allowedRegexpes []*regexp.Regexp | ||
allowedMatches []string | ||
excludedRegexpes []*regexp.Regexp | ||
excludedMatches []string | ||
isRegexp *regexp.Regexp | ||
} | ||
|
||
func NewInterfaceFilter(allowed []string, excluded []string) (*InterfaceFilter, error) { | ||
itf := InterfaceFilter{ | ||
isRegexp: regexp.MustCompile("^/(.*)/$"), | ||
} | ||
|
||
for _, pattern := range allowed { | ||
if err := itf.Allow(pattern); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
for _, pattern := range excluded { | ||
if err := itf.Deny(pattern); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
ret := &itf | ||
return ret, nil | ||
} | ||
|
||
func (itf *InterfaceFilter) Allow(pattern string) error { | ||
return itf.addPattern(pattern, &itf.allowedRegexpes, &itf.allowedMatches) | ||
} | ||
|
||
func (itf *InterfaceFilter) Deny(pattern string) error { | ||
return itf.addPattern(pattern, &itf.excludedRegexpes, &itf.excludedMatches) | ||
} | ||
|
||
func (itf *InterfaceFilter) addPattern(pattern string, | ||
regexps *[]*regexp.Regexp, matches *[]string) error { | ||
if regexps == nil || matches == nil { | ||
return fmt.Errorf("Logic error: addPattern has null params") | ||
} | ||
|
||
pattern = strings.Trim(pattern, " ") | ||
|
||
// the user defined a /regexp/ between slashes: compile and store it as regular expression | ||
if sm := itf.isRegexp.FindStringSubmatch(pattern); len(sm) > 1 { | ||
re, err := regexp.Compile(sm[1]) | ||
|
||
if err != nil { | ||
return fmt.Errorf("wrong interface regexp %q: %w", pattern, err) | ||
} | ||
|
||
*regexps = append(*regexps, re) | ||
} else { | ||
// otherwise, store it as exact match pattern | ||
*matches = append(*matches, pattern) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (itf *InterfaceFilter) IsAllowed(name string) bool { | ||
// if the allowed list is empty, any interface is allowed except if it matches the exclusion list | ||
allowed := len(itf.allowedRegexpes)+len(itf.allowedMatches) == 0 | ||
|
||
// otherwise, we check if it appears in the allowed lists (both exact match and regexp) | ||
for i := 0; !allowed && i < len(itf.allowedMatches); i++ { | ||
allowed = allowed || name == itf.allowedMatches[i] | ||
} | ||
|
||
for i := 0; !allowed && i < len(itf.allowedRegexpes); i++ { | ||
allowed = allowed || itf.allowedRegexpes[i].MatchString(string(name)) | ||
} | ||
|
||
if !allowed { | ||
return false | ||
} | ||
|
||
// if the interface matches the allow lists, we still need to check that is not excluded | ||
for _, match := range itf.excludedMatches { | ||
if name == match { | ||
return false | ||
} | ||
} | ||
|
||
for _, re := range itf.excludedRegexpes { | ||
if re.MatchString(string(name)) { | ||
return false | ||
} | ||
} | ||
|
||
return true | ||
} |
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,61 @@ | ||
// Copyright Red Hat / IBM | ||
// Copyright Grafana Labs | ||
// | ||
// Licensed 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 | ||
// | ||
// http://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. | ||
|
||
// This implementation is a derivation of the code in | ||
// https://github.com/netobserv/netobserv-ebpf-agent/tree/release-1.4 | ||
|
||
package tcmanager | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestInterfaces_DefaultConfig(t *testing.T) { | ||
ifaces, err := NewInterfaceFilter(nil, []string{"lo"}) | ||
require.NoError(t, err) | ||
|
||
assert.True(t, ifaces.IsAllowed("eth0")) | ||
assert.True(t, ifaces.IsAllowed("br-0")) | ||
assert.False(t, ifaces.IsAllowed("lo")) | ||
} | ||
|
||
func TestInterfaceFilter_SelectingInterfaces_DefaultExclusion(t *testing.T) { | ||
ifaces, err := NewInterfaceFilter([]string{"eth0", "/^br-/"}, []string{"lo"}) | ||
require.NoError(t, err) | ||
|
||
assert.True(t, ifaces.IsAllowed("eth0")) | ||
assert.True(t, ifaces.IsAllowed("br-0")) | ||
assert.False(t, ifaces.IsAllowed("eth01")) | ||
assert.False(t, ifaces.IsAllowed("abr-3")) | ||
assert.False(t, ifaces.IsAllowed("lo")) | ||
} | ||
|
||
func TestInterfaceFilter_ExclusionTakesPriority(t *testing.T) { | ||
|
||
ifaces, err := NewInterfaceFilter([]string{"/^eth/", "/^br-/"}, []string{"eth1", "/^br-1/"}) | ||
require.NoError(t, err) | ||
|
||
assert.True(t, ifaces.IsAllowed("eth0")) | ||
assert.True(t, ifaces.IsAllowed("eth10")) | ||
assert.True(t, ifaces.IsAllowed("eth11")) | ||
assert.True(t, ifaces.IsAllowed("br-2")) | ||
assert.True(t, ifaces.IsAllowed("br-0")) | ||
assert.False(t, ifaces.IsAllowed("eth1")) | ||
assert.False(t, ifaces.IsAllowed("br-1")) | ||
assert.False(t, ifaces.IsAllowed("br-10")) | ||
} |