Keep a scan out of route-proxy's unrouted state: only names under a served domain, a few a minute, no visitor address (issue 774)
mesh/delivery merged: source.main moved to novox/mesh-controller@159db37618c2
mesh/check being carried to b7c3868980f3 on 159db37618c2 | from a4426190b0c7
mesh/delivery merged: source.main moved to novox/mesh-controller@159db37618c2
mesh/check being carried to b7c3868980f3 on 159db37618c2 | from a4426190b0c7
Review of ddae6729: a scanner's random Host or SNI names each became a key, bounded only by the
bucket's lifetime and size, and every failed write was retried at each change and logged each time.
A refused name is now kept by name only under the registrable domain of a name the proxy serves, and
at most five new names a minute; every other refusal is counted in memory and written once a minute
under <node>._other (last minute, since start), which route_proxy_unrouted shows. A refused write of
that state is dropped and logged once until a write succeeds; trimming memory to 4096 names moved
from the request to the writer. The visitor's address is no longer kept: the journal has it.
Quick: drop unrouted keys from strangers by a fixed allow-list. Thorough: bound by the proxy's own
domains, by rate, and by never retrying. Chose thorough: the scan is constant on a public machine.
This commit is contained in:
@@ -79,8 +79,10 @@ func (f front) tools() []stdio.Tool {
|
||||
}},
|
||||
{Name: UnroutedTool,
|
||||
Description: "Names the mesh's web front (route-proxy) answered \"no route for\": a site asked for in a request or " +
|
||||
"a TLS handshake that no route serves, newest first, with when it was last asked, by whom, and how often. " +
|
||||
"A site that should be served and is missing its route shows here. Kept a week. Read-only.",
|
||||
"a TLS handshake that no route serves, newest first, with when it was last asked and how often. Only names under a " +
|
||||
"domain the proxy serves are kept by name, a few new ones a minute; every other refusal (a scan) is counted per " +
|
||||
"proxy, the last minute's count and the total since its start. A site that should be served and is missing " +
|
||||
"its route shows here. Kept a week. Read-only.",
|
||||
Input: map[string]any{"type": "object", "properties": map[string]any{"node": node}},
|
||||
Run: func(args map[string]any) (any, error) {
|
||||
at, _ := args["node"].(string)
|
||||
@@ -224,7 +226,7 @@ func (f front) one(name, node string) (map[string]any, error) {
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
asked, err := read[routestate.Unrouted](f.unrouted, node)
|
||||
asked, _, err := f.asked(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -240,7 +242,7 @@ func (f front) one(name, node string) (map[string]any, error) {
|
||||
}
|
||||
|
||||
func (f front) refusals(node string) (map[string]any, error) {
|
||||
asked, err := read[routestate.Unrouted](f.unrouted, node)
|
||||
asked, others, err := f.asked(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -248,5 +250,47 @@ func (f front) refusals(node string) (map[string]any, error) {
|
||||
if asked == nil {
|
||||
asked = []routestate.Unrouted{}
|
||||
}
|
||||
return map[string]any{"unrouted": asked, "names": len(asked)}, nil
|
||||
return map[string]any{"unrouted": asked, "names": len(asked), "other_refusals": others}, nil
|
||||
}
|
||||
|
||||
// asked is the names refused and kept by name, and each proxy's count of the refusals not kept by name.
|
||||
func (f front) asked(node string) ([]routestate.Unrouted, []routestate.Other, error) {
|
||||
keys, err := f.unrouted.Keys()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
names, others := keySet{}, keySet{}
|
||||
for _, key := range keys {
|
||||
if _, name := routestate.Split(key); name == routestate.OtherName {
|
||||
others[key] = true
|
||||
} else {
|
||||
names[key] = true
|
||||
}
|
||||
}
|
||||
asked, err := read[routestate.Unrouted](only{f.unrouted, names}, node)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
counted, err := read[routestate.Other](only{f.unrouted, others}, node)
|
||||
if counted == nil {
|
||||
counted = []routestate.Other{}
|
||||
}
|
||||
return asked, counted, err
|
||||
}
|
||||
|
||||
// keySet is a set of keys; only is a state narrowed to them.
|
||||
type keySet map[string]bool
|
||||
|
||||
type only struct {
|
||||
reader
|
||||
keys keySet
|
||||
}
|
||||
|
||||
func (o only) Keys() ([]string, error) {
|
||||
out := make([]string, 0, len(o.keys))
|
||||
for k := range o.keys {
|
||||
out = append(out, k)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user