1 // Package iplist handles the P2P Plaintext Format described by
2 // https://en.wikipedia.org/wiki/PeerGuardian#P2P_plaintext_format.
24 func (r *Range) String() string {
25 return fmt.Sprintf("%s-%s (%s)", r.First, r.Last, r.Description)
28 // Create a new IP list. The given ranges must already sorted by the lower
29 // bound IP in each range. Behaviour is undefined for lists of overlapping
31 func New(initSorted []Range) *IPList {
37 func (me *IPList) NumRanges() int {
44 // Return the range the given IP is in. Returns nil if no range is found.
45 func (me *IPList) Lookup(ip net.IP) (r *Range) {
49 // TODO: Perhaps all addresses should be converted to IPv6, if the future
50 // of IP is to always be backwards compatible. But this will cost 4x the
51 // memory for IPv4 addresses?
63 if v4 == nil && v6 == nil {
65 Description: fmt.Sprintf("unsupported IP: %s", ip),
71 // Return the range the given IP is in. Returns nil if no range is found.
72 func (me *IPList) lookup(ip net.IP) (r *Range) {
73 // Find the index of the first range for which the following range exceeds
75 i := sort.Search(len(me.ranges), func(i int) bool {
76 if i+1 >= len(me.ranges) {
79 return bytes.Compare(ip, me.ranges[i+1].First) < 0
81 if i == len(me.ranges) {
85 if bytes.Compare(ip, r.First) < 0 || bytes.Compare(ip, r.Last) > 0 {
91 func minifyIP(ip *net.IP) {
94 *ip = append(make([]byte, 0, 4), v4...)
98 // Parse a line of the PeerGuardian Text Lists (P2P) Format. Returns !ok but
99 // no error if a line doesn't contain a range but isn't erroneous, such as
100 // comment and blank lines.
101 func ParseBlocklistP2PLine(l []byte) (r Range, ok bool, err error) {
102 l = bytes.TrimSpace(l)
103 if len(l) == 0 || bytes.HasPrefix(l, []byte("#")) {
106 // TODO: Check this when IPv6 blocklists are available.
107 colon := bytes.LastIndexAny(l, ":")
109 err = errors.New("missing colon")
112 hyphen := bytes.IndexByte(l[colon+1:], '-')
114 err = errors.New("missing hyphen")
118 r.Description = string(l[:colon])
119 r.First = net.ParseIP(string(l[colon+1 : hyphen]))
121 r.Last = net.ParseIP(string(l[hyphen+1:]))
123 if r.First == nil || r.Last == nil || len(r.First) != len(r.Last) {
124 err = errors.New("bad IP range")
131 // Creates an IPList from a line-delimited P2P Plaintext file.
132 func NewFromReader(f io.Reader) (ret *IPList, err error) {
134 // There's a lot of similar descriptions, so we maintain a pool and reuse
135 // them to reduce memory overhead.
136 uniqStrs := make(map[string]string)
137 scanner := bufio.NewScanner(f)
140 r, ok, lineErr := ParseBlocklistP2PLine(scanner.Bytes())
142 err = fmt.Errorf("error parsing line %d: %s", lineNum, lineErr)
149 if s, ok := uniqStrs[r.Description]; ok {
152 uniqStrs[r.Description] = r.Description
154 ranges = append(ranges, r)