Skip to content
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
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ env:
REPO_NAME: ${{ github.event.repository.name }}
OUT_FOLDER: ${{ github.event.repository.name }}
EXECUTABLE_NAME: ${{ github.event.repository.name }}
GO_VERSION: '1.23'
GO_VERSION: '1.24'

permissions:
contents: write
Expand Down Expand Up @@ -47,7 +47,7 @@ jobs:
go-version: ${{ env.GO_VERSION }}

- name: Build ${{ env.OS_GOOS }}/${{ env.OS_GOARCH }}
run: GOOS=${{ env.OS_GOOS }} GOARCH=${{ env.OS_GOARCH }} go build -o ${{ env.EXECUTABLE_NAME }}.exe
run: GOOS=${{ env.OS_GOOS }} GOARCH=${{ env.OS_GOARCH }} go build -o ${{ env.EXECUTABLE_NAME }}.exe -ldflags "-X main.version=${{ env.VERSION }}"
shell: bash

- name: Create archive
Expand Down
25 changes: 25 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Test

on:
pull_request:
branches: [ "main" ]

env:
GO_VERSION: '1.24'

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}

- name: Build
run: go build -v ./...

- name: Test
run: go test -v ./...
8 changes: 1 addition & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,4 @@ module cider

go 1.23.2

require github.com/urfave/cli/v2 v2.27.5

require (
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
)
require github.com/urfave/cli/v3 v3.0.0-beta1
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg=
github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
172 changes: 172 additions & 0 deletions internal/cidr/cidr_block.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package cidr

import (
"cider/internal/list"
"cider/internal/utils"
"fmt"
"math"
"slices"
"strconv"
"strings"
)

const (
INT_SIZE = 32
)

type CIDRBlock struct {
Network string
HostPortion int
}

func NewBlock(network string) *CIDRBlock {
networkAndHostPortion := strings.Split(network, "/")

networkPortion := networkAndHostPortion[0]
hostPortion := networkAndHostPortion[1]

return &CIDRBlock{
Network: networkPortion,
HostPortion: must(strconv.Atoi(hostPortion)),
}
}

func (b *CIDRBlock) Subnet(sizes []int) ([]string, error) {
// sort subnets largest to smallest to prevent fragmentation
slices.Sort(sizes)

next := b
subnets := []string{}
for _, size := range sizes {
subnetBlock := NewBlock(fmt.Sprintf("%s/%v", next.Network, size))

if !b.Contains(subnetBlock.Network) {
return nil, fmt.Errorf("invalid configuration: subnet %s/%v is outside provided network range %s/%v", next.Network, size, b.Network, b.HostPortion)
}

subnets = append(subnets, fmt.Sprintf("%s/%v", subnetBlock.Network, subnetBlock.HostPortion))

next = NewBlock(fmt.Sprintf("%s/%v", subnetBlock.StartAddressOfNextBlock(), size))
}

return subnets, nil
}

// https://stackoverflow.com/questions/9622967/how-to-see-if-an-ip-address-belongs-inside-of-a-range-of-ips-using-cidr-notation
func (b *CIDRBlock) Contains(ip string) bool {
IP_addr := ipToDecimal(ip)
CIDR_addr := ipToDecimal(b.Network)
CIDR_mask := -1 << (INT_SIZE - b.HostPortion)

return (IP_addr & CIDR_mask) == (CIDR_addr & CIDR_mask)
}

// http://www.aboutmyip.com/AboutMyXApp/IP2Integer.jsp
func ipToDecimal(ip string) int {
parts := strings.Split(ip, ".")

base := 10
octet1 := must(strconv.ParseInt(parts[0], base, INT_SIZE))
octet2 := must(strconv.ParseInt(parts[1], base, INT_SIZE))
octet3 := must(strconv.ParseInt(parts[2], base, INT_SIZE))
octet4 := must(strconv.ParseInt(parts[3], base, INT_SIZE))

return int((octet1 * 16777216) + (octet2 * 65536) + (octet3 * 256) + octet4)
}

func (b *CIDRBlock) NetworkPortionBinary() string {
octets := strings.Split(b.Network, ".")
octets = list.Map(octets, toBin)

return fmt.Sprintf("%s.%s.%s.%s", octets[0], octets[1], octets[2], octets[3])
}

func toBin(s string) string {
asInt := must(strconv.ParseInt(s, 10, INT_SIZE))
asBinaryString := strconv.FormatInt(asInt, 2)
paddedBynaryString := utils.PadLeft(asBinaryString, '0', 8)
return paddedBynaryString
}

func (b *CIDRBlock) SubnetMask() string {
ones := strings.Repeat("1", b.HostPortion)
zeroes := strings.Repeat("0", INT_SIZE-b.HostPortion)

mask := ones + zeroes

octets := stringToOctets(mask)

return fmt.Sprintf("%v.%v.%v.%v", octets[0], octets[1], octets[2], octets[3])
}

func (b *CIDRBlock) AvailableHosts() uint {
numAddresses := math.Pow(2, float64(INT_SIZE)-float64(b.HostPortion))

return uint(numAddresses)
}

func (b *CIDRBlock) AvailableAzureHosts() string {
// Azure reserves the first four addresses and the last address, for a total of five IP addresses within each subnet
// https://learn.microsoft.com/en-us/azure/virtual-network/virtual-networks-faq#are-there-any-restrictions-on-using-ip-addresses-within-these-subnets
if b.AvailableHosts() >= 5 {
return fmt.Sprintf("%v", b.AvailableHosts()-5)
}

return "N/A"
}

func (b *CIDRBlock) StartAddressOfNextBlock() string {
octets := strings.Split(b.BroadcastAddress(), ".")
octets = list.Map(octets, toBin)
binStr := strings.Join(octets, "")

next := must(strconv.ParseInt(binStr, 2, INT_SIZE)) + 1

asBinaryString := strconv.FormatInt(next, 2)
asBinaryString = utils.PadLeft(asBinaryString, '0', 32)

octetsInt := stringToOctets(asBinaryString)

return fmt.Sprintf("%v.%v.%v.%v", octetsInt[0], octetsInt[1], octetsInt[2], octetsInt[3])
}

func (b *CIDRBlock) NetworkAddress() string {
ipBin := strings.ReplaceAll(b.NetworkPortionBinary(), ".", "")[0:b.HostPortion]

broadcast := ipBin + strings.Repeat("0", INT_SIZE-b.HostPortion)

octets := stringToOctets(broadcast)

return fmt.Sprintf("%v.%v.%v.%v", octets[0], octets[1], octets[2], octets[3])
}

func (b *CIDRBlock) BroadcastAddress() string {
// https://stackoverflow.com/questions/1470792/how-to-calculate-the-ip-range-when-the-ip-address-and-the-netmask-is-given
ipBin := strings.ReplaceAll(b.NetworkPortionBinary(), ".", "")[0:b.HostPortion]

broadcast := ipBin + strings.Repeat("1", INT_SIZE-b.HostPortion)

octets := stringToOctets(broadcast)

return fmt.Sprintf("%v.%v.%v.%v", octets[0], octets[1], octets[2], octets[3])
}

func stringToOctets(ipString string) []int64 {
octets := make([]int64, 4)

base := 2
octets[0] = must(strconv.ParseInt(ipString[0:8], base, INT_SIZE))
octets[1] = must(strconv.ParseInt(ipString[8:16], base, INT_SIZE))
octets[2] = must(strconv.ParseInt(ipString[16:24], base, INT_SIZE))
octets[3] = must(strconv.ParseInt(ipString[24:32], base, INT_SIZE))

return octets
}

func must[T any](x T, e error) T {
if e != nil {
panic(e)
}

return x
}
Loading