mirror of
https://github.com/urbit/shrub.git
synced 2024-12-25 13:04:17 +03:00
468 lines
14 KiB
Plaintext
468 lines
14 KiB
Plaintext
:: Ping our sponsorship tree regularly for routing.
|
|
::
|
|
:: To traverse NAT, we need the response to come back from someone
|
|
:: we've sent a message to. We ping our sponsor so that they know
|
|
:: where we are. However, we also need to ping our galaxy because if
|
|
:: the other ship tries to respond directly, it may be blocked by our
|
|
:: firewall or NAT. Thus, the response must come from a ship we've
|
|
:: messaged directly, and the only one we can guarantee is our galaxy.
|
|
:: Note this issue manifests itself even for bootstrapping a planet to
|
|
:: talk to its own star.
|
|
::
|
|
/+ default-agent, verb, dbug
|
|
=* point point:kale
|
|
::
|
|
|%
|
|
:: How often to ping our sponsor when we might be behind a NAT.
|
|
::
|
|
:: NAT timeouts are often pretty short for UDP entries. 5 minutes is
|
|
:: a common value. We use 25 seconds, same as Wireguard.
|
|
::
|
|
++ nat-timeout ~s25
|
|
::
|
|
:: How often to check our IP when we know we're not behind a NAT.
|
|
::
|
|
++ ip-timeout ~m5
|
|
::
|
|
:: Chosen because it's run by Cloudflare, and others I tried were
|
|
:: inconsistently slow.
|
|
::
|
|
++ ip-reflector 'https://icanhazip.com'
|
|
::
|
|
+$ card card:agent:gall
|
|
+$ ship-state
|
|
$% [%idle ~]
|
|
[%poking ~]
|
|
[%http until=@da]
|
|
[%waiting until=@da]
|
|
==
|
|
+$ state-2
|
|
$: %2
|
|
ships=(set ship)
|
|
nonce=@ud
|
|
$= plan
|
|
$~ [%nat ~]
|
|
$% [%nat ~]
|
|
[%pub ip=(unit @t)]
|
|
[%off ~]
|
|
[%one ~]
|
|
==
|
|
==
|
|
--
|
|
::
|
|
%- agent:dbug
|
|
::
|
|
=| state=state-2
|
|
=> |%
|
|
:: Bind for the the writer monad on (quip effect state)
|
|
::
|
|
++ rind
|
|
|* [effect=mold state=*]
|
|
|* state-type=mold
|
|
|= $: m-b=(quip effect state-type)
|
|
fun=$-(state-type (quip effect state-type))
|
|
==
|
|
^- (quip effect state-type)
|
|
=^ effects-1=(list effect) state m-b
|
|
=^ effects-2=(list effect) state (fun state)
|
|
[(weld effects-1 effects-2) state]
|
|
::
|
|
++ once
|
|
|= =cord
|
|
=(cord (scot %uw nonce.state))
|
|
::
|
|
:: Subsystem to keep track of which ships to ping across breaches
|
|
:: and sponsorship changes
|
|
::
|
|
++ ships
|
|
=| force=_|
|
|
|%
|
|
++ rind (^rind card state)
|
|
++ kick
|
|
|= [our=@p now=@da]
|
|
^- (quip card _state)
|
|
:: ?: =(%czar (clan:title our))
|
|
:: `state
|
|
::
|
|
:: NB: !! This includes our own ship, and for moons, this is
|
|
:: what has caused Jael to fetch our own rift from our parent.
|
|
:: This role may be taken by Ames's subscription to
|
|
:: %public-keys, but this must be tested before changing the
|
|
:: behavior here.
|
|
::
|
|
=/ new-ships (~(gas in *(set ship)) (saxo:title our now our))
|
|
=/ removed (~(dif in ships.state) new-ships)
|
|
=/ added (~(dif in new-ships) ships.state)
|
|
;< new-state=_state rind
|
|
?~ removed `state
|
|
[[%pass /jael %arvo %j %nuke removed]~ state]
|
|
=. state new-state
|
|
::
|
|
;< new-state=_state rind
|
|
?~ added `state
|
|
[[%pass /jael %arvo %j %public-keys added]~ state]
|
|
=. state new-state
|
|
::
|
|
:: Kick even if ships weren't added or removed
|
|
::
|
|
(kick-pings our now new-ships force)
|
|
::
|
|
:: Kick whenever we get a response. We really care about
|
|
:: breaches and sponsorship changes.
|
|
::
|
|
:: Delay until next event in case of breach, so that ames can
|
|
:: clear its state.
|
|
::
|
|
++ take-jael
|
|
|= now=@da
|
|
^- (quip card _state)
|
|
[[%pass /jael/delay %arvo %b %wait now]~ state]
|
|
::
|
|
++ take-delay %*(kick ships force %.y)
|
|
--
|
|
::
|
|
:: Starts pinging a new set of `ships`.
|
|
::
|
|
++ kick-pings
|
|
|= [our=@p now=@da ships=(set ship) force=?]
|
|
^- (quip card _state)
|
|
=: nonce.state +(nonce.state)
|
|
ships.state ships
|
|
==
|
|
::
|
|
?: force (kick:nat our)
|
|
?- -.plan.state
|
|
%off `state
|
|
%nat (kick:nat our)
|
|
%one (kick:one our)
|
|
%pub (kick:pub our now)
|
|
==
|
|
::
|
|
:: Subsystem for pinging our sponsors when we might be behind a NAT
|
|
:: XX no longer true if using STUN-enabled vere 2.XX
|
|
:: Ping each ship every 25 seconds to keep the pinhole open.
|
|
:: This is expensive, but if you don't do it and you are behind a
|
|
:: NAT, you will stop receiving packets from other ships except
|
|
:: during the 30 seconds following each packet you send.
|
|
::
|
|
++ nat
|
|
?> ?=(%nat -.plan.state)
|
|
|%
|
|
++ rind (^rind card state)
|
|
++ kick
|
|
|= our=@p
|
|
^- (quip card _state)
|
|
=/ ships ~(tap in ships.state)
|
|
|- ^- (quip card _state)
|
|
?~ ships `state
|
|
?: =(our i.ships) $(ships t.ships)
|
|
;< new-state=_state rind (send-ping i.ships)
|
|
=. state new-state
|
|
$(ships t.ships)
|
|
::
|
|
++ send-ping
|
|
|= =ship
|
|
^- (quip card _state)
|
|
:_ state
|
|
=/ wire /nat/(scot %uw nonce.state)/ping/(scot %p ship)
|
|
[%pass wire %agent [ship %ping] %poke %noun !>(~)]~
|
|
::
|
|
++ take-ping
|
|
|= [now=@da =wire error=(unit tang)]
|
|
^- (quip card _state)
|
|
?. ?=([%nat @ %ping @ ~] wire) `state
|
|
?. (once i.t.wire) `state
|
|
=/ ship (slav %p i.t.t.t.wire)
|
|
%- (slog ?~(error ~ ['ping: got nack' >ship< u.error]))
|
|
:_ state
|
|
=/ wire /nat/(scot %uw nonce.state)/wait/(scot %p ship)
|
|
[%pass wire %arvo %b %wait (add nat-timeout now)]~
|
|
::
|
|
++ take-wait
|
|
|= =wire
|
|
^- (quip card _state)
|
|
?. ?=([%nat @ %wait @ ~] wire) `state
|
|
?. (once i.t.wire) `state
|
|
=/ ship (slav %p i.t.t.t.wire)
|
|
(send-ping ship)
|
|
--
|
|
::
|
|
:: Subsystem for pinging our sponsors when we know we're not behind a NAT
|
|
::
|
|
:: Check our IP address every minute, and only if it changes,
|
|
:: ping all our sponsors.
|
|
::
|
|
++ pub
|
|
?> ?=(%pub -.plan.state)
|
|
|%
|
|
++ rind (^rind card state)
|
|
++ kick
|
|
|= [our=@p now=@da]
|
|
^- (quip card _state)
|
|
;< new-state=_state rind (send-pings our)
|
|
=. state new-state
|
|
::
|
|
;< new-state=_state rind check-ip
|
|
=. state new-state
|
|
::
|
|
(set-timer now)
|
|
::
|
|
++ send-pings
|
|
|= our=@p
|
|
^- (quip card _state)
|
|
:_ state
|
|
%+ murn ~(tap in ships.state)
|
|
|= =ship
|
|
?: =(our ship)
|
|
~
|
|
=/ wire /pub/(scot %uw nonce.state)/ping/(scot %p ship)
|
|
`u=[%pass wire %agent [ship %ping] %poke %noun !>(~)]
|
|
::
|
|
++ take-pings
|
|
|= [=wire error=(unit tang)]
|
|
^- (quip card _state)
|
|
?. ?=([%pub @ %ping @ ~] wire) `state
|
|
?. (once i.t.wire) `state
|
|
=/ ship (slav %p i.t.t.t.wire)
|
|
%- (slog ?~(error ~ ['ping: got nack' >ship< u.error]))
|
|
`state
|
|
::
|
|
++ check-ip
|
|
^- (quip card _state)
|
|
:_ state
|
|
=/ wire /pub/(scot %uw nonce.state)/ip
|
|
=/ =request:http [%'GET' ip-reflector ~ ~]
|
|
[%pass wire %arvo %i %request request *outbound-config:iris]~
|
|
::
|
|
++ take-ip
|
|
|= [our=@p =wire resp=client-response:iris]
|
|
^- (quip card _state)
|
|
?. ?=([%pub @ %ip ~] wire) `state
|
|
?. (once i.t.wire) `state
|
|
::
|
|
?. ?=(%finished -.resp) `state :: will retry in a minute
|
|
?. ?=(%200 status-code.response-header.resp)
|
|
=* s status-code.response-header.resp
|
|
%- (slog leaf+"ping: ip check failed: {<s>}" ~)
|
|
`state
|
|
::
|
|
?~ full-file.resp
|
|
%- (slog 'ping: ip check body empty' ~)
|
|
`state
|
|
::
|
|
=* body q.data.u.full-file.resp
|
|
?~ body
|
|
%- (slog 'ping: ip check body empty' ~)
|
|
`state
|
|
::
|
|
=/ ip (end [3 (dec (met 3 body))] body)
|
|
?: =(ip.plan.state `ip) `state
|
|
::
|
|
=. ip.plan.state `ip
|
|
(send-pings our)
|
|
::
|
|
++ set-timer
|
|
|= now=@da
|
|
^- (quip card _state)
|
|
=/ =wire /pub/(scot %uw nonce.state)/wait
|
|
[[%pass wire %arvo %b %wait (add ip-timeout now)]~ state]
|
|
::
|
|
++ take-wait
|
|
|= [our=@p now=@da =wire]
|
|
^- (quip card _state)
|
|
?. ?=([%pub @ %wait ~] wire) `state
|
|
?. (once i.t.wire) `state
|
|
;< new-state=_state rind check-ip
|
|
=. state new-state
|
|
::
|
|
(set-timer now)
|
|
--
|
|
:: Subsystem for formally acknowledging a change in our IP:PORT
|
|
::
|
|
:: If our sponsor sends a STUN response, with an IP different than what
|
|
:: we had previously cached, we formally acknowledge this change by
|
|
:: sending one %poke to every ship in the sponsorship chain.
|
|
::
|
|
++ one
|
|
?> ?=(%one -.plan.state)
|
|
|%
|
|
++ kick
|
|
|= our=@p
|
|
^- (quip card _state)
|
|
:_ state
|
|
%- ~(rep in ships.state)
|
|
|= [=ship cards=(list card)]
|
|
?: =(our ship) cards
|
|
=/ wire /one/(scot %uw nonce.state)/ping/(scot %p ship)
|
|
:_ cards ^- card
|
|
[%pass wire %agent [ship %ping] %poke %noun !>(~)]
|
|
--
|
|
--
|
|
%+ verb |
|
|
^- agent:gall
|
|
|_ =bowl:gall
|
|
+* this .
|
|
def ~(. (default-agent this %|) bowl)
|
|
::
|
|
:: +on-init: initializing on startup
|
|
::
|
|
++ on-init
|
|
^- [(list card) _this]
|
|
=. plan.state [%nat ~]
|
|
=^ cards state (kick:ships our.bowl now.bowl)
|
|
[cards this]
|
|
::
|
|
++ on-save !>(state)
|
|
++ on-load
|
|
|= old-vase=vase
|
|
|^
|
|
=/ old !<(state-any old-vase)
|
|
=? old ?=(%0 -.old) (state-0-to-1 old)
|
|
=? old ?=(%1 -.old) (state-1-to-2 old)
|
|
?> ?=(%2 -.old)
|
|
=. state old
|
|
=^ cards state (kick:ships our.bowl now.bowl)
|
|
[cards this]
|
|
::
|
|
+$ state-any $%(state-0 state-1 state-2)
|
|
+$ state-0 [%0 ships=(map ship [=rift =ship-state])]
|
|
+$ state-1
|
|
$: %1
|
|
ships=(set ship)
|
|
nonce=@ud
|
|
$= plan
|
|
$~ [%nat ~]
|
|
$% [%nat ~]
|
|
[%pub ip=(unit @t)]
|
|
== ==
|
|
::
|
|
++ state-0-to-1
|
|
|= old=state-0
|
|
^- state-1
|
|
[%1 ~ 0 %nat ~]
|
|
::
|
|
++ state-1-to-2
|
|
|= old=state-1
|
|
^- state-2
|
|
old(- %2)
|
|
--
|
|
:: +on-poke: positively acknowledge pokes
|
|
::
|
|
++ on-poke
|
|
|= [=mark =vase]
|
|
?. =(our src):bowl :: don't crash, this is where pings are handled
|
|
`this
|
|
::
|
|
=^ cards state
|
|
?: ?=([%kick ?] q.vase)
|
|
:: NB: ames calls this on %born (with fail=%.n) and after not hearing STUN
|
|
:: responses for more than ~s5 (with fail=%.y)
|
|
::
|
|
:: if %ping was turned off (due to a successfull STUN) but we failed
|
|
:: to get a STUN response in time switch to %nat and start a ~s25 timer
|
|
::
|
|
:: if the %kick has fail=%.n (e.g. for every %born), the plan will remain
|
|
:: unchanged, but we will innitiate a new round of %poke pings with
|
|
:: increasing nonce.
|
|
::
|
|
:: if we get repeated [%stun fail=&], but we are already in either %nat
|
|
:: or %pub, do nothing, since there are already timers in place to %ping
|
|
:: repeatedly.
|
|
::
|
|
=/ stun-failed=? &(?=([%off ~] plan.state) =(+.q.vase %.y))
|
|
?: &(?=([%off ~] plan.state) =(+.q.vase %.n))
|
|
:: ignore restarts if we were already STUNning, if ip:port changed
|
|
:: %once will trigger one formal %ping
|
|
::
|
|
`state
|
|
=? plan.state stun-failed
|
|
[%nat ~]
|
|
?: &(!stun-failed =(+.q.vase %.y))
|
|
`state
|
|
(kick:ships our.bowl now.bowl)
|
|
?: =(q.vase %stop) :: NB: ames calls this on [%stun fail=%.n]
|
|
=. plan.state [%off ~]
|
|
(kick:ships our.bowl now.bowl)
|
|
?: &(=(q.vase %once) =(%off -.plan.state)) :: NB: ames calls this on %once
|
|
=. plan.state [%one ~]
|
|
(kick:ships our.bowl now.bowl)
|
|
?: =(q.vase %nat)
|
|
=. plan.state [%nat ~]
|
|
(kick:ships our.bowl now.bowl)
|
|
?: =(q.vase %no-nat)
|
|
=. plan.state [%pub ~]
|
|
(kick:ships our.bowl now.bowl)
|
|
`state
|
|
[cards this]
|
|
::
|
|
++ on-watch on-watch:def
|
|
++ on-leave on-leave:def
|
|
++ on-peek
|
|
|= =path
|
|
^- (unit (unit cage))
|
|
``noun+!>(state)
|
|
:: +on-agent: handle ames ack
|
|
::
|
|
++ on-agent
|
|
|= [=wire =sign:agent:gall]
|
|
^- [(list card) _this]
|
|
=^ cards state
|
|
?+ wire `state
|
|
[%nat *]
|
|
?. ?=(%nat -.plan.state) `state
|
|
?. ?=(%poke-ack -.sign) `state
|
|
(take-ping:nat now.bowl wire p.sign)
|
|
::
|
|
[%pub *]
|
|
?. ?=(%pub -.plan.state) `state
|
|
?. ?=(%poke-ack -.sign) `state
|
|
(take-pings:pub wire p.sign)
|
|
::
|
|
[%one *]
|
|
?. ?=(%one -.plan.state) `state
|
|
?: ?=(%poke-ack -.sign) `state
|
|
:: XX handle error?
|
|
`state
|
|
==
|
|
[cards this]
|
|
:: +on-arvo: handle timer firing
|
|
::
|
|
++ on-arvo
|
|
|= [=wire =sign-arvo]
|
|
^- [(list card) _this]
|
|
=^ cards state
|
|
?+ wire `state
|
|
[%jael %delay ~]
|
|
?> ?=(%wake +<.sign-arvo)
|
|
?^ error.sign-arvo
|
|
%- (slog 'ping: strange jael wake fail!' u.error.sign-arvo)
|
|
`state
|
|
(take-delay:ships our.bowl now.bowl)
|
|
::
|
|
[%jael ~]
|
|
?> ?=(%public-keys +<.sign-arvo)
|
|
(take-jael:ships now.bowl)
|
|
::
|
|
[%nat *]
|
|
?. ?=(%nat -.plan.state) `state
|
|
?> ?=(%wake +<.sign-arvo)
|
|
?^ error.sign-arvo
|
|
%- (slog 'ping: strange nat wake fail!' u.error.sign-arvo)
|
|
`state
|
|
(take-wait:nat wire)
|
|
::
|
|
[%pub @ %ip *]
|
|
?. ?=(%pub -.plan.state) `state
|
|
?> ?=(%http-response +<.sign-arvo)
|
|
(take-ip:pub our.bowl wire client-response.sign-arvo)
|
|
::
|
|
[%pub @ %wait *]
|
|
?. ?=(%pub -.plan.state) `state
|
|
?> ?=(%wake +<.sign-arvo)
|
|
(take-wait:pub our.bowl now.bowl wire)
|
|
==
|
|
[cards this]
|
|
::
|
|
++ on-fail on-fail:def
|
|
--
|