Skip to content

pkg/net: add ip arithmetic functions #3815

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
62 changes: 62 additions & 0 deletions pkg/net/ip.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
package net

import (
"errors"
"fmt"
"math/big"
"net/netip"

"cuelang.org/go/cue"
Expand Down Expand Up @@ -242,3 +244,63 @@ func IPString(ip cue.Value) (string, error) {
}
return ipdata.String(), nil
}

func netIPAdd(addr netip.Addr, offset *big.Int) (netip.Addr, error) {
i := big.NewInt(0).SetBytes(addr.AsSlice())
i = i.Add(i, offset)

if i.Sign() < 0 {
return netip.Addr{}, errors.New("IP address arithmetic resulted in out-of-range address (underflow)")
}

b := i.Bytes()
size := addr.BitLen() / 8

if len(b) > size {
return netip.Addr{}, errors.New("IP address arithmetic resulted in out-of-range address (overflow)")
}

if len(b) < size {
b = append(make([]byte, size-len(b), size), b...)
}
addr, _ = netip.AddrFromSlice(b)
return addr, nil
}

// AddIP adds a numerical offset to a given IP address.
// The address can be provided as a string, byte array, or CIDR subnet notation.
Copy link
Member

Choose a reason for hiding this comment

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

It seems a bit odd that we can provide a CIDR string here. What's the use case for that?
For example, net.AddIPOffset("100.0.0.0/8", 1) returns "100.0.0.1/8" but that's not generally useful, as all the bits after the prefix are conventionally zero and ignored.

Copy link
Author

@takonomura takonomura Jun 26, 2025

Choose a reason for hiding this comment

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

My use case is generating network interface configurations, where IP addresses are written in CIDR notation with a prefix length. Since many configuration formats support this form, allowing AddIPOffset to take a CIDR string makes it easier to produce such values.

// It returns the resulting IP address or CIDR subnet notation as a string.
func AddIP(ip cue.Value, offset *big.Int) (string, error) {
prefix, err := netGetIPCIDR(ip)
if err == nil {
addr, err := netIPAdd(prefix.Addr(), offset)
if err != nil {
return "", err
}
return netip.PrefixFrom(addr, prefix.Bits()).String(), nil
}
ipdata := netGetIP(ip)
if !ipdata.IsValid() {
return "", fmt.Errorf("invalid IP %q", ip)
}
addr, err := netIPAdd(ipdata, offset)
if err != nil {
return "", err
}
return addr.String(), nil
}

// AddIPCIDR adds a numerical offset to a given CIDR subnet
// string, returning a CIDR string.
func AddIPCIDR(ip cue.Value, offset *big.Int) (string, error) {
prefix, err := netGetIPCIDR(ip)
if err != nil {
return "", err
}
shifted := big.NewInt(0).Lsh(offset, (uint)(prefix.Addr().BitLen()-prefix.Bits()))
addr, err := netIPAdd(prefix.Addr(), shifted)
if err != nil {
return "", err
}
return netip.PrefixFrom(addr, prefix.Bits()).String(), nil
}
26 changes: 26 additions & 0 deletions pkg/net/pkg.go

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

38 changes: 38 additions & 0 deletions pkg/net/testdata/gen.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@ t31: net.URL & "https://foo.com/bar"
t32: net.AbsURL & "/foo/bar"
t33: net.AbsURL & "https://foo.com/bar"
t34: net.AbsURL & "%"
t35: net.AddIP("127.0.0.1", 1)
t36: net.AddIP("127.0.0.1/8", 2)
t37: net.AddIP("2001:db8::", 1)
t38: net.AddIP("2001:db8::/64", 2)
t39: net.AddIP("invalid ip", 1)
t40: net.AddIP("0.0.0.0", -1)
t41: net.AddIP("255.255.255.255", 1)
t42: net.AddIP("::ffff:127.0.0.1", 1)
t43: net.AddIPCIDR("192.168.0.0/23", 1)
t44: net.AddIPCIDR("2001:db8:1111:2222::/64", 1)
t45: net.AddIPCIDR("192.168.0.0", 1)
t46: net.AddIPCIDR("10.0.0.0/8", -11)
t47: net.AddIPCIDR("255.0.0.0/8", 1)
-- out/net --
Errors:
t25: invalid value "2001:db8::1234567" (does not satisfy net.IPv6):
Expand Down Expand Up @@ -64,6 +77,18 @@ t20: error in call to net.IPCIDR: netip.ParsePrefix("172.16.12.3"): no '/':
t27: invalid value "23.23.23.23" (does not satisfy net.IPv6):
./in.cue:29:6
./in.cue:29:19
t39: error in call to net.AddIP: invalid IP "invalid ip":
./in.cue:41:6
t40: error in call to net.AddIP: IP address arithmetic resulted in out-of-range address (underflow):
./in.cue:42:6
t41: error in call to net.AddIP: IP address arithmetic resulted in out-of-range address (overflow):
./in.cue:43:6
t45: error in call to net.AddIPCIDR: netip.ParsePrefix("192.168.0.0"): no '/':
./in.cue:47:6
t46: error in call to net.AddIPCIDR: IP address arithmetic resulted in out-of-range address (underflow):
./in.cue:48:6
t47: error in call to net.AddIPCIDR: IP address arithmetic resulted in out-of-range address (overflow):
./in.cue:49:6

Result:
t1: "foo.bar."
Expand Down Expand Up @@ -100,3 +125,16 @@ t31: "https://foo.com/bar"
t32: _|_ // t32: invalid value "/foo/bar" (does not satisfy net.AbsURL): t32: error in call to net.AbsURL: URL is not absolute
t33: "https://foo.com/bar"
t34: _|_ // t34: invalid value "%" (does not satisfy net.AbsURL): t34: error in call to net.AbsURL: parse "%": invalid URL escape "%"
t35: "127.0.0.2"
t36: "127.0.0.3/8"
t37: "2001:db8::1"
t38: "2001:db8::2/64"
t39: _|_ // t39: error in call to net.AddIP: invalid IP "invalid ip"
t40: _|_ // t40: error in call to net.AddIP: IP address arithmetic resulted in out-of-range address (underflow)
t41: _|_ // t41: error in call to net.AddIP: IP address arithmetic resulted in out-of-range address (overflow)
t42: "::ffff:127.0.0.2"
t43: "192.168.2.0/23"
t44: "2001:db8:1111:2223::/64"
t45: _|_ // t45: error in call to net.AddIPCIDR: netip.ParsePrefix("192.168.0.0"): no '/'
t46: _|_ // t46: error in call to net.AddIPCIDR: IP address arithmetic resulted in out-of-range address (underflow)
t47: _|_ // t47: error in call to net.AddIPCIDR: IP address arithmetic resulted in out-of-range address (overflow)