summaryrefslogtreecommitdiffstats
path: root/src/go/collectors/go.d.plugin/pkg/iprange/parse.go
blob: 3471702a11bca3554c4bffffec69db16b7a26348 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// SPDX-License-Identifier: GPL-3.0-or-later

package iprange

import (
	"bytes"
	"fmt"
	"net"
	"regexp"
	"strings"

	"github.com/apparentlymart/go-cidr/cidr"
)

// ParseRanges parses s as a space separated list of IP Ranges, returning the result and an error if any.
// IP Range can be in IPv4 address ("192.0.2.1"), IPv4 range ("192.0.2.0-192.0.2.10")
// IPv4 CIDR ("192.0.2.0/24"), IPv4 subnet mask ("192.0.2.0/255.255.255.0"),
// IPv6 address ("2001:db8::1"), IPv6 range ("2001:db8::-2001:db8::10"),
// or IPv6 CIDR ("2001:db8::/64") form.
// IPv4 CIDR, IPv4 subnet mask and IPv6 CIDR ranges don't include network and broadcast addresses.
func ParseRanges(s string) ([]Range, error) {
	parts := strings.Fields(s)
	if len(parts) == 0 {
		return nil, nil
	}

	var ranges []Range
	for _, v := range parts {
		r, err := ParseRange(v)
		if err != nil {
			return nil, err
		}

		if r != nil {
			ranges = append(ranges, r)
		}
	}
	return ranges, nil
}

var (
	reRange      = regexp.MustCompile("^[0-9a-f.:-]+$")           // addr | addr-addr
	reCIDR       = regexp.MustCompile("^[0-9a-f.:]+/[0-9]{1,3}$") // addr/prefix_length
	reSubnetMask = regexp.MustCompile("^[0-9.]+/[0-9.]{7,}$")     // v4_addr/mask
)

// ParseRange parses s as an IP Range, returning the result and an error if any.
// The string s can be in IPv4 address ("192.0.2.1"), IPv4 range ("192.0.2.0-192.0.2.10")
// IPv4 CIDR ("192.0.2.0/24"), IPv4 subnet mask ("192.0.2.0/255.255.255.0"),
// IPv6 address ("2001:db8::1"), IPv6 range ("2001:db8::-2001:db8::10"),
// or IPv6 CIDR ("2001:db8::/64") form.
// IPv4 CIDR, IPv4 subnet mask and IPv6 CIDR ranges don't include network and broadcast addresses.
func ParseRange(s string) (Range, error) {
	s = strings.ToLower(s)
	if s == "" {
		return nil, nil
	}

	var r Range
	switch {
	case reRange.MatchString(s):
		r = parseRange(s)
	case reCIDR.MatchString(s):
		r = parseCIDR(s)
	case reSubnetMask.MatchString(s):
		r = parseSubnetMask(s)
	}

	if r == nil {
		return nil, fmt.Errorf("ip range (%s) invalid syntax", s)
	}
	return r, nil
}

func parseRange(s string) Range {
	var start, end net.IP
	if idx := strings.IndexByte(s, '-'); idx != -1 {
		start, end = net.ParseIP(s[:idx]), net.ParseIP(s[idx+1:])
	} else {
		start, end = net.ParseIP(s), net.ParseIP(s)
	}

	return New(start, end)
}

func parseCIDR(s string) Range {
	ip, network, err := net.ParseCIDR(s)
	if err != nil {
		return nil
	}

	start, end := cidr.AddressRange(network)
	prefixLen, _ := network.Mask.Size()

	if isV4IP(ip) && prefixLen < 31 || isV6IP(ip) && prefixLen < 127 {
		start = cidr.Inc(start)
		end = cidr.Dec(end)
	}

	return parseRange(fmt.Sprintf("%s-%s", start, end))
}

func parseSubnetMask(s string) Range {
	idx := strings.LastIndexByte(s, '/')
	if idx == -1 {
		return nil
	}

	address, mask := s[:idx], s[idx+1:]

	ip := net.ParseIP(mask).To4()
	if ip == nil {
		return nil
	}

	prefixLen, bits := net.IPv4Mask(ip[0], ip[1], ip[2], ip[3]).Size()
	if prefixLen+bits == 0 {
		return nil
	}

	return parseCIDR(fmt.Sprintf("%s/%d", address, prefixLen))
}

func isV4RangeValid(start, end net.IP) bool {
	return isV4IP(start) && isV4IP(end) && bytes.Compare(end, start) >= 0
}

func isV6RangeValid(start, end net.IP) bool {
	return isV6IP(start) && isV6IP(end) && bytes.Compare(end, start) >= 0
}

func isV4IP(ip net.IP) bool {
	return ip.To4() != nil
}

func isV6IP(ip net.IP) bool {
	return !isV4IP(ip) && ip.To16() != nil
}