Skip to content

Commit 2d3a268

Browse files
authored
Add support for JSONL formatted feeds (#10)
1 parent 41ba5d8 commit 2d3a268

File tree

5 files changed

+76
-22
lines changed

5 files changed

+76
-22
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ go 1.22.7
55
toolchain go1.23.4
66

77
require (
8-
github.com/ebarkie/netaggr v1.3.3
8+
github.com/ebarkie/netaggr v1.3.9
99
github.com/golang/protobuf v1.5.3
1010
github.com/osrg/gobgp/v3 v3.32.0
1111
github.com/sirupsen/logrus v1.9.3

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4
5555
github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0=
5656
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
5757
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
58-
github.com/ebarkie/netaggr v1.3.3 h1:YXvsoSokMhh51iMHdexgGWDU/cE/OFz//b9ZOQ4Og/E=
59-
github.com/ebarkie/netaggr v1.3.3/go.mod h1:y1qSgnqXXH+UHFRcwgEYEt6yDK8yNc3r6uiX4KAdIAc=
58+
github.com/ebarkie/netaggr v1.3.9 h1:wtBE88jWyDrWERl4NvieV3+bwOHhOM0vJLAACrF8VRw=
59+
github.com/ebarkie/netaggr v1.3.9/go.mod h1:o7CZu4wVQE+jVIFIuRRsW+IC4fc4n1ZkwsP7HBNGJyk=
6060
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
6161
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
6262
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=

gobgp.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ import (
1111
"slices"
1212
"time"
1313

14-
"google.golang.org/protobuf/types/known/anypb"
1514
"github.com/golang/protobuf/ptypes/any"
1615
api "github.com/osrg/gobgp/v3/api"
1716
gobgpconfig "github.com/osrg/gobgp/v3/pkg/config"
1817
"github.com/osrg/gobgp/v3/pkg/config/oc"
1918
"github.com/osrg/gobgp/v3/pkg/server"
2019
log "github.com/sirupsen/logrus"
20+
"google.golang.org/protobuf/types/known/anypb"
2121
)
2222

2323
// NewServer creates, starts, and configures a new BGP server with configFile.

main.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ func main() {
6363
defer stop()
6464
signal.Notify(bh.SigC, os.Signal(syscall.SIGUSR1))
6565

66-
log.WithFields(log.Fields{
67-
"err": bh.UpdateRoutes(ctx),
68-
}).Info("Server stopped")
66+
log.WithError(bh.UpdateRoutes(ctx)).Info("Server stopped")
67+
6968
}

parse.go

Lines changed: 70 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package main
66

77
import (
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

2325
var 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

Comments
 (0)