--- /dev/null
+package iplist
+
+import (
+ "bytes"
+ "fmt"
+ "net"
+ "regexp"
+ "sort"
+ "strings"
+)
+
+type IPList struct {
+ ranges []Range
+}
+
+type Range struct {
+ First, Last net.IP
+ Description string
+}
+
+// Create a new IP list. The given range must already sorted by the lower IP
+// in the range. Behaviour is undefined for lists of overlapping ranges.
+func New(initSorted []Range) *IPList {
+ return &IPList{
+ ranges: initSorted,
+ }
+}
+
+// Return the range the given IP is in. Returns nil if no range is found.
+func (me *IPList) Lookup(ip net.IP) (r *Range) {
+ // Find the index of the first range for which the following range exceeds
+ // it.
+ i := sort.Search(len(me.ranges), func(i int) bool {
+ if i+1 >= len(me.ranges) {
+ return true
+ }
+ return bytes.Compare(ip, me.ranges[i+1].First) < 0
+ })
+ if i == len(me.ranges) {
+ return
+ }
+ r = &me.ranges[i]
+ if bytes.Compare(ip, r.First) < 0 || bytes.Compare(ip, r.Last) > 0 {
+ r = nil
+ }
+ return
+}
+
+// Parse a line of the PeerGuardian Text Lists (P2P) Format. Returns !ok but
+// no error if a line doesn't contain a range but isn't erroneous, such as
+// comment and blank lines.
+func ParseBlocklistP2PLine(l string) (r Range, ok bool, err error) {
+ l = strings.TrimSpace(l)
+ if l == "" || strings.HasPrefix(l, "#") {
+ return
+ }
+ sms := regexp.MustCompile(`(.*):([\d.]+)-([\d.]+)`).FindStringSubmatch(l)
+ if sms == nil {
+ err = fmt.Errorf("error parsing %q", l)
+ return
+ }
+ r.Description = sms[1]
+ r.First = net.ParseIP(sms[2])
+ r.Last = net.ParseIP(sms[3])
+ if r.First == nil || r.Last == nil {
+ return
+ }
+ ok = true
+ return
+}
--- /dev/null
+package iplist
+
+import (
+ "bufio"
+ "net"
+ "strings"
+ "testing"
+)
+
+var sample = `
+# List distributed by iblocklist.com
+
+a:1.2.4.0-1.2.4.255
+b:1.2.8.0-1.2.8.255`
+
+func TestSimple(t *testing.T) {
+ var ranges []Range
+ scanner := bufio.NewScanner(strings.NewReader(sample))
+ for scanner.Scan() {
+ r, ok, _ := ParseBlocklistP2PLine(scanner.Text())
+ if ok {
+ t.Log(r)
+ ranges = append(ranges, r)
+ }
+ }
+ if err := scanner.Err(); err != nil {
+ t.Fatalf("error while scanning: %s", err)
+ }
+ if len(ranges) != 2 {
+ t.Fatalf("expected 2 ranges but got %d", len(ranges))
+ }
+ iplist := New(ranges)
+ for _, _case := range []struct {
+ IP string
+ Hit bool
+ Desc string
+ }{
+ {"1.2.3.255", false, ""},
+ {"1.2.8.0", true, "b"},
+ {"1.2.4.255", true, "a"},
+ // Try to roll over to the next octet on the parse.
+ {"1.2.7.256", false, ""},
+ {"1.2.8.254", true, "b"},
+ } {
+ r := iplist.Lookup(net.ParseIP(_case.IP))
+ if !_case.Hit {
+ if r != nil {
+ t.Fatalf("got hit when none was expected")
+ }
+ continue
+ }
+ if r == nil {
+ t.Fatalf("expected hit for %q", _case.IP)
+ }
+ if r.Description != _case.Desc {
+ t.Fatalf("%q != %q", r.Description, _case.Desc)
+ }
+ }
+}