Skip to content
This repository was archived by the owner on Feb 29, 2024. It is now read-only.

Commit b3e1f0f

Browse files
authored
Merge pull request #49 from ties/feature/etags
Use ETags + if-modified-since to reduce number of full requests
2 parents e407106 + 156a388 commit b3e1f0f

File tree

1 file changed

+112
-21
lines changed

1 file changed

+112
-21
lines changed

cmd/gortr/gortr.go

Lines changed: 112 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ var (
7575
PublicKey = flag.String("verify.key", "cf.pub", "Public key path (PEM file)")
7676

7777
CacheBin = flag.String("cache", "https://rpki.cloudflare.com/rpki.json", "URL of the cached JSON data")
78+
Etag = flag.Bool("etag", true, "Enable Etag header")
7879
UserAgent = flag.String("useragent", fmt.Sprintf("Cloudflare-%v (+https://github.com/cloudflare/gortr)", AppVersion), "User-Agent header")
7980
RefreshInterval = flag.Int("refresh", 600, "Refresh interval in seconds")
8081
MaxConn = flag.Int("maxconn", 0, "Max simultaneous connections (0 to disable limit)")
@@ -96,10 +97,24 @@ var (
9697
LastRefresh = prometheus.NewGaugeVec(
9798
prometheus.GaugeOpts{
9899
Name: "rpki_refresh",
99-
Help: "Last refresh.",
100+
Help: "Last successfull request for the given URL.",
100101
},
101102
[]string{"path"},
102103
)
104+
LastChange = prometheus.NewGaugeVec(
105+
prometheus.GaugeOpts{
106+
Name: "rpki_change",
107+
Help: "Last change.",
108+
},
109+
[]string{"path"},
110+
)
111+
RefreshStatusCode = prometheus.NewCounterVec(
112+
prometheus.CounterOpts{
113+
Name: "refresh_requests_total",
114+
Help: "Total number of HTTP requests by status code",
115+
},
116+
[]string{"path", "code"},
117+
)
103118
ClientsMetric = prometheus.NewGaugeVec(
104119
prometheus.GaugeOpts{
105120
Name: "rtr_clients",
@@ -128,7 +143,9 @@ var (
128143

129144
func initMetrics() {
130145
prometheus.MustRegister(NumberOfROAs)
146+
prometheus.MustRegister(LastChange)
131147
prometheus.MustRegister(LastRefresh)
148+
prometheus.MustRegister(RefreshStatusCode)
132149
prometheus.MustRegister(ClientsMetric)
133150
prometheus.MustRegister(PDUsRecv)
134151
}
@@ -138,7 +155,7 @@ func metricHTTP() {
138155
log.Fatal(http.ListenAndServe(*MetricsAddr, nil))
139156
}
140157

141-
func fetchFile(file string, ua string) ([]byte, error) {
158+
func (s *state) fetchFile(file string) ([]byte, error) {
142159
var f io.Reader
143160
var err error
144161
if len(file) > 8 && (file[0:7] == "http://" || file[0:8] == "https://") {
@@ -159,13 +176,18 @@ func fetchFile(file string, ua string) ([]byte, error) {
159176
ProxyConnectHeader: map[string][]string{},
160177
}
161178
// Keep User-Agent in proxy request
162-
tr.ProxyConnectHeader.Set("User-Agent", ua)
179+
tr.ProxyConnectHeader.Set("User-Agent", s.userAgent)
163180

164181
client := &http.Client{Transport: tr}
165182
req, err := http.NewRequest("GET", file, nil)
166-
req.Header.Set("User-Agent", ua)
183+
req.Header.Set("User-Agent", s.userAgent)
167184
req.Header.Set("Accept", "text/json")
168185

186+
etag, ok := s.etags[file]
187+
if s.enableEtags && ok {
188+
req.Header.Set("If-None-Match", etag)
189+
}
190+
169191
proxyurl, err := http.ProxyFromEnvironment(req)
170192
if err != nil {
171193
return nil, err
@@ -181,16 +203,41 @@ func fetchFile(file string, ua string) ([]byte, error) {
181203
if err != nil {
182204
return nil, err
183205
}
206+
207+
RefreshStatusCode.WithLabelValues(file, fmt.Sprintf("%d", fhttp.StatusCode)).Inc()
208+
209+
if fhttp.StatusCode == 304 {
210+
LastRefresh.WithLabelValues(file).Set(float64(s.lastts.UnixNano() / 1e9))
211+
return nil, HttpNotModified{
212+
File: file,
213+
}
214+
} else if fhttp.StatusCode != 200 {
215+
delete(s.etags, file)
216+
return nil, fmt.Errorf("HTTP %s", fhttp.Status)
217+
}
218+
LastRefresh.WithLabelValues(file).Set(float64(s.lastts.UnixNano() / 1e9))
219+
184220
f = fhttp.Body
221+
222+
newEtag := fhttp.Header.Get("ETag")
223+
224+
if !s.enableEtags || newEtag == "" || newEtag != s.etags[file] {
225+
s.etags[file] = newEtag
226+
} else {
227+
return nil, IdenticalEtag{
228+
File: file,
229+
Etag: newEtag,
230+
}
231+
}
185232
} else {
186233
f, err = os.Open(file)
187234
if err != nil {
188235
return nil, err
189236
}
190237
}
191-
data, err2 := ioutil.ReadAll(f)
192-
if err2 != nil {
193-
return nil, err2
238+
data, err := ioutil.ReadAll(f)
239+
if err != nil {
240+
return nil, err
194241
}
195242
return data, nil
196243
}
@@ -236,7 +283,7 @@ func processData(roalistjson []prefixfile.ROAJson) ([]rtr.ROA, int, int, int) {
236283
countv6++
237284
}
238285

239-
key := fmt.Sprintf("%v,%v,%v", prefix, asn, v.Length)
286+
key := fmt.Sprintf("%s,%d,%d", prefix, asn, v.Length)
240287
_, exists := filterDuplicates[key]
241288
if !exists {
242289
filterDuplicates[key] = true
@@ -259,14 +306,32 @@ type IdenticalFile struct {
259306
}
260307

261308
func (e IdenticalFile) Error() string {
262-
return fmt.Sprintf("File %v is identical to the previous version", e.File)
309+
return fmt.Sprintf("File %s is identical to the previous version", e.File)
310+
}
311+
312+
type HttpNotModified struct {
313+
File string
314+
}
315+
316+
func (e HttpNotModified) Error() string {
317+
return fmt.Sprintf("HTTP 304 Not modified for %s", e.File)
318+
}
319+
320+
type IdenticalEtag struct {
321+
File string
322+
Etag string
323+
}
324+
325+
func (e IdenticalEtag) Error() string {
326+
return fmt.Sprintf("File %s is identical according to Etag: %s", e.File, e.Etag)
263327
}
264328

265329
func (s *state) updateFile(file string) error {
266-
log.Debugf("Refreshing cache from %v", file)
267-
data, err := fetchFile(file, s.userAgent)
330+
log.Debugf("Refreshing cache from %s", file)
331+
332+
s.lastts = time.Now().UTC()
333+
data, err := s.fetchFile(file)
268334
if err != nil {
269-
log.Error(err)
270335
return err
271336
}
272337
hsum, _ := checkFile(data)
@@ -277,7 +342,7 @@ func (s *state) updateFile(file string) error {
277342
}
278343
}
279344

280-
s.lastts = time.Now().UTC()
345+
s.lastchange = time.Now().UTC()
281346
s.lastdata = data
282347

283348
roalistjson, err := decodeJSON(s.lastdata)
@@ -291,7 +356,6 @@ func (s *state) updateFile(file string) error {
291356
return errors.New(fmt.Sprintf("File is expired: %v", validtime))
292357
}
293358
}
294-
295359
if s.verify {
296360
log.Debugf("Verifying signature in %v", file)
297361
if roalistjson.Metadata.SignatureDate == "" || roalistjson.Metadata.Signature == "" {
@@ -342,6 +406,7 @@ func (s *state) updateFile(file string) error {
342406
if err != nil {
343407
return err
344408
}
409+
345410
log.Infof("New update (%v uniques, %v total prefixes). %v bytes. Updating sha256 hash %x -> %x",
346411
len(roas), count, len(s.lastconverted), s.lasthash, hsum)
347412
s.lasthash = hsum
@@ -366,16 +431,15 @@ func (s *state) updateFile(file string) error {
366431
countv6_dup++
367432
}
368433
}
369-
s.metricsEvent.UpdateMetrics(countv4, countv6, countv4_dup, countv6_dup, s.lastts, file)
434+
s.metricsEvent.UpdateMetrics(countv4, countv6, countv4_dup, countv6_dup, s.lastchange, s.lastts, file)
370435
}
371436
return nil
372437
}
373438

374439
func (s *state) updateSlurm(file string) error {
375440
log.Debugf("Refreshing slurm from %v", file)
376-
data, err := fetchFile(file, s.userAgent)
441+
data, err := s.fetchFile(file)
377442
if err != nil {
378-
log.Error(err)
379443
return err
380444
}
381445

@@ -404,12 +468,23 @@ func (s *state) routineUpdate(file string, interval int, slurmFile string) {
404468
if slurmFile != "" {
405469
err := s.updateSlurm(slurmFile)
406470
if err != nil {
407-
log.Errorf("Slurm: %v", err)
471+
switch err.(type) {
472+
case HttpNotModified:
473+
log.Info(err)
474+
case IdenticalEtag:
475+
log.Info(err)
476+
default:
477+
log.Errorf("Slurm: %v", err)
478+
}
408479
}
409480
}
410481
err := s.updateFile(file)
411482
if err != nil {
412483
switch err.(type) {
484+
case HttpNotModified:
485+
log.Info(err)
486+
case IdenticalEtag:
487+
log.Info(err)
413488
case IdenticalFile:
414489
log.Info(err)
415490
default:
@@ -431,9 +506,12 @@ type state struct {
431506
lastdata []byte
432507
lastconverted []byte
433508
lasthash []byte
509+
lastchange time.Time
434510
lastts time.Time
435511
sendNotifs bool
436512
userAgent string
513+
etags map[string]string
514+
enableEtags bool
437515

438516
server *rtr.Server
439517

@@ -471,12 +549,12 @@ func (m *metricsEvent) HandlePDU(c *rtr.Client, pdu rtr.PDU) {
471549
"_", -1))).Inc()
472550
}
473551

474-
func (m *metricsEvent) UpdateMetrics(numIPv4 int, numIPv6 int, numIPv4filtered int, numIPv6filtered int, refreshed time.Time, file string) {
552+
func (m *metricsEvent) UpdateMetrics(numIPv4 int, numIPv6 int, numIPv4filtered int, numIPv6filtered int, changed time.Time, refreshed time.Time, file string) {
475553
NumberOfROAs.WithLabelValues("ipv4", "filtered", file).Set(float64(numIPv4filtered))
476554
NumberOfROAs.WithLabelValues("ipv4", "unfiltered", file).Set(float64(numIPv4))
477555
NumberOfROAs.WithLabelValues("ipv6", "filtered", file).Set(float64(numIPv6filtered))
478556
NumberOfROAs.WithLabelValues("ipv6", "unfiltered", file).Set(float64(numIPv6))
479-
LastRefresh.WithLabelValues(file).Set(float64(refreshed.UnixNano() / 1e9))
557+
LastChange.WithLabelValues(file).Set(float64(changed.UnixNano() / 1e9))
480558
}
481559

482560
func ReadPublicKey(key []byte, isPem bool) (*ecdsa.PublicKey, error) {
@@ -563,6 +641,8 @@ func main() {
563641
verify: *Verify,
564642
checktime: *TimeCheck,
565643
userAgent: *UserAgent,
644+
etags: make(map[string]string),
645+
enableEtags: *Etag,
566646
lockJson: &sync.RWMutex{},
567647
}
568648

@@ -708,7 +788,14 @@ func main() {
708788
if slurmFile != "" {
709789
err := s.updateSlurm(slurmFile)
710790
if err != nil {
711-
log.Errorf("Slurm: %v", err)
791+
switch err.(type) {
792+
case HttpNotModified:
793+
log.Info(err)
794+
case IdenticalEtag:
795+
log.Info(err)
796+
default:
797+
log.Errorf("Slurm: %v", err)
798+
}
712799
}
713800
if !*SlurmRefresh {
714801
slurmFile = ""
@@ -718,8 +805,12 @@ func main() {
718805
err := s.updateFile(*CacheBin)
719806
if err != nil {
720807
switch err.(type) {
808+
case HttpNotModified:
809+
log.Info(err)
721810
case IdenticalFile:
722811
log.Info(err)
812+
case IdenticalEtag:
813+
log.Info(err)
723814
default:
724815
log.Errorf("Error updating: %v", err)
725816
}

0 commit comments

Comments
 (0)