diff --git a/go/lib/pathmgr/pathmgr.go b/go/lib/pathmgr/pathmgr.go index e70e27a893..e75c7bec71 100644 --- a/go/lib/pathmgr/pathmgr.go +++ b/go/lib/pathmgr/pathmgr.go @@ -274,8 +274,7 @@ type pathWrap struct { *spathmeta.AppPath } -func (p pathWrap) Key() string { return p.AppPath.Key().String() } -func (p pathWrap) IsPartial() bool { return false } +func (p pathWrap) Key() string { return p.AppPath.Key().String() } func (p pathWrap) Interfaces() []pathpol.PathInterface { intfs := make([]pathpol.PathInterface, 0, len(p.Entry.Path.Interfaces)) for _, intf := range p.Entry.Path.Interfaces { diff --git a/go/lib/pathpol/pathset.go b/go/lib/pathpol/pathset.go index ff6463992e..632d445217 100644 --- a/go/lib/pathpol/pathset.go +++ b/go/lib/pathpol/pathset.go @@ -26,8 +26,6 @@ type PathSet map[string]Path type Path interface { // Interfaces returns all the interfaces of this path. Interfaces() []PathInterface - // IsPartial returns whether this is a partial path. - IsPartial() bool // Returns a string that uniquely identifies this path. Key() string } diff --git a/go/lib/pathpol/policy_test.go b/go/lib/pathpol/policy_test.go index e3ddbcba9f..82b2561680 100644 --- a/go/lib/pathpol/policy_test.go +++ b/go/lib/pathpol/policy_test.go @@ -822,8 +822,6 @@ func (p *testPath) Interfaces() []PathInterface { return p.interfaces } -func (p *testPath) IsPartial() bool { return false } - func (p *testPath) Key() string { return p.key } type testPathIntf struct { diff --git a/go/path_srv/internal/segutil/BUILD.bazel b/go/path_srv/internal/segutil/BUILD.bazel new file mode 100644 index 0000000000..66525effec --- /dev/null +++ b/go/path_srv/internal/segutil/BUILD.bazel @@ -0,0 +1,31 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["filter.go"], + importpath = "github.com/scionproto/scion/go/path_srv/internal/segutil", + visibility = ["//go/path_srv:__subpackages__"], + deps = [ + "//go/lib/addr:go_default_library", + "//go/lib/common:go_default_library", + "//go/lib/ctrl/seg:go_default_library", + "//go/lib/pathpol:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["filter_test.go"], + embed = [":go_default_library"], + deps = [ + "//go/lib/addr:go_default_library", + "//go/lib/common:go_default_library", + "//go/lib/ctrl/seg:go_default_library", + "//go/lib/pathpol:go_default_library", + "//go/lib/xtest:go_default_library", + "//go/lib/xtest/graph:go_default_library", + "@com_github_golang_mock//gomock:go_default_library", + "@com_github_stretchr_testify//assert:go_default_library", + "@com_github_stretchr_testify//require:go_default_library", + ], +) diff --git a/go/path_srv/internal/segutil/filter.go b/go/path_srv/internal/segutil/filter.go new file mode 100644 index 0000000000..ff6639965a --- /dev/null +++ b/go/path_srv/internal/segutil/filter.go @@ -0,0 +1,116 @@ +// Copyright 2019 Anapaya Systems +// +// 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. + +package segutil + +import ( + "fmt" + "strings" + + "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/ctrl/seg" + "github.com/scionproto/scion/go/lib/pathpol" +) + +type Direction int + +const ( + ReverseConsDir Direction = iota + ConsDir +) + +// Filter filters the given segments with the policy. Dir indicates the +// direction of intended usage of the segments. For example up and core segments +// are most often used in reverse construction dir. The direction parameter is +// relevant for the sequence matching of policies. Note that order of segs is +// not preserved. +// NOTE: This function should only be applied on core segments, otherwise the PS +// might filter segments that could still have been used in a final path, +// because of peering links. +func Filter(segs seg.Segments, policy *pathpol.Policy, dir Direction) seg.Segments { + if policy == nil { + return segs + } + // The sequence filter doesn't work for segments, therefore a policy + // without sequence is used. + actPolicy := pathpol.NewPolicy(policy.Name, policy.ACL, nil, policy.Options) + return psToSegs(actPolicy.Act(segsToPs(segs, dir))) +} + +func segsToPs(segs seg.Segments, dir Direction) pathpol.PathSet { + ps := make(pathpol.PathSet, len(segs)) + for _, seg := range segs { + sw := wrap(seg, dir) + ps[sw.Key()] = sw + } + return ps +} + +func psToSegs(ps pathpol.PathSet) seg.Segments { + segs := make(seg.Segments, 0, len(ps)) + for _, sw := range ps { + seg := sw.(segWrap).origSeg + segs = append(segs, seg) + } + return segs +} + +type segWrap struct { + intfs []pathpol.PathInterface + key string + origSeg *seg.PathSegment +} + +func wrap(seg *seg.PathSegment, dir Direction) segWrap { + intfs := make([]pathpol.PathInterface, 0, len(seg.ASEntries)) + keyParts := make([]string, 0, len(seg.ASEntries)) + for _, asEntry := range seg.ASEntries { + for _, hopEntry := range asEntry.HopEntries { + hopField, err := hopEntry.HopField() + // This library expects the segments to be verified first. + if err != nil { + panic(err) + } + for _, ifid := range []common.IFIDType{hopField.ConsIngress, hopField.ConsEgress} { + if ifid != 0 { + intfs = append(intfs, pathInterface{ia: asEntry.IA(), ifid: ifid}) + keyParts = append(keyParts, fmt.Sprintf("%s#%d", asEntry.IA(), ifid)) + } + } + } + } + if dir == ReverseConsDir { + // reverse interfaces + for left, right := 0, len(intfs)-1; left < right; left, right = left+1, right-1 { + intfs[left], intfs[right] = intfs[right], intfs[left] + } + } + return segWrap{ + intfs: intfs, + key: strings.Join(keyParts, " "), + origSeg: seg, + } +} + +func (s segWrap) Interfaces() []pathpol.PathInterface { return s.intfs } +func (s segWrap) Key() string { return s.key } + +type pathInterface struct { + ia addr.IA + ifid common.IFIDType +} + +func (i pathInterface) IA() addr.IA { return i.ia } +func (i pathInterface) IfId() common.IFIDType { return i.ifid } diff --git a/go/path_srv/internal/segutil/filter_test.go b/go/path_srv/internal/segutil/filter_test.go new file mode 100644 index 0000000000..a3db4c043c --- /dev/null +++ b/go/path_srv/internal/segutil/filter_test.go @@ -0,0 +1,92 @@ +// Copyright 2019 Anapaya Systems +// +// 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. + +package segutil + +import ( + "fmt" + "strings" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/ctrl/seg" + "github.com/scionproto/scion/go/lib/pathpol" + "github.com/scionproto/scion/go/lib/xtest" + "github.com/scionproto/scion/go/lib/xtest/graph" +) + +func TestFilter(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + g := graph.NewDefaultGraph(ctrl) + ia110 := xtest.MustParseIA("1-ff00:0:110") + ia111 := xtest.MustParseIA("1-ff00:0:111") + ia120 := xtest.MustParseIA("1-ff00:0:120") + seg110To120 := g.Beacon([]common.IFIDType{graph.If_110_X_120_A}) + seg110To130 := g.Beacon([]common.IFIDType{graph.If_110_X_130_A}) + + tests := map[string]struct { + Segs seg.Segments + Dir Direction + Policy *pathpol.Policy + ExpectedSegs seg.Segments + }{ + "no policy": { + Segs: seg.Segments{seg110To120, seg110To130}, + ExpectedSegs: seg.Segments{seg110To120, seg110To130}, + }, + "acl policy": { + Segs: seg.Segments{seg110To120, seg110To130}, + Policy: &pathpol.Policy{ACL: acl(t, ia120)}, + ExpectedSegs: seg.Segments{seg110To130}, + }, + "sequence policy doesn't filter": { + Segs: seg.Segments{seg110To120, seg110To130}, + Policy: &pathpol.Policy{Sequence: sequence(t, ia111, ia110)}, + ExpectedSegs: seg.Segments{seg110To120, seg110To130}, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + assert.ElementsMatch(t, test.ExpectedSegs, Filter(test.Segs, test.Policy, test.Dir)) + }) + } +} + +func acl(t testing.TB, disallow addr.IA) *pathpol.ACL { + var disallowEntry pathpol.ACLEntry + err := disallowEntry.LoadFromString(fmt.Sprintf("- %s", disallow)) + xtest.FailOnErr(t, err) + var allowEntry pathpol.ACLEntry + err = allowEntry.LoadFromString("+") + xtest.FailOnErr(t, err) + acl, err := pathpol.NewACL(&disallowEntry, &allowEntry) + xtest.FailOnErr(t, err) + return acl +} + +func sequence(t testing.TB, ias ...addr.IA) *pathpol.Sequence { + parts := make([]string, 0, len(ias)) + for _, ia := range ias { + parts = append(parts, ia.String()) + } + seq, err := pathpol.NewSequence(strings.Join(parts, " ")) + require.NoError(t, err) + return seq +} diff --git a/go/sciond/internal/fetcher/filter.go b/go/sciond/internal/fetcher/filter.go index c5ce41bd6b..46464f9c99 100644 --- a/go/sciond/internal/fetcher/filter.go +++ b/go/sciond/internal/fetcher/filter.go @@ -69,5 +69,4 @@ func newPathWrap(p *combinator.Path) pathWrap { } func (p pathWrap) Interfaces() []pathpol.PathInterface { return p.intfs } -func (p pathWrap) IsPartial() bool { return false } func (p pathWrap) Key() string { return p.key }