Skip to content

Commit 7d843b7

Browse files
committed
fix: reamap mask
1 parent d03e24a commit 7d843b7

File tree

5 files changed

+295
-18
lines changed

5 files changed

+295
-18
lines changed

main.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2019-2025 The Liqo Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"fmt"
19+
20+
"github.com/liqotech/liqo/apis/networking/v1beta1"
21+
"github.com/liqotech/liqo/pkg/utils/ipam/mapping"
22+
)
23+
24+
func main() {
25+
cfg := v1beta1.Configuration{
26+
Spec: v1beta1.ConfigurationSpec{
27+
Local: &v1beta1.ClusterConfig{
28+
CIDR: v1beta1.ClusterConfigCIDR{
29+
Pod: []v1beta1.CIDR{"10.71.0.0/18"},
30+
External: []v1beta1.CIDR{"10.70.0.0/16"},
31+
},
32+
},
33+
Remote: v1beta1.ClusterConfig{
34+
CIDR: v1beta1.ClusterConfigCIDR{
35+
Pod: []v1beta1.CIDR{"10.71.0.0/18"},
36+
External: []v1beta1.CIDR{"10.70.0.0/16"},
37+
},
38+
},
39+
},
40+
Status: v1beta1.ConfigurationStatus{
41+
Remote: &v1beta1.ClusterConfig{
42+
CIDR: v1beta1.ClusterConfigCIDR{
43+
Pod: []v1beta1.CIDR{"10.71.64.0/18"},
44+
External: []v1beta1.CIDR{"10.68.0.0/16"},
45+
},
46+
},
47+
},
48+
}
49+
50+
result, err := mapping.MapAddressWithConfiguration(&cfg, "10.71.1.53")
51+
if err != nil {
52+
panic(err)
53+
}
54+
55+
fmt.Println(result)
56+
}

pkg/liqoctl/test/network/check/ip.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,7 @@ func forgeIPTarget(ctx context.Context, cl clientctrl.Client, localIPRemapped ma
127127
if !needsRemap {
128128
target = append(target, ipnet.String())
129129
} else {
130-
maskLen, _ := cidrtarget.Mask.Size()
131-
mapping.RemapMask(ipnet, *cidrtarget, maskLen)
130+
mapping.RemapMask(ipnet, *cidrtarget)
132131
target = append(target, ipnet.String())
133132
}
134133
}

pkg/utils/ipam/mapping/ips.go

+74-16
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package mapping
1616

1717
import (
1818
"context"
19+
"encoding/binary"
1920
"fmt"
2021
"net"
2122

@@ -134,7 +135,6 @@ func MapAddress(ctx context.Context, cl client.Client,
134135
func MapAddressWithConfiguration(cfg *networkingv1beta1.Configuration, address string) (string, error) {
135136
var (
136137
podnet, podnetMapped, extnet, extnetMapped *net.IPNet
137-
podNetMaskLen, extNetMaskLen int
138138
err error
139139
)
140140

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

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

168166
paddr := net.ParseIP(address)
169167
if podNeedsRemap && podnet.Contains(paddr) {
170-
return RemapMask(paddr, *podnetMapped, podNetMaskLen).String(), nil
168+
return RemapMask(paddr, *podnetMapped).String(), nil
171169
}
172170
if extNeedsRemap && extnet.Contains(paddr) {
173-
return RemapMask(paddr, *extnetMapped, extNetMaskLen).String(), nil
171+
return RemapMask(paddr, *extnetMapped).String(), nil
174172
}
175173

176174
return address, nil
177175
}
178176

179-
// RemapMask remaps the mask of the address.
180-
// Consider that net.IP is always a slice of 16 bytes (big-endian).
181-
// The mask is a slice of 4 or 16 bytes (big-endian).
182-
func RemapMask(addr net.IP, mask net.IPNet, maskLen int) net.IP {
183-
maskLenBytes := maskLen / 8
184-
for i := 0; i < maskLenBytes; i++ {
185-
// i+(len(addr)-len(mask.IP)) allows to start from the rightmost byte of the address.
186-
// e.g if addr is ipv4 len(addr) = 16, and mask is ipv4 len(mask.IP) = 4, then we start from addr[12].
187-
addr[i+(len(addr)-len(mask.IP))] = mask.IP[i]
188-
}
189-
return addr
177+
// RemapMask take an IP address and a network mask and remap the address to the network.
178+
// This means that the host part of the address is preserved, while the network part is replaced with the one in the mask.
179+
//
180+
// Example:
181+
// addr: 10.1.0.1
182+
// mask: 40.32.0.0/10
183+
// result: 40.1.0.1
184+
// addrBin: 00001010000000010000000000000001
185+
// maskBin: 11111111110000000000000000000000
186+
// netBin : 00101000000000000000000000000000
187+
// hostBin: 00000000000000010000000000000001
188+
// resultBin : 00101000000000010000000000000001
189+
//
190+
// addr: 10.255.1.1
191+
// mask: 78.5.78.143/18
192+
// result: 78.5.65.1
193+
// addrBin: 00001010111111110000000100000001
194+
// maskBin: 11111111111111111100000000000000
195+
// netBin : 01001110000001010100000000000000
196+
// hostBin: 00000000000000000000000100000001
197+
// resultBin: 01001110000001010100000100000001
198+
func RemapMask(addr net.IP, mask net.IPNet) net.IP {
199+
switch len(mask.IP) {
200+
case net.IPv4len:
201+
addr = addr.To4()
202+
203+
// Convert addr,mask,net to binary representation
204+
// Check the comment of the RemapMask function to better understand the values stored in the variables.
205+
addrBin := binary.BigEndian.Uint32(addr)
206+
maskBin := binary.BigEndian.Uint32(mask.Mask)
207+
netBin := binary.BigEndian.Uint32(mask.IP)
208+
209+
// Calculate the host part of the address
210+
// We need to invert the mask and apply the AND operation to the address
211+
// to keep only the host part
212+
hostBin := addrBin & (^maskBin)
213+
214+
// Calculate the result
215+
// We need to apply the OR operation to the network part and the host part
216+
result := netBin | hostBin
217+
218+
resultBytes := make([]byte, 4)
219+
220+
binary.BigEndian.PutUint32(resultBytes, result)
221+
222+
return resultBytes
223+
case net.IPv6len:
224+
// Refer to the IPv4 case for the explanation of the following operations.
225+
addr = addr.To16()
226+
227+
addrBin1 := binary.BigEndian.Uint64(addr[:8])
228+
addrBin2 := binary.BigEndian.Uint64(addr[8:])
229+
maskBin1 := binary.BigEndian.Uint64(mask.Mask[:8])
230+
maskBin2 := binary.BigEndian.Uint64(mask.Mask[8:])
231+
netBin1 := binary.BigEndian.Uint64(mask.IP[:8])
232+
netBin2 := binary.BigEndian.Uint64(mask.IP[8:])
233+
234+
hostBin1 := addrBin1 & (^maskBin1)
235+
hostBin2 := addrBin2 & (^maskBin2)
236+
237+
result1 := netBin1 | hostBin1
238+
result2 := netBin2 | hostBin2
239+
240+
resultBytes := make([]byte, 16)
241+
242+
binary.BigEndian.PutUint64(resultBytes[:8], result1)
243+
binary.BigEndian.PutUint64(resultBytes[8:], result2)
244+
245+
return resultBytes
246+
}
247+
return nil
190248
}
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2019-2025 The Liqo Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package mapping
16+
17+
import (
18+
"testing"
19+
20+
. "github.com/onsi/ginkgo/v2"
21+
. "github.com/onsi/gomega"
22+
)
23+
24+
func TestMapping(t *testing.T) {
25+
RegisterFailHandler(Fail)
26+
RunSpecs(t, "Mapping Suite")
27+
}

pkg/utils/ipam/mapping/ips_test.go

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Copyright 2019-2025 The Liqo Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package mapping
16+
17+
import (
18+
"fmt"
19+
"net"
20+
21+
. "github.com/onsi/ginkgo/v2"
22+
. "github.com/onsi/gomega"
23+
)
24+
25+
var _ = Describe("RemapMask", func() {
26+
DescribeTable("IPv4 remapping",
27+
func(ipStr, cidrStr string, expectedStr string) {
28+
addr := net.ParseIP(ipStr)
29+
Expect(addr).NotTo(BeNil())
30+
31+
_, mask, err := net.ParseCIDR(cidrStr)
32+
Expect(err).NotTo(HaveOccurred()) // Check no error occurs
33+
Expect(mask).NotTo(BeNil())
34+
35+
expected := net.ParseIP(expectedStr).To4()
36+
Expect(expected).NotTo(BeNil())
37+
38+
result := RemapMask(addr, *mask)
39+
Expect(result).To(Equal(expected))
40+
},
41+
Entry("IPv4 remapping", "192.168.1.20", "10.0.0.0/27", "10.0.0.20"),
42+
Entry("IPv4 remapping", "192.168.2.20", "10.0.0.0/27", "10.0.0.20"),
43+
Entry("IPv4 remapping", "192.168.1.50", "10.0.0.0/27", "10.0.0.18"),
44+
Entry("IPv4 remapping", "192.168.2.50", "10.0.0.0/27", "10.0.0.18"),
45+
Entry("IPv4 remapping", "192.168.1.1", "255.255.255.128/25", "255.255.255.129"),
46+
Entry("IPv4 remapping", "172.16.0.1", "255.255.255.128/25", "255.255.255.129"),
47+
Entry("IPv4 remapping", "192.168.1.200", "255.255.255.128/25", "255.255.255.200"),
48+
Entry("IPv4 remapping", "192.168.1.10", "255.255.255.0/24", "255.255.255.10"),
49+
Entry("IPv4 remapping", "192.168.1.100", "255.255.255.0/24", "255.255.255.100"),
50+
Entry("IPv4 remapping", "172.16.0.1", "255.255.240.0/20", "255.255.240.1"),
51+
Entry("IPv4 remapping", "172.16.1.1", "255.255.240.0/20", "255.255.241.1"),
52+
Entry("IPv4 remapping", "192.168.1.10", "255.4.224.0/19", "255.4.225.10"),
53+
Entry("IPv4 remapping", "192.168.2.10", "255.4.224.0/19", "255.4.226.10"),
54+
Entry("IPv4 remapping", "10.0.0.1", "255.255.192.0/18", "255.255.192.1"),
55+
Entry("IPv4 remapping", "10.255.1.1", "78.5.78.143/18", "78.5.65.1"),
56+
Entry("IPv4 remapping", "192.168.1.20", "192.168.0.0/16", "192.168.1.20"),
57+
Entry("IPv4 remapping", "192.168.2.20", "192.168.0.0/16", "192.168.2.20"),
58+
Entry("IPv4 remapping", "172.16.1.1", "172.16.0.0/12", "172.16.1.1"),
59+
Entry("IPv4 remapping", "172.16.2.1", "172.16.0.0/12", "172.16.2.1"),
60+
Entry("IPv4 remapping", "10.0.0.1", "255.192.0.0/10", "255.192.0.1"),
61+
Entry("IPv4 remapping", "10.1.0.1", "40.32.0.0/10", "40.1.0.1"),
62+
Entry("IPv4 remapping", "10.0.0.1", "255.0.0.0/8", "255.0.0.1"),
63+
Entry("IPv4 remapping", "10.0.0.50", "255.0.0.0/8", "255.0.0.50"),
64+
Entry("IPv4 remapping", "10.1.1.1", "20.0.0.0/8", "20.1.1.1"),
65+
Entry("IPv4 remapping", "10.2.1.1", "20.0.0.0/8", "20.2.1.1"),
66+
Entry("IPv4 remapping", "192.168.1.10", "240.0.0.0/4", "240.168.1.10"),
67+
Entry("IPv4 remapping", "192.168.2.10", "240.0.0.0/4", "240.168.2.10"),
68+
Entry("IPv4 remapping", "192.168.1.100", "0.0.0.0/0", "192.168.1.100"),
69+
Entry("IPv4 remapping", "10.0.0.1", "255.255.255.255/32", "255.255.255.255"),
70+
Entry("IPv4 remapping", "172.16.1.0", "172.16.0.0/16", "172.16.1.0"),
71+
Entry("IPv4 remapping", "172.16.1.255", "172.16.0.0/16", "172.16.1.255"),
72+
Entry("IPv4 remapping", "10.10.10.10", "192.168.1.0/24", "192.168.1.10"),
73+
Entry("IPv4 remapping", "192.168.1.255", "10.0.0.0/8", "10.168.1.255"),
74+
Entry("IPv4 remapping", "224.0.0.1", "192.168.1.0/24", "192.168.1.1"),
75+
Entry("IPv4 remapping", "127.0.0.1", "10.0.0.0/8", "10.0.0.1"),
76+
Entry("IPv4 remapping", "192.168.1.1", "10.0.0.0/30", "10.0.0.1"),
77+
Entry("IPv4 remapping", "192.168.1.2", "10.0.0.0/30", "10.0.0.2"),
78+
Entry("IPv4 remapping", "192.168.1.10", "10.0.0.0/29", "10.0.0.2"),
79+
Entry("IPv4 remapping", "172.16.5.10", "192.168.0.0/21", "192.168.5.10"),
80+
Entry("IPv4 remapping", "10.0.5.20", "172.16.0.0/13", "172.16.5.20"),
81+
Entry("IPv4 remapping", "192.168.1.100", "10.0.0.0/27", "10.0.0.4"),
82+
Entry("IPv4 remapping", "172.20.100.5", "192.168.0.0/17", "192.168.100.5"),
83+
Entry("IPv4 remapping", "192.168.3.50", "10.0.0.0/23", "10.0.1.50"),
84+
Entry("IPv4 remapping", "10.1.1.2", "192.168.1.0/30", "192.168.1.2"),
85+
Entry("IPv4 remapping", "192.168.5.33", "172.16.1.0/26", "172.16.1.33"),
86+
)
87+
88+
DescribeTable("IPv6 address remapping",
89+
func(ipStr, cidrStr string, expectedStr string) {
90+
addr := net.ParseIP(ipStr)
91+
Expect(addr).NotTo(BeNil())
92+
93+
_, mask, err := net.ParseCIDR(cidrStr)
94+
Expect(err).NotTo(HaveOccurred())
95+
Expect(mask).NotTo(BeNil())
96+
97+
expected := net.ParseIP(expectedStr).To16()
98+
Expect(expected).NotTo(BeNil())
99+
100+
result := RemapMask(addr, *mask)
101+
102+
fmt.Printf("expected: %s\n", expected)
103+
fmt.Printf("result: %s\n", result)
104+
105+
Expect(result).To(Equal(expected))
106+
},
107+
Entry("IPv6 remapping", "2001:db8::1", "2001:db8::/32", "2001:db8::1"),
108+
Entry("IPv6 remapping", "2001:db8:1::1", "2001:db8::/32", "2001:db8:1::1"),
109+
Entry("IPv6 remapping", "2001:db8::1", "2001:db8:1::/48", "2001:db8:1::1"),
110+
Entry("IPv6 remapping", "2001:db8:2::1", "2001:db8:1::/48", "2001:db8:1::1"),
111+
Entry("IPv6 remapping", "2001:db8::1", "2001:db8:1:2::/64", "2001:db8:1:2::1"),
112+
Entry("IPv6 remapping", "2001:db8:1::1", "2001:db8:1:2::/64", "2001:db8:1:2::1"),
113+
Entry("IPv6 remapping", "2001:db8::1", "2001:db8:1:2:3::/80", "2001:db8:1:2:3::1"),
114+
Entry("IPv6 remapping", "2001:db8:1::1", "2001:db8:1:2:3::/80", "2001:db8:1:2:3::1"),
115+
Entry("IPv6 remapping", "2001:db8::1", "2001:db8:1:2:3:4::/96", "2001:db8:1:2:3:4::1"),
116+
Entry("IPv6 remapping", "2001:db8:1::1", "2001:db8:1:2:3:4::/96", "2001:db8:1:2:3:4::1"),
117+
Entry("IPv6 remapping", "2001:db8::1", "2001:db8:1:2:3:4:5::/112", "2001:db8:1:2:3:4:5:1"),
118+
Entry("IPv6 remapping", "2001:db8:1::1", "2001:db8:1:2:3:4:5::/112", "2001:db8:1:2:3:4:5:1"),
119+
Entry("IPv6 remapping", "2001:db8:1::1", "2001:db8:1:2:3:4:5:6/128", "2001:db8:1:2:3:4:5:6"),
120+
Entry("IPv6 remapping", "2001:db8::1", "2001:db8::/16", "2001:db8::1"),
121+
Entry("IPv6 remapping", "2001:db8:abcd::1", "2001:db8::/56", "2001:db8::1"),
122+
Entry("IPv6 remapping", "2001:db8:abcd:1234::1", "2001:db8::/64", "2001:db8::1"),
123+
Entry("IPv6 remapping", "fc00::1", "fd00::/8", "fd00::1"),
124+
Entry("IPv6 remapping", "fd00:1234::1", "fc00::/7", "fd00:1234::1"),
125+
Entry("IPv6 remapping", "fe80::1", "fd00::/8", "fd80::1"),
126+
Entry("IPv6 remapping", "2001:db8:abcd::1", "2001:db8:1234::/32", "2001:db8:abcd::1"),
127+
Entry("IPv6 remapping", "2001::1", "2002::/16", "2002::1"),
128+
Entry("IPv6 remapping", "2001:db8:abcd::1", "2001:db8::/49", "2001:db8::1"),
129+
Entry("IPv6 remapping", "2001:db8:abcd:1234::1", "2001:db8::/57", "2001:db8:0:34::1"),
130+
Entry("IPv6 remapping", "2001:db8:abcd:1234::1", "2001:db8::/69", "2001:db8::1"),
131+
Entry("IPv6 remapping", "2001:db8:abcd:1234::1", "2001:db8::/73", "2001:db8::1"),
132+
Entry("IPv6 remapping", "2001:db8::1", "2001:db8::/127", "2001:db8::1"),
133+
Entry("IPv6 remapping", "2001:db8:abcd::1", "2001:db8::/37", "2001:db8:3cd::1"),
134+
Entry("IPv6 remapping", "2001:fdb8:abcd::45a3:1", "2001:d2f::/53", "2001:d2f::45a3:1"),
135+
Entry("IPv6 remapping", "2001:db8:abcd:1234::1", "2001:db8::/61", "2001:db8:0:4::1"),
136+
)
137+
})

0 commit comments

Comments
 (0)