Skip to content

Commit

Permalink
Merge pull request #407 from glazychev-art/iprule
Browse files Browse the repository at this point in the history
Add iprule chain element
  • Loading branch information
denis-tingaikin authored Jan 24, 2022
2 parents d78e942 + d56a308 commit 2292537
Show file tree
Hide file tree
Showing 20 changed files with 475 additions and 20 deletions.
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ require (
github.com/networkservicemesh/sdk v0.5.1-0.20220124073803-419fa7053a1a
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.7.0
github.com/vishvananda/netlink v1.1.0
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae
github.com/vishvananda/netlink v1.1.1-0.20220118170537-d6b03fdeb845
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74
go.uber.org/atomic v1.7.0
go.uber.org/goleak v1.1.12
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
google.golang.org/grpc v1.42.0
Expand Down
11 changes: 6 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -218,11 +218,11 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
github.com/vishvananda/netlink v1.1.1-0.20220118170537-d6b03fdeb845 h1:bIVwDExax2ZzN6mYF8Az0ms0wg+v/LYA4VI8x1TSQcs=
github.com/vishvananda/netlink v1.1.1-0.20220118170537-d6b03fdeb845/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
Expand Down Expand Up @@ -264,6 +264,7 @@ go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKu
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v0.11.0 h1:cLDgIBTf4lLOlztkhzAEdQsJ4Lj+i5Wc9k6Nn0K1VyU=
go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
Expand Down Expand Up @@ -327,14 +328,14 @@ golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
2 changes: 2 additions & 0 deletions pkg/kernel/const_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// +build linux

package kernel

import (
Expand Down
2 changes: 2 additions & 0 deletions pkg/kernel/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// +build linux

// Package kernel contains Link representation of network interface
package kernel

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) 2020-2021 Cisco and/or its affiliates.
// Copyright (c) 2020-2022 Cisco and/or its affiliates.
//
// Copyright (c) 2021 Nordix Foundation.
// Copyright (c) 2021-2022 Nordix Foundation.
//
// SPDX-License-Identifier: Apache-2.0
//
Expand Down Expand Up @@ -56,7 +56,7 @@ func create(ctx context.Context, conn *networkservice.Connection, isClient bool)
if err != nil {
return errors.WithStack(err)
}
defer netlinkHandle.Delete()
defer netlinkHandle.Close()

ifName := mechanism.GetInterfaceName()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func create(ctx context.Context, conn *networkservice.Connection, isClient bool)
if err != nil {
return errors.WithStack(err)
}
defer netlinkHandle.Delete()
defer netlinkHandle.Close()

ifName := mechanism.GetInterfaceName()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
// Copyright (c) 2022 Doc.ai and/or its affiliates.
//
// SPDX-License-Identifier: Apache-2.0
//
// 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.

// +build linux

package iprule

import (
"context"
"time"

"github.com/pkg/errors"
"github.com/vishvananda/netlink"
"go.uber.org/atomic"

"github.com/networkservicemesh/api/pkg/api/networkservice"
"github.com/networkservicemesh/api/pkg/api/networkservice/mechanisms/kernel"

"github.com/networkservicemesh/sdk/pkg/tools/log"

link "github.com/networkservicemesh/sdk-kernel/pkg/kernel"
)

func create(ctx context.Context, conn *networkservice.Connection, tableIDs *Map, counter *atomic.Int32) error {
if mechanism := kernel.ToMechanism(conn.GetMechanism()); mechanism != nil && mechanism.GetVLAN() == 0 {
// Construct the netlink handle for the target namespace for this kernel interface
netlinkHandle, err := link.GetNetlinkHandle(mechanism.GetNetNSURL())
if err != nil {
return errors.WithStack(err)
}
defer netlinkHandle.Close()

l, err := netlinkHandle.LinkByName(mechanism.GetInterfaceName())
if err != nil {
return errors.WithStack(err)
}

if err = netlinkHandle.LinkSetUp(l); err != nil {
return errors.WithStack(err)
}

for _, policy := range conn.Context.IpContext.Policies {
// Check if we already created required ip table
key := tableKey{
from: policy.From,
protocol: policy.Proto,
port: policy.Port,
}
tableID, ok := tableIDs.Load(key)
if !ok {
counter.Inc()
tableID = int(counter.Load())
}

// If policy doesn't contain any route - add default
if len(policy.Routes) == 0 {
policy.Routes = append(policy.Routes, defaultRoute())
}
for _, route := range policy.Routes {
if err := routeAdd(ctx, netlinkHandle, l, route, tableID); err != nil {
return err
}
}

if !ok {
// Check and delete old rules if they don't fit
_ = delOldRules(ctx, netlinkHandle, policy, tableID)
// Add new rule
if err := ruleAdd(ctx, netlinkHandle, policy, tableID); err != nil {
return err
}
tableIDs.Store(key, tableID)
}
}
}
return nil
}

func policyToRule(policy *networkservice.PolicyRoute) (*netlink.Rule, error) {
rule := netlink.NewRule()
if policy.From != "" {
src, err := netlink.ParseIPNet(policy.From)
if err != nil {
return nil, errors.WithStack(err)
}
rule.Src = src
}
rule.IPProto = int(policy.Proto)
rule.Dport = netlink.NewRulePortRange(uint16(policy.Port), uint16(policy.Port))
return rule, nil
}

func ruleAdd(ctx context.Context, handle *netlink.Handle, policy *networkservice.PolicyRoute, tableID int) error {
rule, err := policyToRule(policy)
if err != nil {
return errors.WithStack(err)
}
rule.Table = tableID

now := time.Now()
if err := handle.RuleAdd(rule); err != nil {
log.FromContext(ctx).
WithField("From", policy.From).
WithField("IPProto", policy.Proto).
WithField("Port", policy.Port).
WithField("Table", tableID).
WithField("duration", time.Since(now)).
WithField("netlink", "RuleAdd").Errorf("error %+v", err)
return errors.WithStack(err)
}
log.FromContext(ctx).
WithField("From", policy.From).
WithField("IPProto", policy.Proto).
WithField("Port", policy.Port).
WithField("Table", tableID).
WithField("duration", time.Since(now)).
WithField("netlink", "RuleAdd").Debug("completed")
return nil
}

func delOldRules(ctx context.Context, handle *netlink.Handle, policy *networkservice.PolicyRoute, tableID int) error {
rule, err := policyToRule(policy)
if err != nil {
return errors.WithStack(err)
}
flags := netlink.RT_FILTER_PROTOCOL
if rule.Src != nil {
flags |= netlink.RT_FILTER_SRC
}
rules, err := handle.RuleListFiltered(netlink.FAMILY_ALL, rule, flags)
if err != nil {
return errors.WithStack(err)
}
for i := range rules {
if rules[i].Dport == rule.Dport {
if rules[i].Table != tableID {
err = delRule(ctx, handle, policy)
if err != nil {
return errors.WithStack(err)
}
}
}
}
return nil
}

func defaultRoute() *networkservice.Route {
return &networkservice.Route{
Prefix: "0.0.0.0/0",
}
}

func routeAdd(ctx context.Context, handle *netlink.Handle, l netlink.Link, route *networkservice.Route, tableID int) error {
if route.GetPrefixIPNet() == nil {
return errors.New("kernelRoute prefix must not be nil")
}
dst := route.GetPrefixIPNet()
dst.IP = dst.IP.Mask(dst.Mask)
kernelRoute := &netlink.Route{
LinkIndex: l.Attrs().Index,
Scope: netlink.SCOPE_UNIVERSE,
Dst: dst,
Table: tableID,
}

gw := route.GetNextHopIP()
if gw != nil {
kernelRoute.Gw = gw
kernelRoute.SetFlag(netlink.FLAG_ONLINK)
}

now := time.Now()
if err := handle.RouteReplace(kernelRoute); err != nil {
log.FromContext(ctx).
WithField("link.Name", l.Attrs().Name).
WithField("Dst", kernelRoute.Dst).
WithField("Gw", kernelRoute.Gw).
WithField("Scope", kernelRoute.Scope).
WithField("Flags", kernelRoute.Flags).
WithField("Table", tableID).
WithField("duration", time.Since(now)).
WithField("netlink", "RouteReplace").Errorf("error %+v", err)
return errors.WithStack(err)
}
log.FromContext(ctx).
WithField("link.Name", l.Attrs().Name).
WithField("Dst", kernelRoute.Dst).
WithField("Gw", kernelRoute.Gw).
WithField("Scope", kernelRoute.Scope).
WithField("Flags", kernelRoute.Flags).
WithField("Table", tableID).
WithField("duration", time.Since(now)).
WithField("netlink", "RouteReplace").Debug("completed")
return nil
}

func del(ctx context.Context, conn *networkservice.Connection) error {
if mechanism := kernel.ToMechanism(conn.GetMechanism()); mechanism != nil && mechanism.GetVLAN() == 0 {
netlinkHandle, err := link.GetNetlinkHandle(mechanism.GetNetNSURL())
if err != nil {
return errors.WithStack(err)
}
defer netlinkHandle.Close()
for _, policy := range conn.Context.IpContext.Policies {
if err := delRule(ctx, netlinkHandle, policy); err != nil {
return errors.WithStack(err)
}
}
}
return nil
}

func delRule(ctx context.Context, handle *netlink.Handle, policy *networkservice.PolicyRoute) error {
rule, err := policyToRule(policy)
if err != nil {
return errors.WithStack(err)
}

now := time.Now()
if err := handle.RuleDel(rule); err != nil {
log.FromContext(ctx).
WithField("From", policy.From).
WithField("IPProto", policy.Proto).
WithField("Port", policy.Port).
WithField("duration", time.Since(now)).
WithField("netlink", "RuleDel").Errorf("error %+v", err)
return errors.WithStack(err)
}
log.FromContext(ctx).
WithField("From", policy.From).
WithField("IPProto", policy.Proto).
WithField("Port", policy.Port).
WithField("duration", time.Since(now)).
WithField("netlink", "RuleDel").Debug("completed")
return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) 2022 Doc.ai and/or its affiliates.
//
// SPDX-License-Identifier: Apache-2.0
//
// 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 iprule provides networkservice chain elements that support setting ip rules
package iprule
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) 2022 Doc.ai and/or its affiliates.
//
// SPDX-License-Identifier: Apache-2.0
//
// 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 iprule

import "sync"

//go:generate go-syncmap -output table_map.gen.go -type Map<tableKey,int>

type tableKey struct {
from string
protocol uint32
port uint32
}

// Map - sync.Map with key == tableKey and value == uint32
type Map sync.Map
Loading

0 comments on commit 2292537

Please sign in to comment.