├── .travis.yml ├── LICENSE ├── README.md ├── funcs.go ├── go.mod ├── go.sum ├── ip.y ├── iprange.go ├── iprange_test.go ├── lex.go ├── sortip.go ├── y.go └── y.output /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.8" 5 | - "1.9" 6 | 7 | script: 8 | - "go test -v" 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 José Nieto , Arturo Vergara 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iprange 2 | 3 | [![GoDoc](https://godoc.org/github.com/malfunkt/iprange?status.svg)](https://godoc.org/github.com/malfunkt/iprange) 4 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg)]() 5 | [![Build Status](https://travis-ci.org/malfunkt/iprange.svg?branch=master)](https://travis-ci.org/malfunkt/iprange) 6 | 7 | `iprange` is a library you can use to parse IPv4 addresses from a string in the `nmap` format. 8 | 9 | It takes a string, and returns a list of `Min`-`Max` pairs, which can then be expanded and normalized automatically by the package. 10 | 11 | ## Supported Formats 12 | 13 | `iprange` supports the following formats: 14 | 15 | * `10.0.0.1` 16 | * `10.0.0.0/24` 17 | * `10.0.0.*` 18 | * `10.0.0.1-10` 19 | * `10.0.0.1, 10.0.0.5-10, 192.168.1.*, 192.168.10.0/24` 20 | 21 | ## Usage 22 | 23 | ```go 24 | package main 25 | 26 | import ( 27 | "log" 28 | 29 | "github.com/malfunkt/iprange" 30 | ) 31 | 32 | func main() { 33 | list, err := iprange.ParseList("10.0.0.1, 10.0.0.5-10, 192.168.1.*, 192.168.10.0/24") 34 | if err != nil { 35 | log.Printf("error: %s", err) 36 | } 37 | log.Printf("%+v", list) 38 | 39 | rng := list.Expand() 40 | log.Printf("%s", rng) 41 | } 42 | ``` 43 | -------------------------------------------------------------------------------- /funcs.go: -------------------------------------------------------------------------------- 1 | package iprange 2 | 3 | import ( 4 | "encoding/binary" 5 | "net" 6 | "sort" 7 | ) 8 | 9 | func streamRange(lower, upper net.IP) chan net.IP { 10 | ipchan := make(chan net.IP, 1) 11 | 12 | rangeMask := net.IP([]byte{ 13 | upper[0] - lower[0], 14 | upper[1] - lower[1], 15 | upper[2] - lower[2], 16 | upper[3] - lower[3], 17 | }) 18 | 19 | go func() { 20 | defer close(ipchan) 21 | 22 | lower32 := binary.BigEndian.Uint32([]byte(lower)) 23 | upper32 := binary.BigEndian.Uint32([]byte(upper)) 24 | diff := upper32 - lower32 25 | 26 | if diff < 0 { 27 | panic("Lower address is actually higher than upper address.") 28 | } 29 | 30 | mask := net.IP([]byte{0, 0, 0, 0}) 31 | 32 | for { 33 | ipchan <- net.IP([]byte{ 34 | lower[0] + mask[0], 35 | lower[1] + mask[1], 36 | lower[2] + mask[2], 37 | lower[3] + mask[3], 38 | }) 39 | 40 | if mask.Equal(rangeMask) { 41 | break 42 | } 43 | 44 | for i := 3; i >= 0; i-- { 45 | if rangeMask[i] > 0 { 46 | if mask[i] < rangeMask[i] { 47 | mask[i] = mask[i] + 1 48 | break 49 | } else { 50 | mask[i] = mask[i] % rangeMask[i] 51 | if i < 1 { 52 | break 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | }() 60 | 61 | return ipchan 62 | } 63 | 64 | // Expand expands an address with a mask taken from a stream 65 | func (r *AddressRange) Expand() []net.IP { 66 | ips := []net.IP{} 67 | for ip := range streamRange(r.Min, r.Max) { 68 | ips = append(ips, ip) 69 | } 70 | return ips 71 | } 72 | 73 | // Expand expands and normalizes a set of parsed target specifications 74 | func (l AddressRangeList) Expand() []net.IP { 75 | var res []net.IP 76 | for i := range l { 77 | res = append(res, l[i].Expand()...) 78 | } 79 | return normalize(res) 80 | } 81 | 82 | func normalize(src []net.IP) []net.IP { 83 | sort.Sort(asc(src)) 84 | dst := make([]net.IP, 1, len(src)) 85 | dst[0] = src[0] 86 | for i := range src { 87 | if !dst[len(dst)-1].Equal(src[i]) { 88 | dst = append(dst, src[i]) 89 | } 90 | } 91 | return dst 92 | } 93 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/malfunkt/iprange 2 | 3 | go 1.23.3 4 | 5 | require ( 6 | github.com/pkg/errors v0.9.1 7 | github.com/stretchr/testify v1.10.0 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.1 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 4 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 8 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /ip.y: -------------------------------------------------------------------------------- 1 | %{ 2 | 3 | package iprange 4 | 5 | import ( 6 | "encoding/binary" 7 | "net" 8 | 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | type AddressRangeList []AddressRange 13 | 14 | type AddressRange struct { 15 | Min net.IP 16 | Max net.IP 17 | } 18 | 19 | type octetRange struct { 20 | min byte 21 | max byte 22 | } 23 | 24 | const ( 25 | ipV4MaskLength = 32 26 | maxMaskValue = ipV4MaskLength 27 | ) 28 | 29 | %} 30 | 31 | %union { 32 | byteValue byte 33 | octRange octetRange 34 | addrRange AddressRange 35 | result AddressRangeList 36 | ipMask net.IPMask 37 | } 38 | 39 | %token NUM 40 | %type mask 41 | %type address target 42 | %type term octet_range 43 | %type result 44 | 45 | %% 46 | 47 | result: target 48 | { 49 | $$ = append($$, $1) 50 | iplex.(*ipLex).output = $$ 51 | } 52 | | result comma target 53 | { 54 | $$ = append($1, $3) 55 | iplex.(*ipLex).output = $$ 56 | } 57 | 58 | comma: ',' | ',' ' ' 59 | 60 | target: address '/' mask 61 | { 62 | mask := $3 63 | min := $1.Min.Mask(mask) 64 | maxInt := binary.BigEndian.Uint32([]byte(min)) + 65 | 0xffffffff - 66 | binary.BigEndian.Uint32([]byte(mask)) 67 | maxBytes := make([]byte, 4) 68 | binary.BigEndian.PutUint32(maxBytes, maxInt) 69 | maxBytes = maxBytes[len(maxBytes)-4:] 70 | max := net.IP(maxBytes) 71 | $$ = AddressRange { 72 | Min: min.To4(), 73 | Max: max.To4(), 74 | } 75 | } 76 | | address 77 | { 78 | $$ = $1 79 | } 80 | 81 | address: term '.' term '.' term '.' term 82 | { 83 | $$ = AddressRange { 84 | Min: net.IPv4($1.min, $3.min, $5.min, $7.min).To4(), 85 | Max: net.IPv4($1.max, $3.max, $5.max, $7.max).To4(), 86 | } 87 | } 88 | 89 | term: NUM { $$ = octetRange { $1, $1 } } 90 | | '*' { $$ = octetRange { 0, 255 } } 91 | | octet_range { $$ = $1 } 92 | 93 | octet_range: NUM '-' NUM { $$ = octetRange { $1, $3 } } 94 | 95 | mask: NUM 96 | { 97 | if $1 > maxMaskValue { 98 | $$ = net.CIDRMask(maxMaskValue, ipV4MaskLength) 99 | break 100 | } 101 | $$ = net.CIDRMask(int($1), ipV4MaskLength) 102 | } 103 | 104 | %% 105 | 106 | // ParseList takes a list of target specifications and returns a list of ranges, 107 | // even if the list contains a single element. 108 | func ParseList(in string) (AddressRangeList, error) { 109 | lex := &ipLex{line: []byte(in)} 110 | errCode := ipParse(lex) 111 | if errCode != 0 || lex.err != nil { 112 | return nil, errors.Wrap(lex.err, "could not parse target") 113 | } 114 | return lex.output, nil 115 | } 116 | 117 | // Parse takes a single target specification and returns a range. It effectively calls ParseList 118 | // and returns the first result 119 | func Parse(in string) (*AddressRange, error) { 120 | l, err := ParseList(in) 121 | if err != nil { 122 | return nil, err 123 | } 124 | return &l[0], nil 125 | } 126 | -------------------------------------------------------------------------------- /iprange.go: -------------------------------------------------------------------------------- 1 | //go:generate goyacc -p "ip" ip.y 2 | 3 | package iprange 4 | -------------------------------------------------------------------------------- /iprange_test.go: -------------------------------------------------------------------------------- 1 | package iprange 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestSimpleAddress(t *testing.T) { 11 | ipRange, err := Parse("192.168.1.1") 12 | assert.Nil(t, err) 13 | 14 | assert.Equal(t, net.IPv4(192, 168, 1, 1).To4(), ipRange.Min) 15 | assert.Equal(t, ipRange.Min, ipRange.Max) 16 | } 17 | 18 | func TestCIDRAddress(t *testing.T) { 19 | { 20 | ipRange, err := Parse("192.168.1.1/24") 21 | assert.Nil(t, err) 22 | 23 | assert.Equal(t, net.IPv4(192, 168, 1, 0).To4(), ipRange.Min) 24 | assert.Equal(t, net.IPv4(192, 168, 1, 255).To4(), ipRange.Max) 25 | } 26 | 27 | { 28 | ipRange, err := Parse("192.168.2.1/24") 29 | assert.Nil(t, err) 30 | 31 | assert.Equal(t, net.IPv4(192, 168, 2, 0).To4(), ipRange.Min) 32 | assert.Equal(t, net.IPv4(192, 168, 2, 255).To4(), ipRange.Max) 33 | 34 | out := ipRange.Expand() 35 | assert.Equal(t, int(0xffffffff-0xffffff00), len(out)-1) 36 | for i := 0; i < 256; i++ { 37 | assert.Equal(t, net.IP([]byte{192, 168, 2, byte(i)}), out[i]) 38 | } 39 | } 40 | 41 | { 42 | ipRange, err := Parse("10.1.2.3/16") 43 | assert.Nil(t, err) 44 | 45 | assert.Equal(t, net.IPv4(10, 1, 0, 0).To4(), ipRange.Min) 46 | assert.Equal(t, net.IPv4(10, 1, 255, 255).To4(), ipRange.Max) 47 | 48 | out := ipRange.Expand() 49 | assert.Equal(t, int(0xffffffff-0xffff0000), len(out)-1) 50 | for i := 0; i < 65536; i++ { 51 | assert.Equal(t, net.IP([]byte{10, 1, byte(i / 256), byte(i % 256)}), out[i]) 52 | } 53 | } 54 | 55 | { 56 | ipRange, err := Parse("10.1.2.3/32") 57 | assert.Nil(t, err) 58 | 59 | assert.Equal(t, net.IPv4(10, 1, 2, 3).To4(), ipRange.Min) 60 | assert.Equal(t, ipRange.Min, ipRange.Max) 61 | } 62 | 63 | { 64 | ipRange, err := Parse("10.1.2.3/40") 65 | assert.Nil(t, err) 66 | 67 | assert.Equal(t, net.IPv4(10, 1, 2, 3).To4(), ipRange.Min) 68 | assert.Equal(t, ipRange.Min, ipRange.Max) 69 | } 70 | 71 | { 72 | ipRange, err := Parse("10.1.2.3/0") 73 | assert.Nil(t, err) 74 | 75 | assert.Equal(t, net.IPv4(0, 0, 0, 0).To4(), ipRange.Min) 76 | assert.Equal(t, net.IPv4(255, 255, 255, 255).To4(), ipRange.Max) 77 | } 78 | } 79 | 80 | func TestWildcardAddress(t *testing.T) { 81 | ipRange, err := Parse("192.168.1.*") 82 | assert.Nil(t, err) 83 | 84 | assert.Equal(t, net.IPv4(192, 168, 1, 0).To4(), ipRange.Min) 85 | assert.Equal(t, net.IPv4(192, 168, 1, 255).To4(), ipRange.Max) 86 | } 87 | 88 | func TestRangeAddress(t *testing.T) { 89 | { 90 | ipRange, err := Parse("192.168.1.10-20") 91 | assert.Nil(t, err) 92 | 93 | assert.Equal(t, net.IPv4(192, 168, 1, 10).To4(), ipRange.Min) 94 | assert.Equal(t, net.IPv4(192, 168, 1, 20).To4(), ipRange.Max) 95 | } 96 | 97 | { 98 | ipRange, err := Parse("192.168.10-20.1") 99 | assert.Nil(t, err) 100 | 101 | assert.Equal(t, net.IPv4(192, 168, 10, 1).To4(), ipRange.Min) 102 | assert.Equal(t, net.IPv4(192, 168, 20, 1).To4(), ipRange.Max) 103 | } 104 | 105 | { 106 | ipRange, err := Parse("0-255.1.1.1") 107 | assert.Nil(t, err) 108 | 109 | assert.Equal(t, net.IPv4(0, 1, 1, 1).To4(), ipRange.Min) 110 | assert.Equal(t, net.IPv4(255, 1, 1, 1).To4(), ipRange.Max) 111 | } 112 | 113 | { 114 | ipRange, err := Parse("1-2.3-4.5-6.7-8") 115 | assert.Nil(t, err) 116 | 117 | assert.Equal(t, net.IPv4(1, 3, 5, 7).To4(), ipRange.Min) 118 | assert.Equal(t, net.IPv4(2, 4, 6, 8).To4(), ipRange.Max) 119 | 120 | out := ipRange.Expand() 121 | 122 | assert.Equal(t, 16, len(out)) 123 | assert.Equal(t, out, []net.IP{ 124 | net.IP([]byte{1, 3, 5, 7}), 125 | net.IP([]byte{1, 3, 5, 8}), 126 | net.IP([]byte{1, 3, 6, 7}), 127 | net.IP([]byte{1, 3, 6, 8}), 128 | net.IP([]byte{1, 4, 5, 7}), 129 | net.IP([]byte{1, 4, 5, 8}), 130 | net.IP([]byte{1, 4, 6, 7}), 131 | net.IP([]byte{1, 4, 6, 8}), 132 | net.IP([]byte{2, 3, 5, 7}), 133 | net.IP([]byte{2, 3, 5, 8}), 134 | net.IP([]byte{2, 3, 6, 7}), 135 | net.IP([]byte{2, 3, 6, 8}), 136 | net.IP([]byte{2, 4, 5, 7}), 137 | net.IP([]byte{2, 4, 5, 8}), 138 | net.IP([]byte{2, 4, 6, 7}), 139 | net.IP([]byte{2, 4, 6, 8}), 140 | }) 141 | } 142 | } 143 | 144 | func TestMixedAddress(t *testing.T) { 145 | ipRange, err := Parse("192.168.10-20.*/25") 146 | assert.Nil(t, err) 147 | 148 | assert.Equal(t, net.IPv4(192, 168, 10, 0).To4(), ipRange.Min) 149 | assert.Equal(t, net.IPv4(192, 168, 10, 127).To4(), ipRange.Max) 150 | } 151 | 152 | func TestList(t *testing.T) { 153 | rangeList, err := ParseList("192.168.1.1, 192.168.1.1/24, 192.168.1.*, 192.168.1.10-20") 154 | assert.Nil(t, err) 155 | assert.Len(t, rangeList, 4) 156 | 157 | assert.Equal(t, net.IP([]byte{192, 168, 1, 1}), rangeList[0].Min) 158 | assert.Equal(t, net.IP([]byte{192, 168, 1, 1}), rangeList[0].Max) 159 | 160 | assert.Equal(t, net.IP([]byte{192, 168, 1, 0}), rangeList[1].Min) 161 | assert.Equal(t, net.IP([]byte{192, 168, 1, 255}), rangeList[1].Max) 162 | 163 | assert.Equal(t, net.IP([]byte{192, 168, 1, 0}), rangeList[2].Min) 164 | assert.Equal(t, net.IP([]byte{192, 168, 1, 255}), rangeList[2].Max) 165 | 166 | assert.Equal(t, net.IP([]byte{192, 168, 1, 10}), rangeList[3].Min) 167 | assert.Equal(t, net.IP([]byte{192, 168, 1, 20}), rangeList[3].Max) 168 | } 169 | 170 | func TestBadAddress(t *testing.T) { 171 | ipRange, err := Parse("192.168.10") 172 | assert.Nil(t, ipRange) 173 | assert.Error(t, err) 174 | } 175 | 176 | func TestBadList(t *testing.T) { 177 | rangeList, err := ParseList("192.168.1,, 192.168.1.1/24, 192.168.1.*, 192.168.1.10-20") 178 | assert.Error(t, err) 179 | assert.Nil(t, rangeList) 180 | } 181 | 182 | func TestListExpansion(t *testing.T) { 183 | rangeList, err := ParseList("192.168.1.10, 192.168.1.1-20, 192.168.1.10/29") 184 | assert.Nil(t, err) 185 | 186 | expanded := rangeList.Expand() 187 | assert.Len(t, expanded, 20) 188 | } 189 | -------------------------------------------------------------------------------- /lex.go: -------------------------------------------------------------------------------- 1 | package iprange 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "log" 7 | "strconv" 8 | "unicode/utf8" 9 | ) 10 | 11 | const eof = 0 12 | 13 | type ipLex struct { 14 | line []byte 15 | peek rune 16 | output AddressRangeList 17 | err error 18 | } 19 | 20 | func (ip *ipLex) Lex(yylval *ipSymType) int { 21 | for { 22 | c := ip.next() 23 | switch c { 24 | case eof: 25 | return eof 26 | case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 27 | return ip.byte(c, yylval) 28 | default: 29 | return int(c) 30 | } 31 | } 32 | } 33 | 34 | func (ip *ipLex) byte(c rune, yylval *ipSymType) int { 35 | add := func(b *bytes.Buffer, c rune) { 36 | if _, err := b.WriteRune(c); err != nil { 37 | log.Fatalf("WriteRune: %s", err) 38 | } 39 | } 40 | var b bytes.Buffer 41 | add(&b, c) 42 | L: 43 | for { 44 | c = ip.next() 45 | switch c { 46 | case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 47 | add(&b, c) 48 | default: 49 | break L 50 | } 51 | } 52 | if c != eof { 53 | ip.peek = c 54 | } 55 | octet, err := strconv.ParseUint(b.String(), 10, 32) 56 | if err != nil { 57 | log.Printf("badly formatted octet") 58 | return eof 59 | } 60 | yylval.byteValue = byte(octet) 61 | return NUM 62 | } 63 | 64 | func (ip *ipLex) next() rune { 65 | if ip.peek != eof { 66 | r := ip.peek 67 | ip.peek = eof 68 | return r 69 | } 70 | if len(ip.line) == 0 { 71 | return eof 72 | } 73 | c, size := utf8.DecodeRune(ip.line) 74 | ip.line = ip.line[size:] 75 | if c == utf8.RuneError && size == 1 { 76 | log.Print("invalid utf8") 77 | return ip.next() 78 | } 79 | return c 80 | } 81 | 82 | func (ip *ipLex) Error(s string) { 83 | ip.err = errors.New(s) 84 | } 85 | -------------------------------------------------------------------------------- /sortip.go: -------------------------------------------------------------------------------- 1 | package iprange 2 | 3 | import ( 4 | "math/big" 5 | "net" 6 | ) 7 | 8 | // Asc implements sorting in ascending order for IP addresses 9 | type asc []net.IP 10 | 11 | func (a asc) Len() int { 12 | return len(a) 13 | } 14 | 15 | func (a asc) Swap(i, j int) { 16 | a[i], a[j] = a[j], a[i] 17 | } 18 | 19 | func (a asc) Less(i, j int) bool { 20 | bigi := big.NewInt(0).SetBytes(a[i]) 21 | bigj := big.NewInt(0).SetBytes(a[j]) 22 | 23 | if bigi.Cmp(bigj) == -1 { 24 | return true 25 | } 26 | return false 27 | } 28 | -------------------------------------------------------------------------------- /y.go: -------------------------------------------------------------------------------- 1 | // Code generated by goyacc -p ip ip.y. DO NOT EDIT. 2 | 3 | //line ip.y:2 4 | 5 | package iprange 6 | 7 | import ( 8 | "encoding/binary" 9 | __yyfmt__ "fmt" 10 | "net" 11 | 12 | "github.com/pkg/errors" 13 | ) 14 | 15 | //line ip.y:3 16 | 17 | type AddressRangeList []AddressRange 18 | 19 | type AddressRange struct { 20 | Min net.IP 21 | Max net.IP 22 | } 23 | 24 | type octetRange struct { 25 | min byte 26 | max byte 27 | } 28 | 29 | const ( 30 | ipV4MaskLength = 32 31 | maxMaskValue = ipV4MaskLength 32 | ) 33 | 34 | //line ip.y:31 35 | type ipSymType struct { 36 | yys int 37 | byteValue byte 38 | octRange octetRange 39 | addrRange AddressRange 40 | result AddressRangeList 41 | ipMask net.IPMask 42 | } 43 | 44 | const NUM = 57346 45 | 46 | var ipToknames = [...]string{ 47 | "$end", 48 | "error", 49 | "$unk", 50 | "NUM", 51 | "','", 52 | "' '", 53 | "'/'", 54 | "'.'", 55 | "'*'", 56 | "'-'", 57 | } 58 | 59 | var ipStatenames = [...]string{} 60 | 61 | const ipEofCode = 1 62 | const ipErrCode = 2 63 | const ipInitialStackSize = 16 64 | 65 | //line ip.y:104 66 | 67 | // ParseList takes a list of target specifications and returns a list of ranges, 68 | // even if the list contains a single element. 69 | func ParseList(in string) (AddressRangeList, error) { 70 | lex := &ipLex{line: []byte(in)} 71 | errCode := ipParse(lex) 72 | if errCode != 0 || lex.err != nil { 73 | return nil, errors.Wrap(lex.err, "could not parse target") 74 | } 75 | return lex.output, nil 76 | } 77 | 78 | // Parse takes a single target specification and returns a range. It effectively calls ParseList 79 | // and returns the first result 80 | func Parse(in string) (*AddressRange, error) { 81 | l, err := ParseList(in) 82 | if err != nil { 83 | return nil, err 84 | } 85 | return &l[0], nil 86 | } 87 | 88 | //line yacctab:1 89 | var ipExca = [...]int8{ 90 | -1, 1, 91 | 1, -1, 92 | -2, 0, 93 | } 94 | 95 | const ipPrivate = 57344 96 | 97 | const ipLast = 23 98 | 99 | var ipAct = [...]int8{ 100 | 4, 5, 12, 21, 2, 10, 6, 19, 11, 14, 101 | 9, 18, 17, 13, 16, 8, 1, 7, 3, 15, 102 | 20, 0, 22, 103 | } 104 | 105 | var ipPact = [...]int16{ 106 | -3, 5, -1000, -2, 0, -8, -1000, -1000, -3, 3, 107 | 10, -3, 7, -1000, -1000, -1000, -1000, -1, -1000, -3, 108 | -5, -3, -1000, 109 | } 110 | 111 | var ipPgo = [...]int8{ 112 | 0, 19, 18, 4, 0, 17, 16, 15, 113 | } 114 | 115 | var ipR1 = [...]int8{ 116 | 0, 6, 6, 7, 7, 3, 3, 2, 4, 4, 117 | 4, 5, 1, 118 | } 119 | 120 | var ipR2 = [...]int8{ 121 | 0, 1, 3, 1, 2, 3, 1, 7, 1, 1, 122 | 1, 3, 1, 123 | } 124 | 125 | var ipChk = [...]int16{ 126 | -1000, -6, -3, -2, -4, 4, 9, -5, -7, 5, 127 | 7, 8, 10, -3, 6, -1, 4, -4, 4, 8, 128 | -4, 8, -4, 129 | } 130 | 131 | var ipDef = [...]int8{ 132 | 0, -2, 1, 6, 0, 8, 9, 10, 0, 3, 133 | 0, 0, 0, 2, 4, 5, 12, 0, 11, 0, 134 | 0, 0, 7, 135 | } 136 | 137 | var ipTok1 = [...]int8{ 138 | 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 139 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 140 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 141 | 3, 3, 6, 3, 3, 3, 3, 3, 3, 3, 142 | 3, 3, 9, 3, 5, 10, 8, 7, 143 | } 144 | 145 | var ipTok2 = [...]int8{ 146 | 2, 3, 4, 147 | } 148 | 149 | var ipTok3 = [...]int8{ 150 | 0, 151 | } 152 | 153 | var ipErrorMessages = [...]struct { 154 | state int 155 | token int 156 | msg string 157 | }{} 158 | 159 | //line yaccpar:1 160 | 161 | /* parser for yacc output */ 162 | 163 | var ( 164 | ipDebug = 0 165 | ipErrorVerbose = false 166 | ) 167 | 168 | type ipLexer interface { 169 | Lex(lval *ipSymType) int 170 | Error(s string) 171 | } 172 | 173 | type ipParser interface { 174 | Parse(ipLexer) int 175 | Lookahead() int 176 | } 177 | 178 | type ipParserImpl struct { 179 | lval ipSymType 180 | stack [ipInitialStackSize]ipSymType 181 | char int 182 | } 183 | 184 | func (p *ipParserImpl) Lookahead() int { 185 | return p.char 186 | } 187 | 188 | func ipNewParser() ipParser { 189 | return &ipParserImpl{} 190 | } 191 | 192 | const ipFlag = -1000 193 | 194 | func ipTokname(c int) string { 195 | if c >= 1 && c-1 < len(ipToknames) { 196 | if ipToknames[c-1] != "" { 197 | return ipToknames[c-1] 198 | } 199 | } 200 | return __yyfmt__.Sprintf("tok-%v", c) 201 | } 202 | 203 | func ipStatname(s int) string { 204 | if s >= 0 && s < len(ipStatenames) { 205 | if ipStatenames[s] != "" { 206 | return ipStatenames[s] 207 | } 208 | } 209 | return __yyfmt__.Sprintf("state-%v", s) 210 | } 211 | 212 | func ipErrorMessage(state, lookAhead int) string { 213 | const TOKSTART = 4 214 | 215 | if !ipErrorVerbose { 216 | return "syntax error" 217 | } 218 | 219 | for _, e := range ipErrorMessages { 220 | if e.state == state && e.token == lookAhead { 221 | return "syntax error: " + e.msg 222 | } 223 | } 224 | 225 | res := "syntax error: unexpected " + ipTokname(lookAhead) 226 | 227 | // To match Bison, suggest at most four expected tokens. 228 | expected := make([]int, 0, 4) 229 | 230 | // Look for shiftable tokens. 231 | base := int(ipPact[state]) 232 | for tok := TOKSTART; tok-1 < len(ipToknames); tok++ { 233 | if n := base + tok; n >= 0 && n < ipLast && int(ipChk[int(ipAct[n])]) == tok { 234 | if len(expected) == cap(expected) { 235 | return res 236 | } 237 | expected = append(expected, tok) 238 | } 239 | } 240 | 241 | if ipDef[state] == -2 { 242 | i := 0 243 | for ipExca[i] != -1 || int(ipExca[i+1]) != state { 244 | i += 2 245 | } 246 | 247 | // Look for tokens that we accept or reduce. 248 | for i += 2; ipExca[i] >= 0; i += 2 { 249 | tok := int(ipExca[i]) 250 | if tok < TOKSTART || ipExca[i+1] == 0 { 251 | continue 252 | } 253 | if len(expected) == cap(expected) { 254 | return res 255 | } 256 | expected = append(expected, tok) 257 | } 258 | 259 | // If the default action is to accept or reduce, give up. 260 | if ipExca[i+1] != 0 { 261 | return res 262 | } 263 | } 264 | 265 | for i, tok := range expected { 266 | if i == 0 { 267 | res += ", expecting " 268 | } else { 269 | res += " or " 270 | } 271 | res += ipTokname(tok) 272 | } 273 | return res 274 | } 275 | 276 | func iplex1(lex ipLexer, lval *ipSymType) (char, token int) { 277 | token = 0 278 | char = lex.Lex(lval) 279 | if char <= 0 { 280 | token = int(ipTok1[0]) 281 | goto out 282 | } 283 | if char < len(ipTok1) { 284 | token = int(ipTok1[char]) 285 | goto out 286 | } 287 | if char >= ipPrivate { 288 | if char < ipPrivate+len(ipTok2) { 289 | token = int(ipTok2[char-ipPrivate]) 290 | goto out 291 | } 292 | } 293 | for i := 0; i < len(ipTok3); i += 2 { 294 | token = int(ipTok3[i+0]) 295 | if token == char { 296 | token = int(ipTok3[i+1]) 297 | goto out 298 | } 299 | } 300 | 301 | out: 302 | if token == 0 { 303 | token = int(ipTok2[1]) /* unknown char */ 304 | } 305 | if ipDebug >= 3 { 306 | __yyfmt__.Printf("lex %s(%d)\n", ipTokname(token), uint(char)) 307 | } 308 | return char, token 309 | } 310 | 311 | func ipParse(iplex ipLexer) int { 312 | return ipNewParser().Parse(iplex) 313 | } 314 | 315 | func (iprcvr *ipParserImpl) Parse(iplex ipLexer) int { 316 | var ipn int 317 | var ipVAL ipSymType 318 | var ipDollar []ipSymType 319 | _ = ipDollar // silence set and not used 320 | ipS := iprcvr.stack[:] 321 | 322 | Nerrs := 0 /* number of errors */ 323 | Errflag := 0 /* error recovery flag */ 324 | ipstate := 0 325 | iprcvr.char = -1 326 | iptoken := -1 // iprcvr.char translated into internal numbering 327 | defer func() { 328 | // Make sure we report no lookahead when not parsing. 329 | ipstate = -1 330 | iprcvr.char = -1 331 | iptoken = -1 332 | }() 333 | ipp := -1 334 | goto ipstack 335 | 336 | ret0: 337 | return 0 338 | 339 | ret1: 340 | return 1 341 | 342 | ipstack: 343 | /* put a state and value onto the stack */ 344 | if ipDebug >= 4 { 345 | __yyfmt__.Printf("char %v in %v\n", ipTokname(iptoken), ipStatname(ipstate)) 346 | } 347 | 348 | ipp++ 349 | if ipp >= len(ipS) { 350 | nyys := make([]ipSymType, len(ipS)*2) 351 | copy(nyys, ipS) 352 | ipS = nyys 353 | } 354 | ipS[ipp] = ipVAL 355 | ipS[ipp].yys = ipstate 356 | 357 | ipnewstate: 358 | ipn = int(ipPact[ipstate]) 359 | if ipn <= ipFlag { 360 | goto ipdefault /* simple state */ 361 | } 362 | if iprcvr.char < 0 { 363 | iprcvr.char, iptoken = iplex1(iplex, &iprcvr.lval) 364 | } 365 | ipn += iptoken 366 | if ipn < 0 || ipn >= ipLast { 367 | goto ipdefault 368 | } 369 | ipn = int(ipAct[ipn]) 370 | if int(ipChk[ipn]) == iptoken { /* valid shift */ 371 | iprcvr.char = -1 372 | iptoken = -1 373 | ipVAL = iprcvr.lval 374 | ipstate = ipn 375 | if Errflag > 0 { 376 | Errflag-- 377 | } 378 | goto ipstack 379 | } 380 | 381 | ipdefault: 382 | /* default state action */ 383 | ipn = int(ipDef[ipstate]) 384 | if ipn == -2 { 385 | if iprcvr.char < 0 { 386 | iprcvr.char, iptoken = iplex1(iplex, &iprcvr.lval) 387 | } 388 | 389 | /* look through exception table */ 390 | xi := 0 391 | for { 392 | if ipExca[xi+0] == -1 && int(ipExca[xi+1]) == ipstate { 393 | break 394 | } 395 | xi += 2 396 | } 397 | for xi += 2; ; xi += 2 { 398 | ipn = int(ipExca[xi+0]) 399 | if ipn < 0 || ipn == iptoken { 400 | break 401 | } 402 | } 403 | ipn = int(ipExca[xi+1]) 404 | if ipn < 0 { 405 | goto ret0 406 | } 407 | } 408 | if ipn == 0 { 409 | /* error ... attempt to resume parsing */ 410 | switch Errflag { 411 | case 0: /* brand new error */ 412 | iplex.Error(ipErrorMessage(ipstate, iptoken)) 413 | Nerrs++ 414 | if ipDebug >= 1 { 415 | __yyfmt__.Printf("%s", ipStatname(ipstate)) 416 | __yyfmt__.Printf(" saw %s\n", ipTokname(iptoken)) 417 | } 418 | fallthrough 419 | 420 | case 1, 2: /* incompletely recovered error ... try again */ 421 | Errflag = 3 422 | 423 | /* find a state where "error" is a legal shift action */ 424 | for ipp >= 0 { 425 | ipn = int(ipPact[ipS[ipp].yys]) + ipErrCode 426 | if ipn >= 0 && ipn < ipLast { 427 | ipstate = int(ipAct[ipn]) /* simulate a shift of "error" */ 428 | if int(ipChk[ipstate]) == ipErrCode { 429 | goto ipstack 430 | } 431 | } 432 | 433 | /* the current p has no shift on "error", pop stack */ 434 | if ipDebug >= 2 { 435 | __yyfmt__.Printf("error recovery pops state %d\n", ipS[ipp].yys) 436 | } 437 | ipp-- 438 | } 439 | /* there is no state on the stack with an error shift ... abort */ 440 | goto ret1 441 | 442 | case 3: /* no shift yet; clobber input char */ 443 | if ipDebug >= 2 { 444 | __yyfmt__.Printf("error recovery discards %s\n", ipTokname(iptoken)) 445 | } 446 | if iptoken == ipEofCode { 447 | goto ret1 448 | } 449 | iprcvr.char = -1 450 | iptoken = -1 451 | goto ipnewstate /* try again in the same state */ 452 | } 453 | } 454 | 455 | /* reduction by production ipn */ 456 | if ipDebug >= 2 { 457 | __yyfmt__.Printf("reduce %v in:\n\t%v\n", ipn, ipStatname(ipstate)) 458 | } 459 | 460 | ipnt := ipn 461 | ippt := ipp 462 | _ = ippt // guard against "declared and not used" 463 | 464 | ipp -= int(ipR2[ipn]) 465 | // ipp is now the index of $0. Perform the default action. Iff the 466 | // reduced production is ε, $1 is possibly out of range. 467 | if ipp+1 >= len(ipS) { 468 | nyys := make([]ipSymType, len(ipS)*2) 469 | copy(nyys, ipS) 470 | ipS = nyys 471 | } 472 | ipVAL = ipS[ipp+1] 473 | 474 | /* consult goto table to find next state */ 475 | ipn = int(ipR1[ipn]) 476 | ipg := int(ipPgo[ipn]) 477 | ipj := ipg + ipS[ipp].yys + 1 478 | 479 | if ipj >= ipLast { 480 | ipstate = int(ipAct[ipg]) 481 | } else { 482 | ipstate = int(ipAct[ipj]) 483 | if int(ipChk[ipstate]) != -ipn { 484 | ipstate = int(ipAct[ipg]) 485 | } 486 | } 487 | // dummy call; replaced with literal code 488 | switch ipnt { 489 | 490 | case 1: 491 | ipDollar = ipS[ippt-1 : ippt+1] 492 | //line ip.y:48 493 | { 494 | ipVAL.result = append(ipVAL.result, ipDollar[1].addrRange) 495 | iplex.(*ipLex).output = ipVAL.result 496 | } 497 | case 2: 498 | ipDollar = ipS[ippt-3 : ippt+1] 499 | //line ip.y:53 500 | { 501 | ipVAL.result = append(ipDollar[1].result, ipDollar[3].addrRange) 502 | iplex.(*ipLex).output = ipVAL.result 503 | } 504 | case 5: 505 | ipDollar = ipS[ippt-3 : ippt+1] 506 | //line ip.y:61 507 | { 508 | mask := ipDollar[3].ipMask 509 | min := ipDollar[1].addrRange.Min.Mask(mask) 510 | maxInt := binary.BigEndian.Uint32([]byte(min)) + 511 | 0xffffffff - 512 | binary.BigEndian.Uint32([]byte(mask)) 513 | maxBytes := make([]byte, 4) 514 | binary.BigEndian.PutUint32(maxBytes, maxInt) 515 | maxBytes = maxBytes[len(maxBytes)-4:] 516 | max := net.IP(maxBytes) 517 | ipVAL.addrRange = AddressRange{ 518 | Min: min.To4(), 519 | Max: max.To4(), 520 | } 521 | } 522 | case 6: 523 | ipDollar = ipS[ippt-1 : ippt+1] 524 | //line ip.y:77 525 | { 526 | ipVAL.addrRange = ipDollar[1].addrRange 527 | } 528 | case 7: 529 | ipDollar = ipS[ippt-7 : ippt+1] 530 | //line ip.y:82 531 | { 532 | ipVAL.addrRange = AddressRange{ 533 | Min: net.IPv4(ipDollar[1].octRange.min, ipDollar[3].octRange.min, ipDollar[5].octRange.min, ipDollar[7].octRange.min).To4(), 534 | Max: net.IPv4(ipDollar[1].octRange.max, ipDollar[3].octRange.max, ipDollar[5].octRange.max, ipDollar[7].octRange.max).To4(), 535 | } 536 | } 537 | case 8: 538 | ipDollar = ipS[ippt-1 : ippt+1] 539 | //line ip.y:89 540 | { 541 | ipVAL.octRange = octetRange{ipDollar[1].byteValue, ipDollar[1].byteValue} 542 | } 543 | case 9: 544 | ipDollar = ipS[ippt-1 : ippt+1] 545 | //line ip.y:90 546 | { 547 | ipVAL.octRange = octetRange{0, 255} 548 | } 549 | case 10: 550 | ipDollar = ipS[ippt-1 : ippt+1] 551 | //line ip.y:91 552 | { 553 | ipVAL.octRange = ipDollar[1].octRange 554 | } 555 | case 11: 556 | ipDollar = ipS[ippt-3 : ippt+1] 557 | //line ip.y:93 558 | { 559 | ipVAL.octRange = octetRange{ipDollar[1].byteValue, ipDollar[3].byteValue} 560 | } 561 | case 12: 562 | ipDollar = ipS[ippt-1 : ippt+1] 563 | //line ip.y:96 564 | { 565 | if ipDollar[1].byteValue > maxMaskValue { 566 | ipVAL.ipMask = net.CIDRMask(maxMaskValue, ipV4MaskLength) 567 | break 568 | } 569 | ipVAL.ipMask = net.CIDRMask(int(ipDollar[1].byteValue), ipV4MaskLength) 570 | } 571 | } 572 | goto ipstack /* stack new state and value */ 573 | } 574 | -------------------------------------------------------------------------------- /y.output: -------------------------------------------------------------------------------- 1 | 2 | state 0 3 | $accept: .result $end 4 | 5 | NUM shift 5 6 | '*' shift 6 7 | . error 8 | 9 | address goto 3 10 | target goto 2 11 | term goto 4 12 | octet_range goto 7 13 | result goto 1 14 | 15 | state 1 16 | $accept: result.$end 17 | result: result.comma target 18 | 19 | $end accept 20 | ',' shift 9 21 | . error 22 | 23 | comma goto 8 24 | 25 | state 2 26 | result: target. (1) 27 | 28 | . reduce 1 (src line 47) 29 | 30 | 31 | state 3 32 | target: address.'/' mask 33 | target: address. (6) 34 | 35 | '/' shift 10 36 | . reduce 6 (src line 76) 37 | 38 | 39 | state 4 40 | address: term.'.' term '.' term '.' term 41 | 42 | '.' shift 11 43 | . error 44 | 45 | 46 | state 5 47 | term: NUM. (8) 48 | octet_range: NUM.'-' NUM 49 | 50 | '-' shift 12 51 | . reduce 8 (src line 89) 52 | 53 | 54 | state 6 55 | term: '*'. (9) 56 | 57 | . reduce 9 (src line 90) 58 | 59 | 60 | state 7 61 | term: octet_range. (10) 62 | 63 | . reduce 10 (src line 91) 64 | 65 | 66 | state 8 67 | result: result comma.target 68 | 69 | NUM shift 5 70 | '*' shift 6 71 | . error 72 | 73 | address goto 3 74 | target goto 13 75 | term goto 4 76 | octet_range goto 7 77 | 78 | state 9 79 | comma: ','. (3) 80 | comma: ','.' ' 81 | 82 | ' ' shift 14 83 | . reduce 3 (src line 58) 84 | 85 | 86 | state 10 87 | target: address '/'.mask 88 | 89 | NUM shift 16 90 | . error 91 | 92 | mask goto 15 93 | 94 | state 11 95 | address: term '.'.term '.' term '.' term 96 | 97 | NUM shift 5 98 | '*' shift 6 99 | . error 100 | 101 | term goto 17 102 | octet_range goto 7 103 | 104 | state 12 105 | octet_range: NUM '-'.NUM 106 | 107 | NUM shift 18 108 | . error 109 | 110 | 111 | state 13 112 | result: result comma target. (2) 113 | 114 | . reduce 2 (src line 52) 115 | 116 | 117 | state 14 118 | comma: ',' ' '. (4) 119 | 120 | . reduce 4 (src line 58) 121 | 122 | 123 | state 15 124 | target: address '/' mask. (5) 125 | 126 | . reduce 5 (src line 60) 127 | 128 | 129 | state 16 130 | mask: NUM. (12) 131 | 132 | . reduce 12 (src line 95) 133 | 134 | 135 | state 17 136 | address: term '.' term.'.' term '.' term 137 | 138 | '.' shift 19 139 | . error 140 | 141 | 142 | state 18 143 | octet_range: NUM '-' NUM. (11) 144 | 145 | . reduce 11 (src line 93) 146 | 147 | 148 | state 19 149 | address: term '.' term '.'.term '.' term 150 | 151 | NUM shift 5 152 | '*' shift 6 153 | . error 154 | 155 | term goto 20 156 | octet_range goto 7 157 | 158 | state 20 159 | address: term '.' term '.' term.'.' term 160 | 161 | '.' shift 21 162 | . error 163 | 164 | 165 | state 21 166 | address: term '.' term '.' term '.'.term 167 | 168 | NUM shift 5 169 | '*' shift 6 170 | . error 171 | 172 | term goto 22 173 | octet_range goto 7 174 | 175 | state 22 176 | address: term '.' term '.' term '.' term. (7) 177 | 178 | . reduce 7 (src line 81) 179 | 180 | 181 | 10 terminals, 8 nonterminals 182 | 13 grammar rules, 23/16000 states 183 | 0 shift/reduce, 0 reduce/reduce conflicts reported 184 | 57 working sets used 185 | memory: parser 16/240000 186 | 5 extra closures 187 | 19 shift entries, 1 exceptions 188 | 11 goto entries 189 | 6 entries saved by goto default 190 | Optimizer space used: output 23/240000 191 | 23 table entries, 1 zero 192 | maximum spread: 10, maximum offset: 21 193 | --------------------------------------------------------------------------------