Skip to content

Commit

Permalink
Generic validating framework added.
Browse files Browse the repository at this point in the history
Basically the idea is that a set of validating functions will be mapped to specific API paths, and automatically executed.
If any of them return an error, we block the object provisioning.
The framework enables the easy expansion of the wbehook with other APIs... which we might need preeeeettyyyy sooon in the future ;)

Several functions in ipam and netcontrol packages are moved around to better fitting places.
  • Loading branch information
Levovar committed May 21, 2019
1 parent b8b106a commit 09d6f9f
Show file tree
Hide file tree
Showing 6 changed files with 262 additions and 214 deletions.
21 changes: 20 additions & 1 deletion pkg/bitarray/bitarray.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package bitarray

import (
b64 "encoding/base64"
"errors"
"math"
"net"
b64 "encoding/base64"
)

const (
MaxSupportedNetmask = 32
)

// BitArray is type to represent an arbitrary long array of bits
Expand Down Expand Up @@ -32,6 +38,19 @@ func NewBitArrayFromBase64(text string) *BitArray {
return arr
}

func CreateBitArrayFromIpnet(ipnet *net.IPNet) (*BitArray,error) {
ones, _ := ipnet.Mask.Size()
if ones > MaxSupportedNetmask {
return nil, errors.New("DANM does not support networks with more than 2^32 IP addresses")
}
bitArray,err := NewBitArray(int(math.Pow(2,float64(MaxSupportedNetmask-ones))))
if err != nil {
return nil,errors.New("BitArray allocation failed because:" + err.Error())
}
bitArray.Set(uint32(math.Pow(2,float64(MaxSupportedNetmask-ones))-1))
return bitArray,nil
}

// Set sets the bit at the input position of the BitArray
func (arr *BitArray) Set(pos uint32) {
arr.data[pos/8] |= byte( 0x1 << (7-pos%8))
Expand Down
51 changes: 41 additions & 10 deletions pkg/ipam/ipam.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strconv"
"strings"
"time"
"encoding/binary"
"math/big"
"math/rand"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -87,13 +88,13 @@ func updateDanmNetAllocation (netClient client.DanmNetInterface, netInfo danmtyp
func resetIP(netInfo *danmtypes.DanmNet, rip string) {
ba := bitarray.NewBitArrayFromBase64(netInfo.Spec.Options.Alloc)
_, ipnet, _ := net.ParseCIDR(netInfo.Spec.Options.Cidr)
ipnetNum := netcontrol.Ip2int(ipnet.IP)
ipnetNum := Ip2int(ipnet.IP)
ip, _, err := net.ParseCIDR(rip)
if err != nil {
//Invalid IP, nothing to do here. Next call would crash if we wouldn't return
return
}
reserved := netcontrol.Ip2int(ip)
reserved := Ip2int(ip)
if !ipnet.Contains(ip) {
//IP is outside of CIDR, nothing to do here. Next call would crash if we wouldn't return
return
Expand Down Expand Up @@ -132,13 +133,13 @@ func allocIPv4(reqType string, netInfo *danmtypes.DanmNet, ip4 *string) (error)
}
ba := bitarray.NewBitArrayFromBase64(netInfo.Spec.Options.Alloc)
_, ipnet, _ := net.ParseCIDR(netInfo.Spec.Options.Cidr)
ipnetNum := netcontrol.Ip2int(ipnet.IP)
begin := netcontrol.Ip2int(net.ParseIP(netInfo.Spec.Options.Pool.Start)) - ipnetNum
end := netcontrol.Ip2int(net.ParseIP(netInfo.Spec.Options.Pool.End)) - ipnetNum
ipnetNum := Ip2int(ipnet.IP)
begin := Ip2int(net.ParseIP(netInfo.Spec.Options.Pool.Start)) - ipnetNum
end := Ip2int(net.ParseIP(netInfo.Spec.Options.Pool.End)) - ipnetNum
for i:=begin; i<=end; i++ {
if !ba.Get(uint32(i)) {
ones, _ := ipnet.Mask.Size()
*ip4 = (netcontrol.Int2ip(ipnetNum + i)).String() + "/" + strconv.Itoa(ones)
*ip4 = (Int2ip(ipnetNum + i)).String() + "/" + strconv.Itoa(ones)
ba.Set(uint32(i))
netInfo.Spec.Options.Alloc = ba.Encode()
break
Expand All @@ -160,8 +161,8 @@ func allocIPv4(reqType string, netInfo *danmtypes.DanmNet, ip4 *string) (error)
return errors.New("static ip is not part of network CIDR/allocation pool")
}
ba := bitarray.NewBitArrayFromBase64(netInfo.Spec.Options.Alloc)
ipnetNum := netcontrol.Ip2int(ipnetFromNet.IP)
requested := netcontrol.Ip2int(ip)
ipnetNum := Ip2int(ipnetFromNet.IP)
requested := Ip2int(ip)
if ba.Get(requested - ipnetNum) {
return errors.New("requested fix ip address is already in use")
}
Expand Down Expand Up @@ -189,12 +190,12 @@ func allocIPv6(reqType string, netInfo *danmtypes.DanmNet, ip6 *string, macAddr
bigeui.SetString(eui, 16)
ip6addr, ip6net, _ := net.ParseCIDR(net6)
ss := big.NewInt(0)
ss.Add(netcontrol.Ip62int(ip6addr), bigeui)
ss.Add(Ip62int(ip6addr), bigeui)
maskLen, _ := ip6net.Mask.Size()
if maskLen>64 {
return errors.New("IPv6 subnets smaller than /64 are not supported at the moment!")
}
*ip6 = (netcontrol.Int2ip6(ss)).String() + "/" + strconv.Itoa(maskLen)
*ip6 = (Int2ip6(ss)).String() + "/" + strconv.Itoa(maskLen)
} else {
net6 := netInfo.Spec.Options.Net6
if net6 == "" {
Expand Down Expand Up @@ -222,4 +223,34 @@ func generateMac()(string) {
func GarbageCollectIps(danmClient danmclientset.Interface, netInfo *danmtypes.DanmNet, ip4, ip6 string) {
Free(danmClient, *netInfo, ip4)
Free(danmClient, *netInfo, ip6)
}

// Ip2int converts an IP address stored according to the Golang net package to a native Golang big endian, 32-bit integer
func Ip2int(ip net.IP) uint32 {
if len(ip) == 16 {
return binary.BigEndian.Uint32(ip[12:16])
}
return binary.BigEndian.Uint32(ip)
}

// Ip62int converts an IPv6 address stored according to the Golang net package to a native Golang big endian, 64-bit integer
func Ip62int(ip6 net.IP) *big.Int {
Ip6Int := big.NewInt(0)
Ip6Int.SetBytes(ip6.To16())
return Ip6Int
}

// Int2ip converts an IP address stored as a native Golang big endian, 32-bit integer to an IP
// represented according to the Golang net package
func Int2ip(nn uint32) net.IP {
ip := make(net.IP, 4)
binary.BigEndian.PutUint32(ip, nn)
return ip
}

// Int2ip6 converts an IP address stored as a native Golang big endian, 64-bit integer to an IP
// represented according to the Golang net package
func Int2ip6(nn *big.Int) net.IP {
ip := nn.Bytes()
return ip
}
85 changes: 85 additions & 0 deletions pkg/netadmit/netadmit.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,93 @@
package netadmit

import (
"errors"
"net"
"encoding/json"
"io/ioutil"
"net/http"
"k8s.io/api/admission/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
danmtypes "github.com/nokia/danm/crd/apis/danm/v1"
"github.com/nokia/danm/pkg/bitarray"
"github.com/nokia/danm/pkg/ipam"
)

func ValidateNetwork(responseWriter http.ResponseWriter, request *http.Request) {
admissionReview, err := DecodeAdmissionReview(request)
if err != nil {
return
}
manifest, err := getNetworkManifest(admissionReview.Request.Object.Raw)
if err != nil {
return
}
isManifestValid, err := validateNetworkByType(manifest)
if !isManifestValid {
return
}
/* bitArray, err := CreateAllocationArray(dnet)
if err != nil {
return err
}
dnet.Spec.Options.Alloc = bitArray.Encode()*/
return
}

func DecodeAdmissionReview(httpRequest *http.Request) (*v1beta1.AdmissionReview,error) {
var payload []byte
if httpRequest.Body == nil {
return nil, errors.New("Received review request is empty!")
}
payload, err := ioutil.ReadAll(httpRequest.Body);
if err != nil {
return nil, err
}
codecs := serializer.NewCodecFactory(runtime.NewScheme())
deserializer := codecs.UniversalDeserializer()
reviewRequest := v1beta1.AdmissionReview{}
_, _, err = deserializer.Decode(payload, nil, &reviewRequest)
return &reviewRequest, err
}

func getNetworkManifest(objectToReview []byte) (*danmtypes.DanmNet,error) {
networkManifest := danmtypes.DanmNet{}
err := json.Unmarshal(objectToReview, &networkManifest)
return &networkManifest, err
}

func validateNetworkByType(manifest *danmtypes.DanmNet) (bool,error) {
for _, validatorMapping := range danmValidationConfig.ValidatorMappings {
if validatorMapping.ApiType == manifest.TypeMeta.Kind {
for _, validator := range validatorMapping.Validators {
err := validator(manifest)
if err != nil {
return false, err
}
}
}
}
return true, nil
}

func CreateAllocationArray(dnet *danmtypes.DanmNet) (*bitarray.BitArray,error) {
_,ipnet,_ := net.ParseCIDR(dnet.Spec.Options.Cidr)
bitArray, err := bitarray.CreateBitArrayFromIpnet(ipnet)
if err != nil {
return nil, err
}
err = reserveGatewayIps(dnet.Spec.Options.Routes, bitArray, ipnet)
if err != nil {
return nil, err
}
return bitArray, nil
}

func reserveGatewayIps(routes map[string]string, bitArray *bitarray.BitArray, ipnet *net.IPNet) error {
for _, gw := range routes {
gatewayPosition := ipam.Ip2int(net.ParseIP(gw)) - ipam.Ip2int(ipnet.IP)
bitArray.Set(gatewayPosition)
}
return nil
}
115 changes: 115 additions & 0 deletions pkg/netadmit/validators.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package netadmit

import (
"errors"
"net"
"encoding/binary"
danmtypes "github.com/nokia/danm/crd/apis/danm/v1"
"github.com/nokia/danm/pkg/ipam"
)

type Validator func(netInfo *danmtypes.DanmNet) error

type ValidatorConfig struct {
ValidatorMappings []ValidatorMapping
}

type ValidatorMapping struct {
ApiType string
Validators []Validator
}

const (
MaxNidLength = 12
)

var (
DanmNetMapping = ValidatorMapping {
ApiType: "DanmNet",
Validators: []Validator{validateIpv4Fields,validateIpv6Fields,validateAllocationPool,validateVids,validateNetworkId},
}
danmValidationConfig = ValidatorConfig {
ValidatorMappings: []ValidatorMapping{DanmNetMapping},
}
)

func validateIpv4Fields(dnet *danmtypes.DanmNet) error {
return validateIpFields(dnet.Spec.Options.Cidr, dnet.Spec.Options.Routes)
}

func validateIpv6Fields(dnet *danmtypes.DanmNet) error {
return validateIpFields(dnet.Spec.Options.Net6, dnet.Spec.Options.Routes6)
}

func validateIpFields(cidr string, routes map[string]string) error {
if cidr == "" {
if routes != nil {
return errors.New("IP routes cannot be defined for a L2 network")
}
return nil
}
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
return errors.New("Invalid CIDR: " + cidr)
}
for _, gw := range routes {
if !ipnet.Contains(net.ParseIP(gw)) {
return errors.New("Specified GW address:" + gw + " is not part of CIDR:" + cidr)
}
}
return nil
}

func validateAllocationPool(dnet *danmtypes.DanmNet) error {
cidr := dnet.Spec.Options.Cidr
apStart := dnet.Spec.Options.Pool.Start
apEnd := dnet.Spec.Options.Pool.End
if cidr == "" {
if apStart != "" || apEnd != "" {
return errors.New("Allocation pool cannot be defined without CIDR!")
}
return nil
}
_, ipnet, err := net.ParseCIDR(dnet.Spec.Options.Cidr)
if err != nil {
return errors.New("Invalid CIDR parameter: " + dnet.Spec.Options.Cidr)
}
if dnet.Spec.Options.Pool.Start == "" {
dnet.Spec.Options.Pool.Start = (ipam.Int2ip(ipam.Ip2int(ipnet.IP) + 1)).String()
}
if dnet.Spec.Options.Pool.End == "" {
dnet.Spec.Options.Pool.End = (ipam.Int2ip(ipam.Ip2int(getBroadcastAddress(ipnet)) - 1)).String()
}
if !ipnet.Contains(net.ParseIP(dnet.Spec.Options.Pool.Start)) || !ipnet.Contains(net.ParseIP(dnet.Spec.Options.Pool.End)) {
return errors.New("Allocation pool is outside of defined CIDR")
}
if ipam.Ip2int(net.ParseIP(dnet.Spec.Options.Pool.End)) - ipam.Ip2int(net.ParseIP(dnet.Spec.Options.Pool.Start)) <= 0 {
return errors.New("Allocation pool start:" + dnet.Spec.Options.Pool.Start + " is bigger than end:" + dnet.Spec.Options.Pool.End)
}
return nil
}

func getBroadcastAddress(subnet *net.IPNet) (net.IP) {
ip := make(net.IP, len(subnet.IP.To4()))
binary.BigEndian.PutUint32(ip, binary.BigEndian.Uint32(subnet.IP.To4())|^binary.BigEndian.Uint32(net.IP(subnet.Mask).To4()))
return ip
}

func validateVids(dnet *danmtypes.DanmNet) error {
isVlanDefined := (dnet.Spec.Options.Vlan!=0)
isVxlanDefined := (dnet.Spec.Options.Vxlan!=0)
if isVlanDefined && isVxlanDefined {
return errors.New("VLAN ID and VxLAN ID parameters are mutually exclusive")
}
return nil
}

func validateNetworkId(dnet *danmtypes.DanmNet) error {
if dnet.Spec.NetworkID == "" {
return errors.New("Spec.NetworkID mandatory parameter is missing!")
}
if len(dnet.Spec.NetworkID) > MaxNidLength {
return errors.New("Spec.NetworkID cannot be longer than 12 characters (otherwise VLAN and VxLAN host interface creation might fail)!")
}
return nil
}
Loading

0 comments on commit 09d6f9f

Please sign in to comment.