Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bdecf2f34 |
No files matched your search
+81
-2
@@ -171,9 +171,11 @@ type alertsFile struct {
|
|||||||
// check refuses the first entry without a field it needs, which would
|
// check refuses the first entry without a field it needs, which would
|
||||||
// otherwise be read as something the entry does not say. data is the
|
// otherwise be read as something the entry does not say. data is the
|
||||||
// file, for a field that may be null or "" but not left out, which the
|
// file, for a field that may be null or "" but not left out, which the
|
||||||
// struct cannot tell apart.
|
// struct cannot tell apart. Its toUTC then converts every time in it to
|
||||||
|
// UTC, since an admin may write a time with an offset.
|
||||||
type stateFile interface {
|
type stateFile interface {
|
||||||
check(data []byte) error
|
check(data []byte) error
|
||||||
|
toUTC()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load checks that files can be written in Dir, and reads the state files
|
// Load checks that files can be written in Dir, and reads the state files
|
||||||
@@ -918,6 +920,80 @@ func missing(i int, field string) error {
|
|||||||
return fmt.Errorf("entry %d %w %q", i+1, errMissing, field)
|
return fmt.Errorf("entry %d %w %q", i+1, errMissing, field)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// toUTC converts every time of each ban to UTC.
|
||||||
|
func (f *bansFile) toUTC() {
|
||||||
|
for i := range f.Bans {
|
||||||
|
ban := &f.Bans[i]
|
||||||
|
convertToUTC(&ban.Start, ban.Expires, ban.Lifted, &ban.Notes.Request.Time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// toUTC converts every time of each client to UTC.
|
||||||
|
func (f *clientsFile) toUTC() {
|
||||||
|
for i := range f.Clients {
|
||||||
|
client := &f.Clients[i]
|
||||||
|
convertToUTC(&client.Minute.Start, &client.Hour.Start, &client.Day.Start,
|
||||||
|
&client.MinuteBytes.Start, &client.HourBytes.Start,
|
||||||
|
&client.DayBytes.Start, &client.MinuteRefusals.Start,
|
||||||
|
&client.History.FirstSeen, &client.History.LastSeen,
|
||||||
|
&client.History.LookedUp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// toUTC converts both times of each answer to UTC.
|
||||||
|
func (f *lookupsFile) toUTC() {
|
||||||
|
for i := range f.Lookups {
|
||||||
|
convertToUTC(&f.Lookups[i].Answered, &f.Lookups[i].Used)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// toUTC converts every time of each list, verdict and AbuseIPDB score to
|
||||||
|
// UTC.
|
||||||
|
func (f *reputationFile) toUTC() {
|
||||||
|
for i := range f.Lists {
|
||||||
|
convertToUTC(&f.Lists[i].Tried, &f.Lists[i].Fetched)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range f.Verdicts {
|
||||||
|
convertToUTC(&f.Verdicts[i].Fetched)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range f.AbuseIPDB.Scores {
|
||||||
|
convertToUTC(&f.AbuseIPDB.Scores[i].Fetched)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// toUTC converts every time of each cooldown, of the hour, of each alert
|
||||||
|
// waiting and of each anomaly counter to UTC.
|
||||||
|
func (f *alertsFile) toUTC() {
|
||||||
|
for i := range f.Cooldowns {
|
||||||
|
convertToUTC(&f.Cooldowns[i].Sent)
|
||||||
|
}
|
||||||
|
|
||||||
|
convertToUTC(&f.Hour.Start)
|
||||||
|
|
||||||
|
for _, waiting := range f.Waiting {
|
||||||
|
for i := range waiting {
|
||||||
|
convertToUTC(&waiting[i].Time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range f.AnomalyCounters {
|
||||||
|
counter := &f.AnomalyCounters[i]
|
||||||
|
convertToUTC(&counter.Minute.Start, &counter.Hour.Start,
|
||||||
|
&counter.MinuteBytes.Start, &counter.HourBytes.Start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertToUTC converts each of times that is not nil to UTC.
|
||||||
|
func convertToUTC(times ...*time.Time) {
|
||||||
|
for _, at := range times {
|
||||||
|
if at != nil {
|
||||||
|
*at = at.UTC()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// encodeOnePerLine encodes a state file whose entries, under key, are one
|
// encodeOnePerLine encodes a state file whose entries, under key, are one
|
||||||
// to a line, so that grep shows everything about one client.
|
// to a line, so that grep shows everything about one client.
|
||||||
func encodeOnePerLine[E any](key string, entries []E) ([]byte, error) {
|
func encodeOnePerLine[E any](key string, entries []E) ([]byte, error) {
|
||||||
@@ -955,7 +1031,8 @@ func checkWritable(dir string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse reads data, what the state file at path holds, into file, a
|
// parse reads data, what the state file at path holds, into file, a
|
||||||
// pointer to that file's struct, and checks its entries.
|
// pointer to that file's struct, checks its entries, and converts every
|
||||||
|
// time in it to UTC.
|
||||||
func parse(path string, data []byte, file stateFile) error {
|
func parse(path string, data []byte, file stateFile) error {
|
||||||
// The version is read first, so that a file of another version is
|
// The version is read first, so that a file of another version is
|
||||||
// refused for that, and not for an entry this version cannot read.
|
// refused for that, and not for an entry this version cannot read.
|
||||||
@@ -985,6 +1062,8 @@ func parse(path string, data []byte, file stateFile) error {
|
|||||||
return fmt.Errorf("%s%s: %w", path, position(data, err), err)
|
return fmt.Errorf("%s%s: %w", path, position(data, err), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
file.toUTC()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -1351,6 +1352,62 @@ func TestEditOfEachFileTakenIn(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTimesEditedWithAnOffsetWrittenBackInUTC(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
params := newParams(dir)
|
||||||
|
fill(params)
|
||||||
|
// A lifted ban, so that bans.json holds every time a ban has.
|
||||||
|
params.Ledger.Lift(netip.MustParseAddr("192.0.2.1"), midnight())
|
||||||
|
|
||||||
|
files := load(t, params)
|
||||||
|
|
||||||
|
err := files.WriteAll()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("write: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An admin saves each file with every time in it two hours ahead of
|
||||||
|
// UTC, midnight as "2026-10-06T02:00:00+02:00".
|
||||||
|
stamp := regexp.MustCompile(`"\d{4}-\d\d-\d\dT[^"]*Z"`)
|
||||||
|
ahead := time.FixedZone("UTC+2", 2*60*60)
|
||||||
|
written := map[string]string{}
|
||||||
|
|
||||||
|
for _, name := range []string{
|
||||||
|
bansJSON, clientsJSON, lookupsJSON, reputationJSON, alertsJSON,
|
||||||
|
} {
|
||||||
|
written[name] = readFile(t, filepath.Join(dir, name))
|
||||||
|
edited := stamp.ReplaceAllStringFunc(written[name], func(quoted string) string {
|
||||||
|
at, err := time.Parse(time.RFC3339Nano, strings.Trim(quoted, `"`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s holds %s: %v", name, quoted, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return `"` + at.In(ahead).Format(time.RFC3339Nano) + `"`
|
||||||
|
})
|
||||||
|
|
||||||
|
if !strings.Contains(edited, "+02:00") {
|
||||||
|
t.Fatalf("%s holds no time:\n%s", name, edited)
|
||||||
|
}
|
||||||
|
|
||||||
|
edit(t, dir, name, edited)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each write takes its edit in first, and writes the same times back in
|
||||||
|
// UTC.
|
||||||
|
err = files.WriteAll()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("write: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, want := range written {
|
||||||
|
if got := readFile(t, filepath.Join(dir, name)); got != want {
|
||||||
|
t.Errorf("%s written back as\n%s\nwant\n%s", name, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestOwnWritesAreNotTakenIn(t *testing.T) {
|
func TestOwnWritesAreNotTakenIn(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
// The time zone database, so that the process TestTimesAreInUTCWhateverTZSays
|
// The time zone database, so that the process TestMainRunsDiscriminator
|
||||||
// starts finds the zone TZ names on any host.
|
// starts finds the zone TZ names on any host.
|
||||||
_ "time/tzdata"
|
_ "time/tzdata"
|
||||||
|
|
||||||
@@ -41,53 +41,7 @@ func TestMain(m *testing.M) {
|
|||||||
func TestMainRunsDiscriminator(t *testing.T) {
|
func TestMainRunsDiscriminator(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
binary, err := os.Executable()
|
// Kiritimati is 14 hours ahead of UTC; every time sww gives stays in UTC.
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("find the test binary: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(t.Context(), 3*waitLimit)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
out := &output{}
|
|
||||||
cmd := exec.CommandContext(ctx, binary) //nolint:gosec // this test binary
|
|
||||||
cmd.Stdout = out
|
|
||||||
|
|
||||||
cmd.Env = append(os.Environ(),
|
|
||||||
commandForMain+"="+discriminatorCommand,
|
|
||||||
listenAddr+"="+localhost+":0",
|
|
||||||
stateDir+"="+t.TempDir(),
|
|
||||||
rulesDir+"="+t.TempDir(),
|
|
||||||
lookupSource+"=off")
|
|
||||||
|
|
||||||
err = cmd.Start()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("start: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
_ = cmd.Process.Kill()
|
|
||||||
_ = cmd.Wait()
|
|
||||||
})
|
|
||||||
|
|
||||||
addr, _ := out.line(t, "msg", "starting")["address"].(string)
|
|
||||||
ask(t, addr, "/", http.StatusOK, discriminator.DispositionAllow)
|
|
||||||
|
|
||||||
err = cmd.Process.Signal(syscall.SIGTERM)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("signal: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cmd.Wait()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("exit: %v; output:\n%s", err, out.text())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimesAreInUTCWhateverTZSays(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Kiritimati is 14 hours ahead of UTC.
|
|
||||||
const zone = "Pacific/Kiritimati"
|
const zone = "Pacific/Kiritimati"
|
||||||
|
|
||||||
_, err := time.LoadLocation(zone)
|
_, err := time.LoadLocation(zone)
|
||||||
@@ -163,7 +117,8 @@ func TestTimesAreInUTCWhateverTZSays(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// wantUTC checks that value, a time where, is in UTC.
|
// wantUTC checks that value, a time read from the log line or file that
|
||||||
|
// where names, is in UTC.
|
||||||
func wantUTC(t *testing.T, where string, value any) {
|
func wantUTC(t *testing.T, where string, value any) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user