github.com/anacrolix/torrent@v1.61.0/iplist/iplist.go (about) 1 // Package iplist handles the P2P Plaintext Format described by 2 // https://en.wikipedia.org/wiki/PeerGuardian#P2P_plaintext_format. 3 package iplist 4 5 import ( 6 "bufio" 7 "bytes" 8 "errors" 9 "fmt" 10 "io" 11 "net" 12 "sort" 13 ) 14 15 // An abstraction of IP list implementations. 16 type Ranger interface { 17 // Return a Range containing the IP. 18 Lookup(net.IP) (r Range, ok bool) 19 // If your ranges hurt, use this. 20 NumRanges() int 21 } 22 23 type IPList struct { 24 ranges []Range 25 } 26 27 type Range struct { 28 First, Last net.IP 29 Description string 30 } 31 32 func (r Range) String() string { 33 return fmt.Sprintf("%s-%s: %s", r.First, r.Last, r.Description) 34 } 35 36 // Create a new IP list. The given ranges must already sorted by the lower 37 // bound IP in each range. Behaviour is undefined for lists of overlapping 38 // ranges. 39 func New(initSorted []Range) *IPList { 40 return &IPList{ 41 ranges: initSorted, 42 } 43 } 44 45 func (ipl *IPList) NumRanges() int { 46 if ipl == nil { 47 return 0 48 } 49 return len(ipl.ranges) 50 } 51 52 // Return the range the given IP is in. ok if false if no range is found. 53 func (ipl *IPList) Lookup(ip net.IP) (r Range, ok bool) { 54 if ipl == nil { 55 return 56 } 57 // TODO: Perhaps all addresses should be converted to IPv6, if the future 58 // of IP is to always be backwards compatible. But this will cost 4x the 59 // memory for IPv4 addresses? 60 v4 := ip.To4() 61 if v4 != nil { 62 r, ok = ipl.lookup(v4) 63 if ok { 64 return 65 } 66 } 67 v6 := ip.To16() 68 if v6 != nil { 69 return ipl.lookup(v6) 70 } 71 if v4 == nil && v6 == nil { 72 r = Range{ 73 Description: "bad IP", 74 } 75 ok = true 76 } 77 return 78 } 79 80 // Return a range that contains ip, or nil. 81 func lookup( 82 first func(i int) net.IP, 83 full func(i int) Range, 84 n int, 85 ip net.IP, 86 ) ( 87 r Range, ok bool, 88 ) { 89 // Find the index of the first range for which the following range exceeds 90 // it. 91 i := sort.Search(n, func(i int) bool { 92 if i+1 >= n { 93 return true 94 } 95 return bytes.Compare(ip, first(i+1)) < 0 96 }) 97 if i == n { 98 return 99 } 100 r = full(i) 101 ok = bytes.Compare(r.First, ip) <= 0 && bytes.Compare(ip, r.Last) <= 0 102 return 103 } 104 105 // Return the range the given IP is in. Returns nil if no range is found. 106 func (ipl *IPList) lookup(ip net.IP) (Range, bool) { 107 return lookup(func(i int) net.IP { 108 return ipl.ranges[i].First 109 }, func(i int) Range { 110 return ipl.ranges[i] 111 }, len(ipl.ranges), ip) 112 } 113 114 func minifyIP(ip *net.IP) { 115 v4 := ip.To4() 116 if v4 != nil { 117 *ip = append(make([]byte, 0, 4), v4...) 118 } 119 } 120 121 // Parse a line of the PeerGuardian Text Lists (P2P) Format. Returns !ok but 122 // no error if a line doesn't contain a range but isn't erroneous, such as 123 // comment and blank lines. 124 func ParseBlocklistP2PLine(l []byte) (r Range, ok bool, err error) { 125 l = bytes.TrimSpace(l) 126 if len(l) == 0 || bytes.HasPrefix(l, []byte("#")) { 127 return 128 } 129 // TODO: Check this when IPv6 blocklists are available. 130 colon := bytes.LastIndexAny(l, ":") 131 if colon == -1 { 132 err = errors.New("missing colon") 133 return 134 } 135 hyphen := bytes.IndexByte(l[colon+1:], '-') 136 if hyphen == -1 { 137 err = errors.New("missing hyphen") 138 return 139 } 140 hyphen += colon + 1 141 r.Description = string(l[:colon]) 142 r.First = net.ParseIP(string(l[colon+1 : hyphen])) 143 minifyIP(&r.First) 144 r.Last = net.ParseIP(string(l[hyphen+1:])) 145 minifyIP(&r.Last) 146 if r.First == nil || r.Last == nil || len(r.First) != len(r.Last) { 147 err = errors.New("bad IP range") 148 return 149 } 150 ok = true 151 return 152 } 153 154 // Creates an IPList from a line-delimited P2P Plaintext file. 155 func NewFromReader(f io.Reader) (ret *IPList, err error) { 156 var ranges []Range 157 // There's a lot of similar descriptions, so we maintain a pool and reuse 158 // them to reduce memory overhead. 159 uniqStrs := make(map[string]string) 160 scanner := bufio.NewScanner(f) 161 lineNum := 1 162 for scanner.Scan() { 163 r, ok, lineErr := ParseBlocklistP2PLine(scanner.Bytes()) 164 if lineErr != nil { 165 err = fmt.Errorf("error parsing line %d: %s", lineNum, lineErr) 166 return 167 } 168 lineNum++ 169 if !ok { 170 continue 171 } 172 if s, ok := uniqStrs[r.Description]; ok { 173 r.Description = s 174 } else { 175 uniqStrs[r.Description] = r.Description 176 } 177 ranges = append(ranges, r) 178 } 179 err = scanner.Err() 180 if err != nil { 181 return 182 } 183 ret = New(ranges) 184 return 185 }