Subject: Re: Assorted NNCP questions

From: Sergey Matveev <[email-masked]>
Date: Sun, 27 Dec 2020 12:53:51 +0300

Greetings!

*** John Goerzen [2020-12-26 22:48]:
>First, is nncp-toss multithreaded?  If so, would it be possible to have an
>option forcing it to run requests sequentially?

Packets processing is done intentionally sequentially. With fast
hash/encryption (BLAKE2b/ChaCha20-Poly1305) algorithms it should not
have CPU as a bottleneck, but HDD, which obviously works with maximal
performance on sequential loads. But packet processing itself uses
separate goroutine for decryption and separate for decompression (which
is used for "command" packets created with nncp-exec) -- so here it can
occupy more than two CPU/cores. You can limit it by setting GOMAXPROCS=1
environment variable when running any Go program.

>But the onlinedeadline isn't being respected in the run by nncp-caller; it
>still disconnects after 10 seconds, so this results in a new connection being
>established every minute.  I also tried definine onlinedeadline at the parent
>(neighbor) level, rather than within the calls structure, but that didn't
>help either.

onlinedeadline option must be "in sync" on both nodes, because there is
no agreement made on it inside the online protocol itself. But anyway
currently I do not understand why it is not working properly for you. I
remember that there were problems some time ago, but I thought they were
fixed. My "upstream" node (my gateway and mail server) has only
onlinedeadline option for my "laptop":

  stargrave.org: {
    id: [...]
    exchpub: [...]
    signpub: [...]
    noisepub: [...]
    freq: {
      path: /storage
      chunked: 524288
    }
    incoming: /storage/incoming
    exec: {
      sendmail: ["/usr/sbin/sendmail"]
    }
    onlinedeadline: 3600
  }

and my laptop's configuration for that gateway/upstream node is:

  gw: {
    id: [...]
    exchpub: [...]
    signpub: [...]
    noisepub: [...]
    exec: {
      sendmail: ["/usr/sbin/sendmail"]
    }
    incoming: /home/stargrave/incoming
    freq: {
      chunked: 524288
    }
    calls: [
      {
        cron: "*/10 9-21 * * MON-FRI"
        nice: PRIORITY
        rxrate: 1
      },
      {
        cron: "*/1 21-23,0-9 * * MON-FRI"
        onlinedeadline: 3600
        addr: lan
      },
      {
        cron: "*/1 * * * SAT,SUN"
        onlinedeadline: 3600
        addr: lan
      },
    ]
    addrs: {
      lan: "[fe80::be5f:f4ff:fedd:2752%bridge0]:540"
      main: "..."
    }
  }

and I use nncp-caller running as a background process all days long and
nncp-daemon on upstream machine through inetd. And my connections are
very long-live enough:

    # zstd -d < /var/spool/nncp/log.2.zst | grep call-finish
    I 2020-12-24T19:07:40.34625988Z [call-finish duration="22540" node="..." rxbytes="15598501100" rxspeed="706453" txbytes="591692" txspeed="26"]
    I 2020-12-24T20:38:29.768306849Z [call-finish duration="511" node="..." rxbytes="36480" rxspeed="304" txbytes="175416" txspeed="730"]
    I 2020-12-25T00:21:58.498325841Z [call-finish duration="12420" node="..." rxbytes="42044" rxspeed="3" txbytes="171628" txspeed="13"]
    I 2020-12-25T04:16:39.095366317Z [call-finish duration="14079" node="..." rxbytes="39000" rxspeed="2" txbytes="135020" txspeed="9"]
    I 2020-12-25T05:45:00.081001429Z [call-finish duration="5280" node="..." rxbytes="34908" rxspeed="6" txbytes="60328" txspeed="11"]
    I 2020-12-25T07:58:00.080451266Z [call-finish duration="7920" node="..." rxbytes="38640" rxspeed="4" txbytes="146672" txspeed="18"]
    I 2020-12-25T08:18:00.090356686Z [call-finish duration="1140" node="..." rxbytes="33036" rxspeed="32" txbytes="32988" txspeed="32"]
    I 2020-12-25T09:35:00.09105514Z [call-finish duration="4560" node="..." rxbytes="36656" rxspeed="8" txbytes="122124" txspeed="27"]
    I 2020-12-25T09:59:00.090938021Z [call-finish duration="1380" node="..." rxbytes="33100" rxspeed="26" txbytes="33052" txspeed="26"]
    I 2020-12-25T10:17:00.127521868Z [call-finish duration="1020" node="..." rxbytes="33140" rxspeed="36" txbytes="407176" txspeed="452"]
    I 2020-12-25T12:09:00.09443685Z [call-finish duration="6660" node="..." rxbytes="41452" rxspeed="6" txbytes="158844" txspeed="24"]
    I 2020-12-25T12:10:10.107415566Z [call-finish duration="10" node="..." rxbytes="32748" rxspeed="32748" txbytes="32700" txspeed="32700"]

(short ones are because I disconnect my laptop from the network). Are
you sure onlinedeadline option on the node you connect *to* are in the
node's section, and not inside "calls"? But I will check all that
workability again on holidays next year.

>I am considering just running nncp-call instead of nncp-caller as a systemd
>service, hoping that perhaps it would send periodic pings to notice if the
>remote end goes away (does it?)

onlinedeadline exactly tells how many seconds to await and consider peer
dead if no replies were received from it. It should work (as nncp-caller
too :-)). Actually -call and -caller uses completely the same
code/functions and -caller is just a loop waiting when to call -call's
function to connect to another host. Of course there can be bugs, so
soon I will check that again.

>A -> B -> C
>Now, you use nncp-xfer or nncp-bundle to offload data for B.  But instead of
>plugging the USB stick/whatever into B, you plug it into C and load it in.
>Now what happens?

C will:
>- Ignore it all?

Everything here is very simple. And I am surprised that there is no
information about nncp-xfer's directory layout description in
documentation. Have to fix that too soon!

For example I sent some file from node 2BV...VCQ to node NFG...Y2A (I
stripped that long Base32 identifiers) and run nncp-xfer -mkdir on
completely empty directory (representing removable storage). It will
create the following:

    /tmp/shared
    ├── 2BVYXV6RWH74NXWRMD2SLDX44TEPSWP47TVR7NPTVA6Z63WJEVCQ         <- node itself
    │   └── 2BVYXV6RWH74NXWRMD2SLDX44TEPSWP47TVR7NPTVA6Z63WJEVCQ
    └── NFGW32PP4WLOCXSY5KGGJBQTM3GOGHZJ6K745TBHUYG6HDZ2JY2A         <- destination node id
        └── 2BVYXV6RWH74NXWRMD2SLDX44TEPSWP47TVR7NPTVA6Z63WJEVCQ     <- source node id
            └── CS5AE4UVOV4JRT3UKLYZRDHFHQG4BIYMTNOX3V7QPFAOTE3I72KA <- packet itself

Let's close our eyes on "double" 2BV...VCQ directories -- this is just a
possibility to send packets to "self". That shared directory holds
information of "destination" nodes. Each "destination" node holds
directories with "source" nodes. And each of "source" node contains
packets themselves (packet's filename is a checksum of its contents). If
-xfer, running on 2BV...VCQ, has outbound packets to NFG...Y2A and it
sees that specified shared directory contains NFG...Y2A, then it will
create 2BV...VCQ source subdirectory and place the packet inside it.

If no NFG...Y2A exists, then that storage won't travel to that node so
no packet copying will be done. Of course someone have to either create
that directory manually, or run -xfer with -mkdir option (with -node too
as a rule) at least once.

When -xfer sees directory with the self node's id, then it will treat
packets inside it as inbound ones.

In your example if you "nncp-xfer -mkdir -node B /mnt" on completely
blank storage, then only "B/A/pkt" will be created (ok, also "A/A/"). So
then nncp-xfer is run on C-node, because of lack of "C/" that storage
will be completely ignored.

When you use "-via B", then special "transition" packet is created for
B-node. Technically it is a packet with special type, that only contains
node's id to which you must copy packet's contents. And its contents is
just an ordinary encrypted packet that you will create to C-node when
not specifying -via. So packet is literally just wrapped and encrypted
to via-node(s). Because it can be decrypted only by B-node, then no
observer can get information about its contents (well, except for
niceness-level) and determine is it just an ordinary file transmission
or transition packet. So if you created "-via B C" packet, then anyway
only B-node can decrypt it and see that actually it is transition packet
to C-node. C-node just can not do anything with the data encrypted only
to B-node.

This resembles (not intentionally!) onion encryption that is used for
example in Tor. Each packet is wrapped inside another one and any
intermediate node knows only where to send it further and from which it
was received.

All that spool and -xfer's shared directories contain only encrypted
packets. They can be processed only after authenticated decryption. So
you can not even copy them because you do not know anything about them,
except for: http://www.nncpgo.org/Encrypted.html
sender/recipient node's id, niceness level and... that is all. You have
to check the header's signature by sender's public key, perform
ephemeral key agreement to get the symmetric key, decrypt the content's
with that symmetric key. Even the "real" size of the contents is
encrypted and packet can contain small email message and megabytes of
junk after it.

Technically it is rather simple to add ability to encrypt packet to
multiple recipients at once. Just encrypt the same single symmetric key
to each node with ephemeral DH keys. It will add just a few dozens of
bytes per each additional node. So we can add everyone in the -via path
as an additional recipient to the packet and transitional packets inside
it, without any considerable CPU/disk space overhead -- and everyone in
the -via path (and target's node) should be able to process the packet.

One drawback that it reveals all "participants". So onion encryption is
useless there. But I do not want to say that it is unacceptable (if user
is given the choice). NNCP anyway was never anonymity preserving network.

Another complication that each packet can not belong to just a single
target node. Possibly making (symbolic?) links of the same file to
multiple nodes is enough. And if accidentally some out-of-band node sees
the packet that it can process -- it will do it, like in your example
"C" will process "-via B"-ed packet.

Moreover that gives ability to multicast packets. I liked the idea of
hierarchical multicasting in Usenet, but I have never used it. But
several years I was point in FidoNet so I saw that in practice. Actually
FidoNet does not have multicasting ability in its transport protocols:
inbound message to echo-area is fed to echo-processor that just creates
copied of the message to other outbounds nodes. Of course that can be
done with NNCP and additional news-like, echo-processor-like software.
But possibly it could be builtin NNCP out-of-box somehow. I am not sure
about that, but multicasting idea delights me much! Will think about all
of that.

-- 
Sergey Matveev (http://www.stargrave.org/)
OpenPGP: CF60 E89A 5923 1E76 E263  6422 AE1A 8109 E498 57EF