Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add singlepointipam #1093

Merged
merged 1 commit into from
Nov 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions pkg/networkservice/ipam/singlepointipam/connectionInfoMap.gen.go

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

26 changes: 26 additions & 0 deletions pkg/networkservice/ipam/singlepointipam/gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) 2021 Cisco 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 singlepointipam

import (
"sync"
)

//go:generate go-syncmap -output connectionInfoMap.gen.go -type Map<string,*connectionInfo>

// Map - sync.Map with key == string and value == networkservice.NetworkServiceClient
type Map sync.Map
241 changes: 241 additions & 0 deletions pkg/networkservice/ipam/singlepointipam/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
// Copyright (c) 2020-2021 Nordix and 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 singlepointipam

import (
"context"
"fmt"
"net"
"sync"

"github.com/golang/protobuf/ptypes/empty"
"github.com/pkg/errors"

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

"github.com/networkservicemesh/sdk/pkg/networkservice/core/next"
"github.com/networkservicemesh/sdk/pkg/tools/ippool"
)

type singlePIpam struct {
Map
ipPools []*ippool.IPPool
prefixes []*net.IPNet
myIPs []string
masks []string
once sync.Once
initErr error
}

type connectionInfo struct {
ipPool *ippool.IPPool
srcAddr string
dstAddr string
}

func (i *connectionInfo) shouldUpdate(exclude *ippool.IPPool) bool {
srcIP, _, srcErr := net.ParseCIDR(i.srcAddr)
dstIP, _, dstErr := net.ParseCIDR(i.dstAddr)

return srcErr == nil && exclude.ContainsString(srcIP.String()) || dstErr == nil && exclude.ContainsString(dstIP.String())
}

// NewServer - creates a new NetworkServiceServer chain element that implements IPAM service.
func NewServer(prefixes ...*net.IPNet) networkservice.NetworkServiceServer {
return &singlePIpam{
prefixes: prefixes,
}
}
func (sipam *singlePIpam) init() {
if len(sipam.prefixes) == 0 {
sipam.initErr = errors.New("required one or more prefixes")
return
}
for _, prefix := range sipam.prefixes {
if prefix == nil {
sipam.initErr = errors.Errorf("prefix must not be nil: %+v", sipam.prefixes)
return
}
ones, _ := prefix.Mask.Size()
mask := fmt.Sprintf("/%d", ones)
sipam.masks = append(sipam.masks, mask)
ipPool := ippool.NewWithNet(prefix)
sipam.ipPools = append(sipam.ipPools, ipPool)
}
}

func (sipam *singlePIpam) Request(ctx context.Context, request *networkservice.NetworkServiceRequest) (*networkservice.Connection, error) {
sipam.once.Do(sipam.init)
if sipam.initErr != nil {
return nil, sipam.initErr
}

conn := request.GetConnection()
if conn.GetContext() == nil {
conn.Context = &networkservice.ConnectionContext{}
}
connContext := conn.GetContext()
if connContext.GetIpContext() == nil {
connContext.IpContext = &networkservice.IPContext{}
}
ipContext := connContext.GetIpContext()

excludeIP4, excludeIP6 := exclude(ipContext.GetExcludedPrefixes()...)

connInfo, loaded := sipam.Load(conn.GetId())
if loaded && (connInfo.shouldUpdate(excludeIP4) || connInfo.shouldUpdate(excludeIP6)) {
// some of the existing addresses are excluded
deleteAddr(&ipContext.DstIpAddrs, connInfo.dstAddr)
deleteAddr(&ipContext.SrcIpAddrs, connInfo.srcAddr)
sipam.free(connInfo)
loaded = false
}
var err error
if !loaded {
if connInfo, err = sipam.getAddrs(excludeIP4, excludeIP6); err != nil {
return nil, err
}
sipam.Store(conn.GetId(), connInfo)
}

addAddr(&ipContext.SrcIpAddrs, connInfo.srcAddr)
addAddr(&ipContext.DstIpAddrs, connInfo.dstAddr)

conn, err = next.Server(ctx).Request(ctx, request)
if err != nil {
if !loaded {
sipam.free(connInfo)
}
return nil, err
}

return conn, nil
}

func (sipam *singlePIpam) Close(
ctx context.Context, conn *networkservice.Connection) (_ *empty.Empty, err error) {
sipam.once.Do(sipam.init)
if sipam.initErr != nil {
return nil, sipam.initErr
}

if connInfo, ok := sipam.Load(conn.GetId()); ok {
sipam.free(connInfo)
}
return next.Server(ctx).Close(ctx, conn)
}

func addMaskIP(ip net.IP) string {
if ip.To4() != nil {
return ip.String() + "/32"
}
return ip.String() + "/128"
}

func (sipam *singlePIpam) free(connInfo *connectionInfo) {
ipAddr, _, err := net.ParseCIDR(connInfo.srcAddr)
if err == nil {
connInfo.ipPool.AddNetString(addMaskIP(ipAddr))
}
}

func (sipam *singlePIpam) setMyIP(i int) error {
myIP, err := sipam.ipPools[i].Pull()
if err != nil {
return err
}
if i >= len(sipam.myIPs) {
sipam.myIPs = append(sipam.myIPs, myIP.String()+sipam.masks[i])
} else {
sipam.myIPs[i] = myIP.String() + sipam.masks[i]
}
return nil
}

func (sipam *singlePIpam) getAddrs(excludeIP4, excludeIP6 *ippool.IPPool) (connInfo *connectionInfo, err error) {
var dstAddr, srcAddr net.IP

for i := 0; i < len(sipam.prefixes); i++ {
// The NSE needs only one src address
dstSet := false
if i >= len(sipam.myIPs) {
err = sipam.setMyIP(i)
if err != nil {
continue
}
}

for {
myIP, _, _ := net.ParseCIDR(sipam.myIPs[i])
if !excludeIP4.ContainsString(myIP.String()) && !excludeIP6.ContainsString(myIP.String()) {
dstAddr = myIP
dstSet = true
break
}
err = sipam.setMyIP(i)
if err != nil {
break
}
}
for {
if srcAddr, err = sipam.ipPools[i].Pull(); err != nil {
break
}
if !excludeIP4.ContainsString(srcAddr.String()) && !excludeIP6.ContainsString(srcAddr.String()) {
if dstSet {
return &connectionInfo{
ipPool: sipam.ipPools[i],
srcAddr: srcAddr.String() + sipam.masks[i],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we also need non /32 mask for srcAddr?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is meant to be used for external communication also. The client pod which getting this address should be connected to a router and should belong to wider subnet, for example /24.

dstAddr: dstAddr.String() + sipam.masks[i],
}, nil
}
}
}
}
return nil, err
}

//
// common with point2point
// ------------------------

func exclude(prefixes ...string) (ipv4exclude, ipv6exclude *ippool.IPPool) {
ipv4exclude = ippool.New(net.IPv4len)
ipv6exclude = ippool.New(net.IPv6len)
for _, prefix := range prefixes {
ipv4exclude.AddNetString(prefix)
ipv6exclude.AddNetString(prefix)
}
return
}
func deleteAddr(addrs *[]string, addr string) {
for i, a := range *addrs {
if a == addr {
*addrs = append((*addrs)[:i], (*addrs)[i+1:]...)
return
}
}
}

func addAddr(addrs *[]string, addr string) {
for _, a := range *addrs {
if a == addr {
return
}
}
*addrs = append(*addrs, addr)
}
Loading