55package main
66
77import (
8+ "encoding/json"
89 "errors"
910 "fmt"
1011 "io"
@@ -13,6 +14,7 @@ import (
1314 "net/url"
1415 "os"
1516 "sort"
17+ "strings"
1618 "sync"
1719
1820 "github.com/ebarkie/netaggr/pkg/netcalc"
@@ -22,30 +24,45 @@ import (
2224
2325var ErrUnhandledScheme = errors .New ("unhandled scheme" )
2426
25- // readFeed determines a feed's scheme and creates an appropriate io.ReadCloser .
26- func readFeed (feed string ) (io. ReadCloser , error ) {
27+ // readFeed reads a feed and returns the number of networks .
28+ func readFeed (feed string , netC chan <- * net. IPNet ) (int64 , error ) {
2729 u , err := url .Parse (feed )
2830 if err != nil {
29- return nil , err
31+ return 0 , err
3032 }
3133
3234 switch u .Scheme {
3335 case "" :
34- return os .Open (feed )
36+ r , err := os .Open (feed )
37+ if err != nil {
38+ return 0 , err
39+ }
40+ defer r .Close ()
41+
42+ if strings .HasSuffix (feed , ".json" ) {
43+ return readFromJSON (r , netC )
44+ } else {
45+ return netcalc .ReadFrom (r , netC )
46+ }
3547 case "http" , "https" :
3648 resp , err := http .Get (feed )
3749 if err != nil {
38- return nil , err
50+ return 0 , err
3951 }
52+ defer resp .Body .Close ()
4053
4154 if resp .StatusCode != http .StatusOK {
42- return nil , fmt .Errorf ("non-OK status code: %d" , resp .StatusCode )
55+ return 0 , fmt .Errorf ("non-OK status code: %d" , resp .StatusCode )
4356 }
4457
45- return resp .Body , nil
58+ if strings .Contains (resp .Header .Get ("Content-Type" ), "/json" ) {
59+ return readFromJSON (resp .Body , netC )
60+ } else {
61+ return netcalc .ReadFrom (resp .Body , netC )
62+ }
4663 }
4764
48- return nil , ErrUnhandledScheme
65+ return 0 , fmt . Errorf ( "%w: %s" , ErrUnhandledScheme , u . Scheme )
4966}
5067
5168// parseFeeds parses feeds concurrently and returns summarized nets and the
@@ -64,22 +81,19 @@ func parseFeeds(feeds ...string) (nets netcalc.Nets, totalNets int) {
6481 go func (feed string ) {
6582 defer wg .Done ()
6683
67- r , err := readFeed (feed )
84+ i , err := readFeed (feed , netC )
6885 if err != nil {
6986 log .WithFields (log.Fields {
70- "err " : err ,
71- }).Error ("Feed read error" )
87+ "feed " : feed ,
88+ }).WithError ( err ). Error ("Feed read error" )
7289 return
7390 }
74- defer r .Close ()
7591
76- i , err := netcalc .ReadFrom (r , netC )
7792 if err != nil {
7893 log .WithFields (log.Fields {
7994 "feed" : feed ,
8095 "nets" : i ,
81- "err" : err ,
82- }).Error ("Feed parse error" )
96+ }).WithError (err ).Error ("Feed parse error" )
8397 return
8498 }
8599
@@ -102,3 +116,44 @@ func parseFeeds(feeds ...string) (nets netcalc.Nets, totalNets int) {
102116
103117 return
104118}
119+
120+ // sblEntry represents a Spamhaus Block List entry.
121+ type sblEntry struct {
122+ // IP network to drop, in CIDR format.
123+ CIDR string `json:"cidr"`
124+
125+ // Regional Internet Registry that manages the network.
126+ RIR string `json:"rir"`
127+
128+ // Spamhaus Block List identifier.
129+ SBLID string `json:"sblid"`
130+ }
131+
132+ // readFromJSON parses the io.Reader as Spamhaus formatted JSONL and sends the
133+ // resulting IPNets to the NetC channel.
134+ func readFromJSON (r io.Reader , netC chan <- * net.IPNet ) (int64 , error ) {
135+ d := json .NewDecoder (r )
136+ var i int64
137+ for ; ; i ++ {
138+ var e sblEntry
139+ err := d .Decode (& e )
140+ if err == io .EOF {
141+ break
142+ } else if err != nil {
143+ return i , fmt .Errorf ("line %d decode: %w" , i , err )
144+ }
145+ if e .CIDR == "" {
146+ // Probably the footer line
147+ continue
148+ }
149+
150+ _ , n , err := netcalc .ParseNet (e .CIDR )
151+ if err != nil {
152+ return i , fmt .Errorf ("line %d parse %q: %w" , i , e .CIDR , err )
153+ }
154+
155+ netC <- n
156+ }
157+
158+ return i , nil
159+ }
0 commit comments