From 0ac8a9abef021cbd45d362135c8fc25174d752f7 Mon Sep 17 00:00:00 2001
From: Lukas Tribus <lukas@ltri.eu>
Date: Tue, 2 Sep 2025 18:31:24 +0000
Subject: Avoid sending stale data, properly handle errRPKIJsonFileTooOld

Both updateFromNewState() and reloadFromCurrentState() can return
errRPKIJsonFileTooOld.

However the RTR clearing code that would withdraw all VRPs is only in the call
site of updateFromNewState(), which is not called when the JSON file is stuck,
because CacheUpdated is false (as updateFile() returns false, IdenticalFile).

That is why with a hung RPKI validator and logging turned up to info we see:

File /var/lib/rpki-client/json is identical to the previous version
RPKI JSON file is older than 24 hours: 2025-09-01 16:29:30 +0000 UTC
Error updating state: RPKI JSON file is older than 24 hours

But no clearing of the VRPs occurs, because the reloadFromCurrentState()
caller doesn't handle errRPKIJsonFileTooOld specifically and just prints
an error.

Move errRPKIJsonFileTooOld handling into it's own function and call the
handler from both call sites. Although I'm not sure about the use-case for
updateFromNewState(), if it is returning errRPKIJsonFileTooOld I want to
handle it properly. I also want to avoid duplicate handling code, because
that will bite us again in the future if this code ever changes.

diff --git a/cmd/stayrtr/stayrtr.go b/cmd/stayrtr/stayrtr.go
index c712b59..510de70 100644
--- a/cmd/stayrtr/stayrtr.go
+++ b/cmd/stayrtr/stayrtr.go
@@ -539,6 +539,16 @@ func (s *state) updateDelay(delay *time.Ticker, interval int) {
 	}
 }
 
+func (s *state) errRPKIJsonFileTooOldHandler() {
+	// If the exiting build time is over 24 hours, It's time to drop everything out.
+	// to avoid routing on stale data
+	buildTime := s.exported.Metadata.GetBuildTime()
+	if !buildTime.IsZero() && time.Since(buildTime) > time.Hour*24 {
+		log.Errorf("Data is stale, clearing it all.")
+		s.server.AddData([]rtr.SendableData{}) // empty the store of sendable stuff, triggering a emptying of the RTR server
+	}
+}
+
 func (s *state) routineUpdate(file string, interval int, slurmFile string) {
 	log.Debugf("Starting refresh routine (file: %v, interval: %vs, slurm: %v)", file, interval, slurmFile)
 	signals := make(chan os.Signal, 1)
@@ -612,20 +622,18 @@ func (s *state) routineUpdate(file string, interval int, slurmFile string) {
 		if cacheUpdated || slurmNotPresentOrUpdated {
 			err := s.updateFromNewState()
 			if err != nil {
+				log.Errorf("Error updating from new state: %v", err)
 				if err == errRPKIJsonFileTooOld {
-					// If the exiting build time is over 24 hours, It's time to drop everything out.
-					// to avoid routing on stale data
-					buildTime := s.exported.Metadata.GetBuildTime()
-					if !buildTime.IsZero() && time.Since(buildTime) > time.Hour*24 {
-						s.server.AddData([]rtr.SendableData{}) // empty the store of sendable stuff, triggering a emptying of the RTR server
-					}
+					s.errRPKIJsonFileTooOldHandler()
 				}
-				log.Errorf("Error updating from new state: %v", err)
 			}
 		} else {
 			err := s.reloadFromCurrentState()
 			if err != nil {
-				log.Errorf("Error updating state: %v", err)
+				log.Errorf("Error updating from current state: %v", err)
+				if err == errRPKIJsonFileTooOld {
+					s.errRPKIJsonFileTooOldHandler()
+				}
 			}
 		}
 	}
