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

[Network] remap mask #2924

Merged
merged 1 commit into from
Feb 10, 2025
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
3 changes: 1 addition & 2 deletions pkg/liqoctl/test/network/check/ip.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,7 @@ func forgeIPTarget(ctx context.Context, cl clientctrl.Client, localIPRemapped ma
if !needsRemap {
target = append(target, ipnet.String())
} else {
maskLen, _ := cidrtarget.Mask.Size()
mapping.RemapMask(ipnet, *cidrtarget, maskLen)
ipnet = mapping.RemapMask(ipnet, *cidrtarget)
target = append(target, ipnet.String())
}
}
Expand Down
90 changes: 74 additions & 16 deletions pkg/utils/ipam/mapping/ips.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package mapping

import (
"context"
"encoding/binary"
"fmt"
"net"

Expand Down Expand Up @@ -134,7 +135,6 @@ func MapAddress(ctx context.Context, cl client.Client,
func MapAddressWithConfiguration(cfg *networkingv1beta1.Configuration, address string) (string, error) {
var (
podnet, podnetMapped, extnet, extnetMapped *net.IPNet
podNetMaskLen, extNetMaskLen int
err error
)

Expand All @@ -150,7 +150,6 @@ func MapAddressWithConfiguration(cfg *networkingv1beta1.Configuration, address s
if err != nil {
return "", err
}
podNetMaskLen, _ = podnetMapped.Mask.Size()
}

_, extnet, err = net.ParseCIDR(cidrutils.GetPrimary(cfg.Spec.Remote.CIDR.External).String())
Expand All @@ -162,29 +161,88 @@ func MapAddressWithConfiguration(cfg *networkingv1beta1.Configuration, address s
if err != nil {
return "", err
}
extNetMaskLen, _ = extnetMapped.Mask.Size()
}

paddr := net.ParseIP(address)
if podNeedsRemap && podnet.Contains(paddr) {
return RemapMask(paddr, *podnetMapped, podNetMaskLen).String(), nil
return RemapMask(paddr, *podnetMapped).String(), nil
}
if extNeedsRemap && extnet.Contains(paddr) {
return RemapMask(paddr, *extnetMapped, extNetMaskLen).String(), nil
return RemapMask(paddr, *extnetMapped).String(), nil
}

return address, nil
}

// RemapMask remaps the mask of the address.
// Consider that net.IP is always a slice of 16 bytes (big-endian).
// The mask is a slice of 4 or 16 bytes (big-endian).
func RemapMask(addr net.IP, mask net.IPNet, maskLen int) net.IP {
maskLenBytes := maskLen / 8
for i := 0; i < maskLenBytes; i++ {
// i+(len(addr)-len(mask.IP)) allows to start from the rightmost byte of the address.
// e.g if addr is ipv4 len(addr) = 16, and mask is ipv4 len(mask.IP) = 4, then we start from addr[12].
addr[i+(len(addr)-len(mask.IP))] = mask.IP[i]
}
return addr
// RemapMask take an IP address and a network mask and remap the address to the network.
// This means that the host part of the address is preserved, while the network part is replaced with the one in the mask.
//
// Example:
// addr: 10.1.0.1
// mask: 40.32.0.0/10
// result: 40.1.0.1
// addrBin: 00001010000000010000000000000001
// maskBin: 11111111110000000000000000000000
// netBin : 00101000000000000000000000000000
// hostBin: 00000000000000010000000000000001
// resultBin : 00101000000000010000000000000001
//
// addr: 10.255.1.1
// mask: 78.5.78.143/18
// result: 78.5.65.1
// addrBin: 00001010111111110000000100000001
// maskBin: 11111111111111111100000000000000
// netBin : 01001110000001010100000000000000
// hostBin: 00000000000000000000000100000001
// resultBin: 01001110000001010100000100000001 // nolint: godot // this comment must not end with a period.
func RemapMask(addr net.IP, mask net.IPNet) net.IP {
switch len(mask.IP) {
case net.IPv4len:
addr = addr.To4()

// Convert addr,mask,net to binary representation
// Check the comment of the RemapMask function to better understand the values stored in the variables.
addrBin := binary.BigEndian.Uint32(addr)
maskBin := binary.BigEndian.Uint32(mask.Mask)
netBin := binary.BigEndian.Uint32(mask.IP)

// Calculate the host part of the address
// We need to invert the mask and apply the AND operation to the address
// to keep only the host part
hostBin := addrBin & (^maskBin)

// Calculate the result
// We need to apply the OR operation to the network part and the host part
result := netBin | hostBin

resultBytes := make([]byte, 4)

binary.BigEndian.PutUint32(resultBytes, result)

return resultBytes
case net.IPv6len:
// Refer to the IPv4 case for the explanation of the following operations.
addr = addr.To16()

addrBin1 := binary.BigEndian.Uint64(addr[:8])
addrBin2 := binary.BigEndian.Uint64(addr[8:])
maskBin1 := binary.BigEndian.Uint64(mask.Mask[:8])
maskBin2 := binary.BigEndian.Uint64(mask.Mask[8:])
netBin1 := binary.BigEndian.Uint64(mask.IP[:8])
netBin2 := binary.BigEndian.Uint64(mask.IP[8:])

hostBin1 := addrBin1 & (^maskBin1)
hostBin2 := addrBin2 & (^maskBin2)

result1 := netBin1 | hostBin1
result2 := netBin2 | hostBin2

resultBytes := make([]byte, 16)

binary.BigEndian.PutUint64(resultBytes[:8], result1)
binary.BigEndian.PutUint64(resultBytes[8:], result2)

return resultBytes
}
return nil
}
27 changes: 27 additions & 0 deletions pkg/utils/ipam/mapping/ips_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2019-2025 The Liqo Authors
//
// 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 mapping

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestMapping(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Mapping Suite")
}
137 changes: 137 additions & 0 deletions pkg/utils/ipam/mapping/ips_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright 2019-2025 The Liqo Authors
//
// 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 mapping

import (
"fmt"
"net"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("RemapMask", func() {
DescribeTable("IPv4 remapping",
func(ipStr, cidrStr string, expectedStr string) {
addr := net.ParseIP(ipStr)
Expect(addr).NotTo(BeNil())

_, mask, err := net.ParseCIDR(cidrStr)
Expect(err).NotTo(HaveOccurred()) // Check no error occurs
Expect(mask).NotTo(BeNil())

expected := net.ParseIP(expectedStr).To4()
Expect(expected).NotTo(BeNil())

result := RemapMask(addr, *mask)
Expect(result).To(Equal(expected))
},
Entry("IPv4 remapping", "192.168.1.20", "10.0.0.0/27", "10.0.0.20"),
Entry("IPv4 remapping", "192.168.2.20", "10.0.0.0/27", "10.0.0.20"),
Entry("IPv4 remapping", "192.168.1.50", "10.0.0.0/27", "10.0.0.18"),
Entry("IPv4 remapping", "192.168.2.50", "10.0.0.0/27", "10.0.0.18"),
Entry("IPv4 remapping", "192.168.1.1", "255.255.255.128/25", "255.255.255.129"),
Entry("IPv4 remapping", "172.16.0.1", "255.255.255.128/25", "255.255.255.129"),
Entry("IPv4 remapping", "192.168.1.200", "255.255.255.128/25", "255.255.255.200"),
Entry("IPv4 remapping", "192.168.1.10", "255.255.255.0/24", "255.255.255.10"),
Entry("IPv4 remapping", "192.168.1.100", "255.255.255.0/24", "255.255.255.100"),
Entry("IPv4 remapping", "172.16.0.1", "255.255.240.0/20", "255.255.240.1"),
Entry("IPv4 remapping", "172.16.1.1", "255.255.240.0/20", "255.255.241.1"),
Entry("IPv4 remapping", "192.168.1.10", "255.4.224.0/19", "255.4.225.10"),
Entry("IPv4 remapping", "192.168.2.10", "255.4.224.0/19", "255.4.226.10"),
Entry("IPv4 remapping", "10.0.0.1", "255.255.192.0/18", "255.255.192.1"),
Entry("IPv4 remapping", "10.255.1.1", "78.5.78.143/18", "78.5.65.1"),
Entry("IPv4 remapping", "192.168.1.20", "192.168.0.0/16", "192.168.1.20"),
Entry("IPv4 remapping", "192.168.2.20", "192.168.0.0/16", "192.168.2.20"),
Entry("IPv4 remapping", "172.16.1.1", "172.16.0.0/12", "172.16.1.1"),
Entry("IPv4 remapping", "172.16.2.1", "172.16.0.0/12", "172.16.2.1"),
Entry("IPv4 remapping", "10.0.0.1", "255.192.0.0/10", "255.192.0.1"),
Entry("IPv4 remapping", "10.1.0.1", "40.32.0.0/10", "40.1.0.1"),
Entry("IPv4 remapping", "10.0.0.1", "255.0.0.0/8", "255.0.0.1"),
Entry("IPv4 remapping", "10.0.0.50", "255.0.0.0/8", "255.0.0.50"),
Entry("IPv4 remapping", "10.1.1.1", "20.0.0.0/8", "20.1.1.1"),
Entry("IPv4 remapping", "10.2.1.1", "20.0.0.0/8", "20.2.1.1"),
Entry("IPv4 remapping", "192.168.1.10", "240.0.0.0/4", "240.168.1.10"),
Entry("IPv4 remapping", "192.168.2.10", "240.0.0.0/4", "240.168.2.10"),
Entry("IPv4 remapping", "192.168.1.100", "0.0.0.0/0", "192.168.1.100"),
Entry("IPv4 remapping", "10.0.0.1", "255.255.255.255/32", "255.255.255.255"),
Entry("IPv4 remapping", "172.16.1.0", "172.16.0.0/16", "172.16.1.0"),
Entry("IPv4 remapping", "172.16.1.255", "172.16.0.0/16", "172.16.1.255"),
Entry("IPv4 remapping", "10.10.10.10", "192.168.1.0/24", "192.168.1.10"),
Entry("IPv4 remapping", "192.168.1.255", "10.0.0.0/8", "10.168.1.255"),
Entry("IPv4 remapping", "224.0.0.1", "192.168.1.0/24", "192.168.1.1"),
Entry("IPv4 remapping", "127.0.0.1", "10.0.0.0/8", "10.0.0.1"),
Entry("IPv4 remapping", "192.168.1.1", "10.0.0.0/30", "10.0.0.1"),
Entry("IPv4 remapping", "192.168.1.2", "10.0.0.0/30", "10.0.0.2"),
Entry("IPv4 remapping", "192.168.1.10", "10.0.0.0/29", "10.0.0.2"),
Entry("IPv4 remapping", "172.16.5.10", "192.168.0.0/21", "192.168.5.10"),
Entry("IPv4 remapping", "10.0.5.20", "172.16.0.0/13", "172.16.5.20"),
Entry("IPv4 remapping", "192.168.1.100", "10.0.0.0/27", "10.0.0.4"),
Entry("IPv4 remapping", "172.20.100.5", "192.168.0.0/17", "192.168.100.5"),
Entry("IPv4 remapping", "192.168.3.50", "10.0.0.0/23", "10.0.1.50"),
Entry("IPv4 remapping", "10.1.1.2", "192.168.1.0/30", "192.168.1.2"),
Entry("IPv4 remapping", "192.168.5.33", "172.16.1.0/26", "172.16.1.33"),
)

DescribeTable("IPv6 address remapping",
func(ipStr, cidrStr string, expectedStr string) {
addr := net.ParseIP(ipStr)
Expect(addr).NotTo(BeNil())

_, mask, err := net.ParseCIDR(cidrStr)
Expect(err).NotTo(HaveOccurred())
Expect(mask).NotTo(BeNil())

expected := net.ParseIP(expectedStr).To16()
Expect(expected).NotTo(BeNil())

result := RemapMask(addr, *mask)

fmt.Printf("expected: %s\n", expected)
fmt.Printf("result: %s\n", result)

Expect(result).To(Equal(expected))
},
Entry("IPv6 remapping", "2001:db8::1", "2001:db8::/32", "2001:db8::1"),
Entry("IPv6 remapping", "2001:db8:1::1", "2001:db8::/32", "2001:db8:1::1"),
Entry("IPv6 remapping", "2001:db8::1", "2001:db8:1::/48", "2001:db8:1::1"),
Entry("IPv6 remapping", "2001:db8:2::1", "2001:db8:1::/48", "2001:db8:1::1"),
Entry("IPv6 remapping", "2001:db8::1", "2001:db8:1:2::/64", "2001:db8:1:2::1"),
Entry("IPv6 remapping", "2001:db8:1::1", "2001:db8:1:2::/64", "2001:db8:1:2::1"),
Entry("IPv6 remapping", "2001:db8::1", "2001:db8:1:2:3::/80", "2001:db8:1:2:3::1"),
Entry("IPv6 remapping", "2001:db8:1::1", "2001:db8:1:2:3::/80", "2001:db8:1:2:3::1"),
Entry("IPv6 remapping", "2001:db8::1", "2001:db8:1:2:3:4::/96", "2001:db8:1:2:3:4::1"),
Entry("IPv6 remapping", "2001:db8:1::1", "2001:db8:1:2:3:4::/96", "2001:db8:1:2:3:4::1"),
Entry("IPv6 remapping", "2001:db8::1", "2001:db8:1:2:3:4:5::/112", "2001:db8:1:2:3:4:5:1"),
Entry("IPv6 remapping", "2001:db8:1::1", "2001:db8:1:2:3:4:5::/112", "2001:db8:1:2:3:4:5:1"),
Entry("IPv6 remapping", "2001:db8:1::1", "2001:db8:1:2:3:4:5:6/128", "2001:db8:1:2:3:4:5:6"),
Entry("IPv6 remapping", "2001:db8::1", "2001:db8::/16", "2001:db8::1"),
Entry("IPv6 remapping", "2001:db8:abcd::1", "2001:db8::/56", "2001:db8::1"),
Entry("IPv6 remapping", "2001:db8:abcd:1234::1", "2001:db8::/64", "2001:db8::1"),
Entry("IPv6 remapping", "fc00::1", "fd00::/8", "fd00::1"),
Entry("IPv6 remapping", "fd00:1234::1", "fc00::/7", "fd00:1234::1"),
Entry("IPv6 remapping", "fe80::1", "fd00::/8", "fd80::1"),
Entry("IPv6 remapping", "2001:db8:abcd::1", "2001:db8:1234::/32", "2001:db8:abcd::1"),
Entry("IPv6 remapping", "2001::1", "2002::/16", "2002::1"),
Entry("IPv6 remapping", "2001:db8:abcd::1", "2001:db8::/49", "2001:db8::1"),
Entry("IPv6 remapping", "2001:db8:abcd:1234::1", "2001:db8::/57", "2001:db8:0:34::1"),
Entry("IPv6 remapping", "2001:db8:abcd:1234::1", "2001:db8::/69", "2001:db8::1"),
Entry("IPv6 remapping", "2001:db8:abcd:1234::1", "2001:db8::/73", "2001:db8::1"),
Entry("IPv6 remapping", "2001:db8::1", "2001:db8::/127", "2001:db8::1"),
Entry("IPv6 remapping", "2001:db8:abcd::1", "2001:db8::/37", "2001:db8:3cd::1"),
Entry("IPv6 remapping", "2001:fdb8:abcd::45a3:1", "2001:d2f::/53", "2001:d2f::45a3:1"),
Entry("IPv6 remapping", "2001:db8:abcd:1234::1", "2001:db8::/61", "2001:db8:0:4::1"),
)
})