VERSION | 2 +- doc/.gitignore | 1 + doc/Makefile | 7 +++++-- doc/call.texi | 10 ++++++++-- doc/cfg.texi | 9 +++++++++ doc/cmds.texi | 16 ++++++++++------ doc/download.texi | 4 ++++ doc/news.ru.texi | 26 ++++++++++++++++++++++++++ doc/news.texi | 24 ++++++++++++++++++++++++ doc/niceness.texi | 34 ++++++++++++++++++++++++++++++++-- doc/sp.texi | 71 ++++++++++++++++++++++++++++++++++++----------------- doc/sp.txt | 19 +++++++++++++++++++ doc/spool.texi | 8 ++++---- ports/nncp/Makefile | 6 +++--- src/cypherpunks.ru/nncp/call.go | 23 ++++++++++++++++++++++- src/cypherpunks.ru/nncp/cfg.go | 78 +++++++++++++++++++++++++++++++++++++---------------- src/cypherpunks.ru/nncp/cmd/nncp-bundle/main.go | 8 ++++---- src/cypherpunks.ru/nncp/cmd/nncp-call/main.go | 23 +++++++++++++++++------ src/cypherpunks.ru/nncp/cmd/nncp-caller/main.go | 2 ++ src/cypherpunks.ru/nncp/cmd/nncp-daemon/main.go | 8 ++++---- src/cypherpunks.ru/nncp/cmd/nncp-exec/main.go | 16 ++++++++-------- src/cypherpunks.ru/nncp/cmd/nncp-file/main.go | 8 ++++---- src/cypherpunks.ru/nncp/cmd/nncp-freq/main.go | 17 +++++++++-------- src/cypherpunks.ru/nncp/cmd/nncp-pkt/main.go | 8 ++++---- src/cypherpunks.ru/nncp/cmd/nncp-stat/main.go | 15 ++++++++------- src/cypherpunks.ru/nncp/cmd/nncp-toss/main.go | 8 ++++---- src/cypherpunks.ru/nncp/cmd/nncp-xfer/main.go | 8 ++++---- src/cypherpunks.ru/nncp/nice.go | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/cypherpunks.ru/nncp/nice_test.go | 21 +++++++++++++++++++++ src/cypherpunks.ru/nncp/node.go | 12 ++---------- src/cypherpunks.ru/nncp/pkt.go | 25 ++++++++++++++++--------- src/cypherpunks.ru/nncp/pkt_test.go | 7 ++++++- src/cypherpunks.ru/nncp/sp.go | 95 +++++++++++++++++++++++++++++++++++++++-------------- src/cypherpunks.ru/nncp/toss.go | 5 ++++- src/cypherpunks.ru/nncp/tx.go | 28 ++++++++++++++++++++++++---- diff --git a/VERSION b/VERSION index c0431d09ec2f32c68d8ee7484cb0f15fd20f59d2bd7d7ec5cbb204f3b373287f..11e557ac2f7fa212b331b212b052bff9cae420ab4df0579c844624dd2e86bce1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.2 +3.3 diff --git a/doc/.gitignore b/doc/.gitignore index 4a6bc63a316cd9b7e0bb6203c1968e24a7fa7427e1472f922fbc9157966c916b..71f790068b6e18ea1a7ef2428aa4741fb9943757e9eff5f0fd626e8a405f6b3a 100644 --- a/doc/.gitignore +++ b/doc/.gitignore @@ -1,2 +1,3 @@ nncp.info nncp.html +sp.utxt diff --git a/doc/Makefile b/doc/Makefile index db0299cc785175d4f4206b43342acd8f76563b379d6168e7dddd9a4c0b0d8a48..57628622c578f267d0249590da87d8764647f9339a410dcb8f427711db0de248 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -2,12 +2,15 @@ all: nncp.info nncp.html MAKEINFO ?= makeinfo -nncp.info: *.texi pedro.txt +sp.utxt: sp.txt + plantuml -tutxt sp.txt + +nncp.info: *.texi sp.utxt pedro.txt $(MAKEINFO) -o nncp.info index.texi CSS != cat style.css -nncp.html: *.texi pedro.txt +nncp.html: *.texi sp.utxt pedro.txt rm -f nncp.html/*.html $(MAKEINFO) --html \ --set-customization-variable CSS_LINES='$(CSS)' \ diff --git a/doc/call.texi b/doc/call.texi index 0dd2f7f0ff1ee9c6e49efd441e1df664251dcf82de1b3e428e1c1a909c8c090a..ba09599a64fb124d597ef458ab4810d1dd66e5a90f9be71bbabb249e1df2ac76 100644 --- a/doc/call.texi +++ b/doc/call.texi @@ -10,12 +10,14 @@ calls: - cron: "*/1 * * * MON-FRI" onlinedeadline: 3600 - nice: 64 + nice: PRIORITY+10 - cron: "30 * * * SAT,SUN" onlinedeadline: 1800 maxonlinetime: 1750 - nice: 64 + nice: NORMAL + rxrate: 10 + txrate: 20 - cron: "0 * * * SAT,SUN" xx: rx @@ -166,6 +168,10 @@ @item addr Optional. Call only that address, instead of trying all from @ref{CfgAddrs, @emph{addrs}} configuration option. It can be either key from @emph{addrs} dictionary, or an ordinary @option{addr:port}. + +@item rxrate/txrate +Optional. Override @ref{CfgXxRate, @emph{rxrate/txrate}} configuration +option when calling. @item onlinedeadline Optional. Override @ref{CfgOnlineDeadline, @emph{onlinedeadline}} diff --git a/doc/cfg.texi b/doc/cfg.texi index d9584f226aa352fe19559b6001c09fd305d028d482e4118d84d919ee18b76a03..0e18d41844e6375e6c22caaddf7cb7312d68d5d347c4f89db289ef4f7f6e0cd6 100644 --- a/doc/cfg.texi +++ b/doc/cfg.texi @@ -57,6 +57,8 @@ freq: /home/bob/pub freqchunked: 1024 freqminsize: 2048 via: [alice] + rxrate: 10 + txrate: 20 @end verbatim @strong{spool} field contains an absolute path to @ref{Spool, spool} @@ -142,6 +144,13 @@ human-readable name of the link/address. Values are @verb{|addr:port|} pairs pointing to @ref{nncp-daemon}'s listening instance. May be omitted if either no direct connection exists, or @ref{nncp-call} is used with forced address specifying. + +@anchor{CfgXxRate} +@item rxrate/txrate +If greater than zero, then at most *rate packets per second will be +sent/received after the handshake. It could be used as crude bandwidth +traffic shaper: each packet has at most 64 KiB payload size. Could be +omitted at all -- no rate limits. @anchor{CfgOnlineDeadline} @item onlinedeadline diff --git a/doc/cmds.texi b/doc/cmds.texi index 020cac7975a4742d916c8a54389475aaadc8356c3d7a746a80ef2078a2bbbf5c..ca71ce7a4be310f760b0a561f3cdfb5448407b00b424719a1c51ed748f4bacee 100644 --- a/doc/cmds.texi +++ b/doc/cmds.texi @@ -17,11 +17,9 @@ send 2 KiB file and set @option{-minsize 4}, then resulting packet will be 4 KiB (containing file itself and some junk). @item -nice Set desired outgoing packet @ref{Niceness, niceness level}. - 1-255 values are allowed. @item -replynice Set desired reply packet @ref{Niceness, niceness level}. Only freq - and exec packets look at that niceness level. 1-255 values are - allowed. + and exec packets look at that niceness level. @item -node Process only single specified node. @item -via @@ -95,8 +93,13 @@ @node nncp-call @section nncp-call @verbatim -% nncp-call [options] [-onlinedeadline INT] [-maxonlinetime INT] [-rx|-tx] - NODE[:ADDR] [FORCEADDR] +% nncp-call [options] + [-onlinedeadline INT] + [-maxonlinetime INT] + [-rx|-tx] + [-rxrate INT] + [-txrate INT] + NODE[:ADDR] [FORCEADDR] @end verbatim Call (connect to) specified @option{NODE} and run @ref{Sync, @@ -111,7 +114,8 @@ transmission is performed. If @option{-tx} option is specified, then only outbound transmission is performed. @option{-onlinedeadline} overrides @ref{CfgOnlineDeadline, @emph{onlinedeadline}}. @option{-maxonlinetime} overrides @ref{CfgMaxOnlineTime, -@emph{maxonlinetime}}. +@emph{maxonlinetime}}. @option{-rxrate}/@option{-txrate} override +@ref{CfgXxRate, rxrate/txrate}. @node nncp-caller @section nncp-caller diff --git a/doc/download.texi b/doc/download.texi index d37470531cfb1868903b50f0cfcc94ec72e5934dc4e7591e320ecc34686a34a4..043376fdf764393a58afd84b76199442591d06cc7909fef327e3517e6d7a5723 100644 --- a/doc/download.texi +++ b/doc/download.texi @@ -23,6 +23,10 @@ @multitable {XXXXX} {XXXX KiB} {link sign} {xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx} @headitem Version @tab Size @tab Tarball @tab SHA256 checksum +@item @ref{Release 3.2, 3.2} @tab 1147 KiB +@tab @url{download/nncp-3.2.tar.xz, link} @url{download/nncp-3.2.tar.xz.sig, sign} +@tab @code{BE76802F 1E273D1D E91F0648 A7CB23C5 989F5390 A36F2D0C FD873046 51B9141E} + @item @ref{Release 3.1, 3.1} @tab 1145 KiB @tab @url{download/nncp-3.1.tar.xz, link} @url{download/nncp-3.1.tar.xz.sig, sign} @tab @code{B9344516 4230B58E 8AAADAA2 066F37F2 493CCB71 B025126B BCAD8FAD 6535149F} diff --git a/doc/news.ru.texi b/doc/news.ru.texi index 66901534dbc3ca6f8d414aca8c6e569c467725ce4485b450f0bf15b05f0964ea..e4ca43f7bb5e77ced6d399eb398229e3482790a9ffe8ddb3a965945a6f2b1d8b 100644 --- a/doc/news.ru.texi +++ b/doc/news.ru.texi @@ -1,6 +1,32 @@ @node Новости @section Новости +@node Релиз 3.3 +@subsection Релиз 3.3 +@itemize +@item +@command{nncp-daemon}, @command{nncp-call}, @command{nncp-caller} +проверяют существование @file{.seen} файла и расценивают его как то, что +файл уже был скачан. Возможно передача данных была осуществлена +сторонним способом и удалённая сторона должна быть оповещена об этом. +@item +Если более высокоприоритетный пакет попадает в спул, то +@command{nncp-daemon} добавит его в очередь отправки первым, прерывая +низкоприоритетные передачи. +@item +К средствам связанным с online-соединениями (@command{nncp-daemon}, +@command{nncp-call}, @command{nncp-caller}) добавлен простой +ограничитель скорости. +@item +Возможность задания приоритета символьными обозначениями: +@verb{|NORMAL|}, @verb{|BULK+10|}, @verb{|PRIORITY-5|}, итд. +@item +Изменены значения приоритетов по-умолчанию: +для @command{nncp-exec} с 64 на 96, +для @command{nncp-freq} с 64 на 160, +для @command{nncp-file} с 196 на 224. +@end itemize + @node Релиз 3.2 @subsection Релиз 3.2 @itemize diff --git a/doc/news.texi b/doc/news.texi index 87dbeb0df56b5bdd25f99f34afa29721425b1bf37cad1afe7883b75f67e8ecd1..51aef49d38389d52acf96712a57b434a52829868aac4edd993312f0c20e16b9f 100644 --- a/doc/news.texi +++ b/doc/news.texi @@ -3,6 +3,30 @@ @unnumbered News See also this page @ref{Новости, on russian}. +@node Release 3.3 +@section Release 3.3 +@itemize +@item +@command{nncp-daemon}, @command{nncp-call}, @command{nncp-caller} check +if @file{.seen} exists and treat it like file was already downloaded. +Possibly it was transferred out-of-bound and remote side needs to be +notifier about that. +@item +If higher priority packet is spooled, then @command{nncp-daemon} will +queue its sending first, interrupting lower priority transmissions. +@item +Simple packet rate limiter added to online-related tools +(@command{nncp-daemon}, @command{nncp-call}, @command{nncp-caller}). +@item +Ability to specify niceness with symbolic notation: +@verb{|NORMAL|}, @verb{|BULK+10|}, @verb{|PRIORITY-5|}, итд. +@item +Changed default niceness levels: +for @command{nncp-exec} from 64 to 96, +for @command{nncp-freq} from 64 to 160, +for @command{nncp-file} from 196 to 224. +@end itemize + @node Release 3.2 @section Release 3.2 @itemize diff --git a/doc/niceness.texi b/doc/niceness.texi index ac7b54718e14ddcf91a70f70ab53caa45e0c768f5ee09c4fd13981252efeb7b9..cb87939fc9d735db85616b3e8ebff16af9a0ac23dfab00670fddb9384fef5fe9 100644 --- a/doc/niceness.texi +++ b/doc/niceness.texi @@ -6,10 +6,40 @@ command for controlling processes priority. Higher nicer level means that packet is "nicer" and allows other to bypass him -- that means lower transmission precedence. -Send big files with higher nicer level! That will guarantee you that +Send big files with higher nice level! That will guarantee you that higher priority packets, like mail messages, will pass first, even when -lower priority packet was already been partly downloaded. +lower priority packet was already been partially downloaded. There are default niceness levels built-in for @ref{nncp-exec}, @ref{nncp-file} and @ref{nncp-freq} commands. But pay attention that it can give information about underlying payload to the adversary! + +There are 1-255 niceness levels. They could be specified either as +integer, or using aliases with delta modifiers: + +@table @emph +@item FLASH (F) +Urgent priority. +@item PRIORITY (P) +High priority. Command execution/mail use that priority by default. +@item NORMAL (N) +Normal priority. File requests use that priority by default. +@item BULK (B) +Bundles shipped on a "least effort" basis. File transmission use that +priority by default. +@end table + +@verbatim + 1: F-31 65: P-31 129: N-31 193: B-31 + 2: F-30 66: P-30 130: N-30 194: B-30 + ... ... ... ... +32: F 96: P 160: N 224: B +33: F+1 97: P+1 161: N+1 225: B+1 +34: F+2 98: P+2 162: N+2 226: B+2 + ... ... ... ... +64: F+32 128: P+32 192: N+32 255: B+31 | MAX +@end verbatim + +Precedence could be specified both with single-letter aliases and with +whole strings. They are case insensitive. @emph{MAX} is an alias for 255 +niceness level. diff --git a/doc/sp.texi b/doc/sp.texi index 63539a7ff393aeeb4a402aa2faaed32911170069e95069cd48faf6d76e722d26..ce8427eb7bc7cefd815b7001dd20c8c26482689a60e153ad1cf74ef4f72f67a7 100644 --- a/doc/sp.texi +++ b/doc/sp.texi @@ -136,36 +136,63 @@ @end table Typical peer's behaviour is following: +@verbatiminclude sp.utxt + @enumerate -@item Perform Noise-IK handshake. -@item When remote peer's identity is known (by definition for initiator -and after receiving first packet for responser (however it is not -authenticated yet)), then collect all @emph{tx}-related files -information and prepare payload packets with all that @emph{INFO}s. -@item Pad the very first payload packet (that is sent with first Noise -handshake message) with @emph{HALT}s to the maximal size. -@item Send all queued payload packets. -@item When @emph{INFO} packet received, check that is has an acceptable -niceness level (skip if not), check if file's @file{.part} exists and -queue @emph{FREQ} outgoing packet (with corresponding offset if -required). -@item When @emph{FREQ} packet received, append it to current sending -queue. Sending queue contains files with offsets that are needed to be +@item Perform @emph{Noise-IK} handshake: + + @table @strong + @item Initiator + Collects all @emph{tx}-related files information and prepares + payload filled with @emph{INFO}s for including in the @strong{first} + handshake message. + @item Responder + After receiving the first handshake message, it gains remote + identity knowledge and similarly prepares the payload for including + in the @strong{second} handshake message. + @end table + + All payloads are padded to maximal message size with @emph{HALT}s. + +@item If queued @emph{INFO}s are not sent completely in handshake +payloads, then send all of remaining in the transport stage. + +@item When @emph{INFO} packet received: + + @itemize + @item Check that it has an acceptable niceness level. + Ignore it if it is too nice. + @item If already downloaded file exists, then queue @emph{DONE} + sending. + @item If @file{.seen} exists, then queue @emph{DONE} sending. + @item If @file{.part} exists, then queue @emph{FREQ} sending with + corresponding offset. + @end itemize + +@item When @emph{FREQ} packet received, insert it to current sending +queue with niceness level sort: higher priority packets will be sent +first. Sending queue contains files with offsets that are needed to be sent. -@item While sending queue is not empty, send @emph{FILE} packet until -queue's head is not fully sent. @emph{FREQ} can contain offset equal to -size -- anyway sent @emph{FILE} packet with an empty payload. -@item When @emph{FILE} packet received, check if it is not fully -downloaded (comparing to @emph{INFO}'s packet information). If so, then -run background integrity checker on it. If check is succeeded, then + +@item While sending queue is not empty, send @emph{FILE} packets. +@emph{FREQ} could contain offset equal to size -- anyway sent +@emph{FILE} packet with an empty payload. @emph{FILE} sending is +performed only if no other outgoing packets are queued: @emph{INFO}s +have higher priority. + +@item When @emph{FILE} packet received, check if it is completely +downloaded (comparing to @emph{INFO}'s packet size information). If so, +then run background integrity checker on it. If check succeeds, then delete @file{.part} suffix from file's name and send @emph{DONE} packet. + @item When @emph{DONE} packet received, delete corresponding file. @item When @emph{HALT} packet received, empty file sending queue. -@item @emph{FILE} sending is performed only if no other outgoing packets -are queued. + @item Each second, node checks: are there any new @emph{tx} packets appeared and queues corresponding @emph{INFO} packets. + @item If no packets are sent and received during @ref{CfgOnlineDeadline, onlinedeadline} duration, then close the connection. There is no explicit indication that session is over. + @end enumerate diff --git a/doc/sp.txt b/doc/sp.txt new file mode 100644 index 0000000000000000000000000000000000000000..32c23dd06098ae1291028bb2a836320c5d37c81d209481504930105730efcf1a --- /dev/null +++ b/doc/sp.txt @@ -0,0 +1,19 @@ +@startuml +hide footbox +participant Initiator +participant Responder + +== preparation == + +Initiator <- Responder : [s] + +== interactive == + +Initiator -> Responder : [e, es, s, ss], INFO..., HALT... +Initiator <- Responder : [e, ee, se], INFO..., HALT... +Initiator -> Responder : INFO..., FREQ..., DONE... +Initiator <- Responder : INFO..., FREQ..., DONE... +Initiator -> Responder : FILE..., INFO..., DONE... +Initiator <- Responder : FILE..., INFO..., DONE... + +@enduml diff --git a/doc/spool.texi b/doc/spool.texi index 9bf158babe24a5935c7852ca846fb2e2014b41abfd037c798db23e4dbb7e9dae..611c422f3cffbc0874ad16d6c9b4fe6f73894af5d104474d37551f4b6d6437b4 100644 --- a/doc/spool.texi +++ b/doc/spool.texi @@ -21,16 +21,16 @@ @end verbatim Except for @file{tmp}, all other directories are Base32-encoded node identifiers (@file{2WHB...OABQ}, @file{BYRR...CG6Q} in our example). -Each node subdirectory has @file{rx} (received, partly received and +Each node subdirectory has @file{rx} (received, partially received and currently unprocessed packets) and @file{tx} (for outbound packets) directories. Each @file{rx}/@file{tx} directory contains one file per encrypted packet. Its filename is Base32 encoded BLAKE2b hash of the contents. So it can be integrity checked at any time. @file{5ZIB...UMKW.part} is -partly received file from @file{2WHB...OABQ} node. @file{tx} directory -can not contain partly written files -- they are moved atomically from -@file{tmp}. +partially received file from @file{2WHB...OABQ} node. @file{tx} +directory can not contain partially written files -- they are moved +atomically from @file{tmp}. When @ref{nncp-toss} utility is called with @option{-seen} option, it will create empty @file{XXX.seen} files, telling that some kind of diff --git a/ports/nncp/Makefile b/ports/nncp/Makefile index 77157708c2bc9233f1bdf0b3941cba4aca96743e50ecaf7104318cb5df7745b8..704be16a5477314f649c0bded24c702f7b4443b0881061ed155060294dccf6d9 100644 --- a/ports/nncp/Makefile +++ b/ports/nncp/Makefile @@ -1,12 +1,12 @@ -# $FreeBSD: head/net/nncp/Makefile 460314 2018-01-29 16:17:45Z yuri $ +# $FreeBSD: head/net/nncp/Makefile 471003 2018-05-27 20:24:00Z krion $ PORTNAME= nncp -DISTVERSION= 3.1 +DISTVERSION= 3.3 CATEGORIES= net MASTER_SITES= http://www.nncpgo.org/download/ MAINTAINER= stargrave@stargrave.org -COMMENT= Utilities for secure store-and-forward files, mail and command exchanging +COMMENT= Utilities for secure store-and-forward files, mail, command exchanging LICENSE= GPLv3+ LICENSE_FILE= ${WRKSRC}/COPYING diff --git a/src/cypherpunks.ru/nncp/call.go b/src/cypherpunks.ru/nncp/call.go index 4098453b334e32249d6eb40f7c18be6167ff7636a6440206c6bae41ae6cfbf28..dd06a44ff5eb57e2a1babfa26dcee336f0f5e88b48bca6ecf3663c89b0574d39 100644 --- a/src/cypherpunks.ru/nncp/call.go +++ b/src/cypherpunks.ru/nncp/call.go @@ -21,9 +21,28 @@ import ( "net" "strconv" + + "github.com/gorhill/cronexpr" ) -func (ctx *Ctx) CallNode(node *Node, addrs []string, nice uint8, xxOnly TRxTx, onlineDeadline, maxOnlineTime uint) (isGood bool) { +type Call struct { + Cron *cronexpr.Expression + Nice uint8 + Xx TRxTx + RxRate int + TxRate int + Addr *string + OnlineDeadline uint + MaxOnlineTime uint +} + +func (ctx *Ctx) CallNode( + node *Node, + addrs []string, + nice uint8, + xxOnly TRxTx, + rxRate, txRate int, + onlineDeadline, maxOnlineTime uint) (isGood bool) { for _, addr := range addrs { sds := SDS{"node": node.Id, "addr": addr} ctx.LogD("call", sds, "dialing") @@ -38,6 +57,8 @@ conn, node.Id, nice, xxOnly, + rxRate, + txRate, onlineDeadline, maxOnlineTime, ) diff --git a/src/cypherpunks.ru/nncp/cfg.go b/src/cypherpunks.ru/nncp/cfg.go index e4da5fd53727d256607a65f5c4407c83b2abc4262aba940a429b9b1717c87d5b..dd81ceae4caa057e7cd7d1deeeb9652407c7eb8822232d28ba2e96300432cb70 100644 --- a/src/cypherpunks.ru/nncp/cfg.go +++ b/src/cypherpunks.ru/nncp/cfg.go @@ -48,28 +48,32 @@ type NodeYAML struct { Id string ExchPub string SignPub string - NoisePub *string `noisepub,omitempty` - Exec map[string][]string `exec,omitempty` - Incoming *string `incoming,omitempty` - Freq *string `freq,omitempty` - FreqChunked *uint64 `freqchunked,omitempty` - FreqMinSize *uint64 `freqminsize,omitempty` - Via []string `via,omitempty` - Calls []CallYAML `calls,omitempty` + NoisePub *string `yaml:"noisepub,omitempty"` + Exec map[string][]string `yaml:"exec,omitempty"` + Incoming *string `yaml:"incoming,omitempty"` + Freq *string `yaml:"freq,omitempty"` + FreqChunked *uint64 `yaml:"freqchunked,omitempty"` + FreqMinSize *uint64 `yaml:"freqminsize,omitempty"` + Via []string `yaml:"via,omitempty"` + Calls []CallYAML `yaml:"calls,omitempty"` - Addrs map[string]string `addrs,omitempty` + Addrs map[string]string `yaml:"addrs,omitempty"` - OnlineDeadline *uint `onlinedeadline,omitempty` - MaxOnlineTime *uint `maxonlinetime,omitempty` + RxRate *int `yaml:"rxrate,omitempty"` + TxRate *int `yaml:"txrate,omitempty"` + OnlineDeadline *uint `yaml:"onlinedeadline,omitempty"` + MaxOnlineTime *uint `yaml:"maxonlinetime,omitempty"` } type CallYAML struct { Cron string - Nice *int `nice,omitempty` - Xx string `xx,omitempty` - Addr *string `addr,omitempty` - OnlineDeadline *uint `onlinedeadline,omitempty` - MaxOnlineTime *uint `maxonlinetime,omitempty` + Nice *string `yaml:"nice,omitempty"` + Xx string `yaml:"xx,omitempty"` + RxRate *int `yaml:"rxrate,omitempty"` + TxRate *int `yaml:"txrate,omitempty"` + Addr *string `yaml:"addr,omitempty"` + OnlineDeadline *uint `yaml:"onlinedeadline,omitempty"` + MaxOnlineTime *uint `yaml:"maxonlinetime,omitempty"` } type NodeOurYAML struct { @@ -88,17 +92,17 @@ To string } type NotifyYAML struct { - File *FromToYAML `file,omitempty` - Freq *FromToYAML `freq,omitempty` + File *FromToYAML `yaml:"file,omitempty"` + Freq *FromToYAML `yaml:"freq,omitempty"` } type CfgYAML struct { - Self *NodeOurYAML `self,omitempty` + Self *NodeOurYAML `yaml:"self,omitempty"` Neigh map[string]NodeYAML Spool string Log string - Notify *NotifyYAML `notify,omitempty` + Notify *NotifyYAML `yaml:"notify,omitempty"` } func NewNode(name string, yml NodeYAML) (*Node, error) { @@ -163,6 +167,15 @@ if yml.FreqMinSize != nil { freqMinSize = int64(*yml.FreqMinSize) * 1024 } + defRxRate := 0 + if yml.RxRate != nil && *yml.RxRate > 0 { + defRxRate = *yml.RxRate + } + defTxRate := 0 + if yml.TxRate != nil && *yml.TxRate > 0 { + defTxRate = *yml.TxRate + } + defOnlineDeadline := uint(DefaultDeadline) if yml.OnlineDeadline != nil { if *yml.OnlineDeadline <= 0 { @@ -181,13 +194,15 @@ expr, err := cronexpr.Parse(callYml.Cron) if err != nil { return nil, err } + nice := uint8(255) if callYml.Nice != nil { - if *callYml.Nice < 1 || *callYml.Nice > 255 { - return nil, errors.New("Nice must be between 1 and 255") + nice, err = NicenessParse(*callYml.Nice) + if err != nil { + return nil, err } - nice = uint8(*callYml.Nice) } + var xx TRxTx switch callYml.Xx { case "rx": @@ -198,6 +213,16 @@ case "": default: return nil, errors.New("xx field must be either \"rx\" or \"tx\"") } + + rxRate := 0 + if callYml.RxRate != nil && *callYml.RxRate > 0 { + rxRate = *callYml.RxRate + } + txRate := 0 + if callYml.TxRate != nil && *callYml.TxRate > 0 { + txRate = *callYml.TxRate + } + var addr *string if callYml.Addr != nil { if a, exists := yml.Addrs[*callYml.Addr]; exists { @@ -206,6 +231,7 @@ } else { addr = callYml.Addr } } + onlineDeadline := defOnlineDeadline if callYml.OnlineDeadline != nil { if *callYml.OnlineDeadline == 0 { @@ -213,14 +239,18 @@ return nil, errors.New("OnlineDeadline must be at least 1 second") } onlineDeadline = *callYml.OnlineDeadline } + var maxOnlineTime uint if callYml.MaxOnlineTime != nil { maxOnlineTime = *callYml.MaxOnlineTime } + calls = append(calls, &Call{ Cron: expr, Nice: nice, Xx: xx, + RxRate: rxRate, + TxRate: txRate, Addr: addr, OnlineDeadline: onlineDeadline, MaxOnlineTime: maxOnlineTime, @@ -239,6 +269,8 @@ FreqChunked: freqChunked, FreqMinSize: freqMinSize, Calls: calls, Addrs: yml.Addrs, + RxRate: defRxRate, + TxRate: defTxRate, OnlineDeadline: defOnlineDeadline, MaxOnlineTime: defMaxOnlineTime, } diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-bundle/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-bundle/main.go index 9dcb88018fc2f3cde788d63f4729f030dd54737bbe607907d65f1025868e37d1..0e524d15619d6b66b4e327e7651f3cba2664a9f5b866d0aee8726debf5705578 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-bundle/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-bundle/main.go @@ -55,7 +55,7 @@ func main() { var ( cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") - niceRaw = flag.Int("nice", 255, "Minimal required niceness") + niceRaw = flag.String("nice", nncp.NicenessFmt(255), "Minimal required niceness") doRx = flag.Bool("rx", false, "Receive packets") doTx = flag.Bool("tx", false, "Transfer packets") doDelete = flag.Bool("delete", false, "Delete transferred packets") @@ -78,10 +78,10 @@ if *version { fmt.Println(nncp.VersionGet()) return } - if *niceRaw < 1 || *niceRaw > 255 { - log.Fatalln("-nice must be between 1 and 255") + nice, err := nncp.NicenessParse(*niceRaw) + if err != nil { + log.Fatalln(err) } - nice := uint8(*niceRaw) if *doRx && *doTx { log.Fatalln("-rx and -tx can not be set simultaneously") } diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-call/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-call/main.go index d75162ddc5183b983b068a49e6a11b140286056d3464b7535b737aacd19946ab..37524822be7cf60658c46d1ab9c99b76bd207d148cdbf3226b2bc7fa02915e01 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-call/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-call/main.go @@ -40,9 +40,11 @@ func main() { var ( cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") - niceRaw = flag.Int("nice", 255, "Minimal required niceness") + niceRaw = flag.String("nice", nncp.NicenessFmt(255), "Minimal required niceness") rxOnly = flag.Bool("rx", false, "Only receive packets") - txOnly = flag.Bool("tx", false, "Only transfer packets") + txOnly = flag.Bool("tx", false, "Only transmit packets") + rxRate = flag.Int("rxrate", 0, "Maximal receive rate, pkts/sec") + txRate = flag.Int("txrate", 0, "Maximal transmit rate, pkts/sec") spoolPath = flag.String("spool", "", "Override path to spool") logPath = flag.String("log", "", "Override path to logfile") quiet = flag.Bool("quiet", false, "Print only errors") @@ -67,10 +69,10 @@ if flag.NArg() < 1 { usage() os.Exit(1) } - if *niceRaw < 1 || *niceRaw > 255 { - log.Fatalln("-nice must be between 1 and 255") + nice, err := nncp.NicenessParse(*niceRaw) + if err != nil { + log.Fatalln(err) } - nice := uint8(*niceRaw) if *rxOnly && *txOnly { log.Fatalln("-rx and -tx can not be set simultaneously") } @@ -121,7 +123,16 @@ addrs = append(addrs, addr) } } - if !ctx.CallNode(node, addrs, nice, xxOnly, *onlineDeadline, *maxOnlineTime) { + if !ctx.CallNode( + node, + addrs, + nice, + xxOnly, + *rxRate, + *txRate, + *onlineDeadline, + *maxOnlineTime, + ) { os.Exit(1) } } diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-caller/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-caller/main.go index 33dc4b60f175dff6482f21b9523939d03128c59f4bced1947fd6aaf3aeda38f8..47a73070666d3789fb41c229c7dc768a0222ccae9a8cdc96847a7d6bc15abd3c 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-caller/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-caller/main.go @@ -128,6 +128,8 @@ node, addrs, call.Nice, call.Xx, + call.RxRate, + call.TxRate, call.OnlineDeadline, call.MaxOnlineTime, ) diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-daemon/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-daemon/main.go index 46d7fa164b3b1253fbb3765b1b36a19b7ac4068b2b6b25213eedeccac8a5b701..830c56b33ba41f4e7c98d6ee9ff895c34bc9b5bb35ad2fe711100d46bea694a9 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-daemon/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-daemon/main.go @@ -41,7 +41,7 @@ func main() { var ( cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") - niceRaw = flag.Int("nice", 255, "Minimal required niceness") + niceRaw = flag.String("nice", nncp.NicenessFmt(255), "Minimal required niceness") bind = flag.String("bind", "[::]:5400", "Address to bind to") maxConn = flag.Int("maxconn", 128, "Maximal number of simultaneous connections") spoolPath = flag.String("spool", "", "Override path to spool") @@ -61,10 +61,10 @@ if *version { fmt.Println(nncp.VersionGet()) return } - if *niceRaw < 1 || *niceRaw > 255 { - log.Fatalln("-nice must be between 1 and 255") + nice, err := nncp.NicenessParse(*niceRaw) + if err != nil { + log.Fatalln(err) } - nice := uint8(*niceRaw) ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, *logPath, *quiet, *debug) if err != nil { diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-exec/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-exec/main.go index 38262f09e937d8a2d95bc30ccce3b866ed07abcb9c0e61adceb9d6ec15a9fe7c..de1c06bad18f8d576e7fb6fa0d7c67d14aed0fbfa621f9748338dbed63047355 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-exec/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-exec/main.go @@ -40,8 +40,8 @@ func main() { var ( cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") - niceRaw = flag.Int("nice", nncp.DefaultNiceExec, "Outbound packet niceness") - replyNiceRaw = flag.Int("replynice", nncp.DefaultNiceFile, "Possible reply packet niceness") + niceRaw = flag.String("nice", nncp.NicenessFmt(nncp.DefaultNiceExec), "Outbound packet niceness") + replyNiceRaw = flag.String("replynice", nncp.NicenessFmt(nncp.DefaultNiceFile), "Possible reply packet niceness") minSize = flag.Uint64("minsize", 0, "Minimal required resulting packet size, in KiB") viaOverride = flag.String("via", "", "Override Via path to destination node") spoolPath = flag.String("spool", "", "Override path to spool") @@ -65,14 +65,14 @@ if flag.NArg() < 2 { usage() os.Exit(1) } - if *niceRaw < 1 || *niceRaw > 255 { - log.Fatalln("-nice must be between 1 and 255") + nice, err := nncp.NicenessParse(*niceRaw) + if err != nil { + log.Fatalln(err) } - nice := uint8(*niceRaw) - if *replyNiceRaw < 1 || *replyNiceRaw > 255 { - log.Fatalln("-replynice must be between 1 and 255") + replyNice, err := nncp.NicenessParse(*replyNiceRaw) + if err != nil { + log.Fatalln(err) } - replyNice := uint8(*replyNiceRaw) ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, *logPath, *quiet, *debug) if err != nil { diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-file/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-file/main.go index d968bd12a35378e051f7714c123b5302302c55a95bb55a484a776fbe717392eb..b554dd1e35d3041b2b00fd953e7f44bbce3c71aac72e795dcb365f241c0e7e0c 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-file/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-file/main.go @@ -45,7 +45,7 @@ func main() { var ( cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") - niceRaw = flag.Int("nice", nncp.DefaultNiceFile, "Outbound packet niceness") + niceRaw = flag.String("nice", nncp.NicenessFmt(nncp.DefaultNiceFile), "Outbound packet niceness") argMinSize = flag.Int64("minsize", -1, "Minimal required resulting packet size, in KiB") argChunkSize = flag.Int64("chunked", -1, "Split file on specified size chunks, in KiB") viaOverride = flag.String("via", "", "Override Via path to destination node") @@ -70,10 +70,10 @@ if flag.NArg() != 2 { usage() os.Exit(1) } - if *niceRaw < 1 || *niceRaw > 255 { - log.Fatalln("-nice must be between 1 and 255") + nice, err := nncp.NicenessParse(*niceRaw) + if err != nil { + log.Fatalln(err) } - nice := uint8(*niceRaw) ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, *logPath, *quiet, *debug) if err != nil { diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-freq/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-freq/main.go index db7960ea26e7eb43e4c23945809f1153d2ea0075e7aef2c9a17f4557ee672701..4d9e64fc46318141363ebd63175670de1db3a76ff543934bb7624202a141921c 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-freq/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-freq/main.go @@ -25,6 +25,7 @@ "fmt" "log" "os" "path/filepath" + "strconv" "strings" "cypherpunks.ru/nncp" @@ -40,8 +41,8 @@ func main() { var ( cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") - niceRaw = flag.Int("nice", nncp.DefaultNiceFreq, "Outbound packet niceness") - replyNiceRaw = flag.Int("replynice", nncp.DefaultNiceFile, "Reply file packet niceness") + niceRaw = flag.String("nice", nncp.NicenessFmt(nncp.DefaultNiceFreq), "Outbound packet niceness") + replyNiceRaw = flag.String("replynice", strconv.Itoa(nncp.DefaultNiceFile), "Reply file packet niceness") minSize = flag.Uint64("minsize", 0, "Minimal required resulting packet size, in KiB") viaOverride = flag.String("via", "", "Override Via path to destination node") spoolPath = flag.String("spool", "", "Override path to spool") @@ -65,14 +66,14 @@ if flag.NArg() == 0 { usage() os.Exit(1) } - if *niceRaw < 1 || *niceRaw > 255 { - log.Fatalln("-nice must be between 1 and 255") + nice, err := nncp.NicenessParse(*niceRaw) + if err != nil { + log.Fatalln(err) } - nice := uint8(*niceRaw) - if *replyNiceRaw < 1 || *replyNiceRaw > 255 { - log.Fatalln("-replynice must be between 1 and 255") + replyNice, err := nncp.NicenessParse(*replyNiceRaw) + if err != nil { + log.Fatalln(err) } - replyNice := uint8(*replyNiceRaw) ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, *logPath, *quiet, *debug) if err != nil { diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-pkt/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-pkt/main.go index 09817e13795740be823a0a31089cc47461072be544fd4a47f3be9e5a0e1349f2..3d9a8c89cd60e0a046900d2a8aeadbc6e32e74d7a3b1a462e22125c157316b15 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-pkt/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-pkt/main.go @@ -114,8 +114,8 @@ default: path = string(pkt.Path[:pkt.PathLen]) } fmt.Printf( - "Packet type: plain\nPayload type: %s\nNiceness: %d\nPath: %s\n", - payloadType, pkt.Nice, path, + "Packet type: plain\nPayload type: %s\nNiceness: %s (%d)\nPath: %s\n", + payloadType, nncp.NicenessFmt(pkt.Nice), pkt.Nice, path, ) return } @@ -148,8 +148,8 @@ } return } fmt.Printf( - "Packet type: encrypted\nNiceness: %d\nSender: %s\nRecipient: %s\n", - pktEnc.Nice, pktEnc.Sender, pktEnc.Recipient, + "Packet type: encrypted\nNiceness: %s (%d)\nSender: %s\nRecipient: %s\n", + nncp.NicenessFmt(pktEnc.Nice), pktEnc.Nice, pktEnc.Sender, pktEnc.Recipient, ) return } diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-stat/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-stat/main.go index c5c248fbd0911f7027a459c31f76cb4cef520f50a0b2f2975777116e9df9d064..89bf5899272e878e1d55c1c5ff3fe91fe26ed3f7ddd9c3303701ac957230c25e 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-stat/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-stat/main.go @@ -99,18 +99,19 @@ txNums[job.PktEnc.Nice] = txNums[job.PktEnc.Nice] + 1 txBytes[job.PktEnc.Nice] = txBytes[job.PktEnc.Nice] + job.Size } fmt.Println(node.Name) - for nice := 0; nice < 256; nice++ { - rxNum, rxExists := rxNums[uint8(nice)] - txNum, txExists := txNums[uint8(nice)] + var nice uint8 + for nice = 1; nice > 0; nice++ { + rxNum, rxExists := rxNums[nice] + txNum, txExists := txNums[nice] if !(rxExists || txExists) { continue } fmt.Printf( - "\tnice:% 3d | Rx: % 10s, % 3d pkts | Tx: % 10s, % 3d pkts\n", - nice, - humanize.IBytes(uint64(rxBytes[uint8(nice)])), + "\tnice:% 4s | Rx: % 10s, % 3d pkts | Tx: % 10s, % 3d pkts\n", + nncp.NicenessFmt(nice), + humanize.IBytes(uint64(rxBytes[nice])), rxNum, - humanize.IBytes(uint64(txBytes[uint8(nice)])), + humanize.IBytes(uint64(txBytes[nice])), txNum, ) } diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-toss/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-toss/main.go index 442c561271d76f69ff82f61a28917791fd83d9e1c203c20bf7fe477ee0fdcf02..c57062ebf4f34615a0114d44b3cef2b54ef8cd20f9f6eacdffb377f199f00a48 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-toss/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-toss/main.go @@ -40,7 +40,7 @@ func main() { var ( cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") nodeRaw = flag.String("node", "", "Process only that node") - niceRaw = flag.Int("nice", 255, "Minimal required niceness") + niceRaw = flag.String("nice", nncp.NicenessFmt(255), "Minimal required niceness") dryRun = flag.Bool("dryrun", false, "Do not actually write any tossed data") doSeen = flag.Bool("seen", false, "Create .seen files") cycle = flag.Uint("cycle", 0, "Repeat tossing after N seconds in infinite loop") @@ -65,10 +65,10 @@ if *version { fmt.Println(nncp.VersionGet()) return } - if *niceRaw < 1 || *niceRaw > 255 { - log.Fatalln("-nice must be between 1 and 255") + nice, err := nncp.NicenessParse(*niceRaw) + if err != nil { + log.Fatalln(err) } - nice := uint8(*niceRaw) ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, *logPath, *quiet, *debug) if err != nil { diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-xfer/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-xfer/main.go index b89c654cbc62f54c48c4cb181c28a6e2d2bfd4197b5603a479fca6d01218b744..3b017bb5b768062294164329d14ca4b71536c1f33c284a8f2d00d0d7982e962c 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-xfer/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-xfer/main.go @@ -45,7 +45,7 @@ func main() { var ( cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") nodeRaw = flag.String("node", "", "Process only that node") - niceRaw = flag.Int("nice", 255, "Minimal required niceness") + niceRaw = flag.String("nice", nncp.NicenessFmt(255), "Minimal required niceness") rxOnly = flag.Bool("rx", false, "Only receive packets") txOnly = flag.Bool("tx", false, "Only transfer packets") mkdir = flag.Bool("mkdir", false, "Create necessary outbound directories") @@ -71,10 +71,10 @@ if flag.NArg() != 1 { usage() os.Exit(1) } - if *niceRaw < 1 || *niceRaw > 255 { - log.Fatalln("-nice must be between 1 and 255") + nice, err := nncp.NicenessParse(*niceRaw) + if err != nil { + log.Fatalln(err) } - nice := uint8(*niceRaw) if *rxOnly && *txOnly { log.Fatalln("-rx and -tx can not be set simultaneously") } diff --git a/src/cypherpunks.ru/nncp/nice.go b/src/cypherpunks.ru/nncp/nice.go new file mode 100644 index 0000000000000000000000000000000000000000..b9d81081b4d3800bb13c3fbc19b6bb1ec9dc557daa6652c6f7870c42933e7d3d --- /dev/null +++ b/src/cypherpunks.ru/nncp/nice.go @@ -0,0 +1,108 @@ +package nncp + +import ( + "errors" + "fmt" + "regexp" + "strconv" + "strings" +) + +const ( + NiceFlash = 32 + NicePriority = 96 + NiceNormal = 160 + NiceBulk = 224 + + DefaultNiceExec = NicePriority + DefaultNiceFreq = NiceNormal + DefaultNiceFile = NiceBulk +) + +var ( + niceRe *regexp.Regexp = regexp.MustCompile(`^(\w+)([-+])(\d+)$`) + niceAliases map[string]uint8 = map[string]uint8{ + "flash": NiceFlash, + "f": NiceFlash, + "priority": NicePriority, + "p": NicePriority, + "normal": NiceNormal, + "n": NiceNormal, + "bulk": NiceBulk, + "b": NiceBulk, + "max": 255, + } +) + +func NicenessParse(s string) (uint8, error) { + if nice, err := strconv.Atoi(s); err == nil { + if nice <= 0 || nice > 255 { + return 0, errors.New("nice out of bounds") + } + return uint8(nice), nil + } + s = strings.ToLower(s) + var baseNice uint8 + var found bool + if baseNice, found = niceAliases[s]; found { + return baseNice, nil + } + matches := niceRe.FindStringSubmatch(s) + if len(matches) != 1+3 { + return 0, errors.New("invalid niceness") + } + baseNice, found = niceAliases[matches[1]] + if !found { + return 0, errors.New("invalid niceness") + } + delta, err := strconv.Atoi(matches[3]) + if err != nil { + return 0, err + } + if matches[2] == "-" { + if delta > 31 { + return 0, errors.New("too big niceness delta") + } + return baseNice - uint8(delta), nil + } else { + if delta > 32 || (baseNice == NiceBulk && delta > 31) { + return 0, errors.New("too big niceness delta") + } + return baseNice + uint8(delta), nil + } +} + +func NicenessFmt(nice uint8) string { + switch { + case nice == 255: + return "MAX" + case NiceFlash-31 < nice && nice < NiceFlash: + return fmt.Sprintf("F-%d", NiceFlash-nice) + case nice == NiceFlash: + return "F" + case NiceFlash < nice && nice <= (NiceFlash+32): + return fmt.Sprintf("F+%d", nice-NiceFlash) + + case NicePriority-31 < nice && nice < NicePriority: + return fmt.Sprintf("P-%d", NicePriority-nice) + case nice == NicePriority: + return "P" + case NicePriority < nice && nice <= (NicePriority+32): + return fmt.Sprintf("P+%d", nice-NicePriority) + + case NiceNormal-31 < nice && nice < NiceNormal: + return fmt.Sprintf("N-%d", NiceNormal-nice) + case nice == NiceNormal: + return "N" + case NiceNormal < nice && nice <= (NiceNormal+32): + return fmt.Sprintf("N+%d", nice-NiceNormal) + + case NiceBulk-31 < nice && nice < NiceBulk: + return fmt.Sprintf("B-%d", NiceBulk-nice) + case nice == NiceBulk: + return "B" + case NiceBulk < nice && nice <= (NiceBulk+30): + return fmt.Sprintf("B+%d", nice-NiceBulk) + } + return strconv.Itoa(int(nice)) +} diff --git a/src/cypherpunks.ru/nncp/nice_test.go b/src/cypherpunks.ru/nncp/nice_test.go new file mode 100644 index 0000000000000000000000000000000000000000..acad49887a5f070bd9baa0e3d214e3f80dafba7907e66acd89d81a6d8d7e3e1a --- /dev/null +++ b/src/cypherpunks.ru/nncp/nice_test.go @@ -0,0 +1,21 @@ +package nncp + +import ( + "strings" + "testing" +) + +func TestNiceSymmetric(t *testing.T) { + var nice uint8 + for nice = 1; nice > 0; nice++ { + s := NicenessFmt(nice) + parsed, err := NicenessParse(s) + if err != nil || parsed != nice { + t.Error(err) + } + parsed, err = NicenessParse(strings.ToLower(s)) + if err != nil || parsed != nice { + t.Error(err) + } + } +} diff --git a/src/cypherpunks.ru/nncp/node.go b/src/cypherpunks.ru/nncp/node.go index 7a0d4f85ee97754647aba41a75158d45807bfe96ceb44e552281ef745bb02c96..1fa20ab8fdbe0812fb6a1ec23e369ef256a2b7c950730e71ff465b99cc5a671c 100644 --- a/src/cypherpunks.ru/nncp/node.go +++ b/src/cypherpunks.ru/nncp/node.go @@ -24,7 +24,6 @@ "errors" "sync" "github.com/flynn/noise" - "github.com/gorhill/cronexpr" "golang.org/x/crypto/blake2b" "golang.org/x/crypto/ed25519" "golang.org/x/crypto/nacl/box" @@ -49,6 +48,8 @@ FreqChunked int64 FreqMinSize int64 Via []*NodeId Addrs map[string]string + RxRate int + TxRate int OnlineDeadline uint MaxOnlineTime uint Calls []*Call @@ -65,15 +66,6 @@ SignPub ed25519.PublicKey SignPrv ed25519.PrivateKey NoisePub *[32]byte NoisePrv *[32]byte -} - -type Call struct { - Cron *cronexpr.Expression - Nice uint8 - Xx TRxTx - Addr *string - OnlineDeadline uint - MaxOnlineTime uint } func NewNodeGenerate() (*NodeOur, error) { diff --git a/src/cypherpunks.ru/nncp/pkt.go b/src/cypherpunks.ru/nncp/pkt.go index 2f430b518c084e5e745379c40641b8684c98d8e8809cf20c695aba641428317a..7c057ab98fed5d90775b8640c0f2693645b47e6dd8c90a18505a5153c9f24999 100644 --- a/src/cypherpunks.ru/nncp/pkt.go +++ b/src/cypherpunks.ru/nncp/pkt.go @@ -47,10 +47,6 @@ PktTypeTrns PktType = iota MaxPathSize = 1<<8 - 1 - DefaultNiceExec = 64 - DefaultNiceFreq = 64 - DefaultNiceFile = 196 - NNCPBundlePrefix = "NNCP" ) @@ -174,7 +170,14 @@ } return written, nil } -func PktEncWrite(our *NodeOur, their *Node, pkt *Pkt, nice uint8, size, padSize int64, data io.Reader, out io.Writer) error { +func PktEncWrite( + our *NodeOur, + their *Node, + pkt *Pkt, + nice uint8, + size, padSize int64, + data io.Reader, + out io.Writer) error { pubEph, prvEph, err := box.GenerateKey(rand.Reader) if err != nil { return err @@ -253,7 +256,7 @@ mac, err = blake2b.New256(keyAuth) if err != nil { return err } - lr := io.LimitedReader{data, size} + lr := io.LimitedReader{R: data, N: size} mr := io.MultiReader(&pktBuf, &lr) mw := io.MultiWriter(out, mac) fullSize := pktBuf.Len() + int(size) @@ -271,7 +274,7 @@ if padSize > 0 { if _, err = io.ReadFull(kdf, keyEnc[:]); err != nil { return err } - lr = io.LimitedReader{DevZero{}, padSize} + lr = io.LimitedReader{R: DevZero{}, N: padSize} written, err = ae(keyEnc, &lr, out) if err != nil { return err @@ -298,7 +301,11 @@ } return ed25519.Verify(their.SignPub, tbsBuf.Bytes(), pktEnc.Sign[:]), nil } -func PktEncRead(our *NodeOur, nodes map[NodeId]*Node, data io.Reader, out io.Writer) (*Node, int64, error) { +func PktEncRead( + our *NodeOur, + nodes map[NodeId]*Node, + data io.Reader, + out io.Writer) (*Node, int64, error) { var pktEnc PktEnc _, err := xdr.Unmarshal(data, &pktEnc) if err != nil { @@ -373,7 +380,7 @@ return their, 0, err } fullSize := PktOverhead + size - 8 - 2*blake2b.Size256 - lr := io.LimitedReader{data, fullSize} + lr := io.LimitedReader{R: data, N: fullSize} tr := io.TeeReader(&lr, mac) written, err := ae(keyEnc, tr, out) if err != nil { diff --git a/src/cypherpunks.ru/nncp/pkt_test.go b/src/cypherpunks.ru/nncp/pkt_test.go index ca901a44cece39e222430c0dbede26c570a756f2a33114a936b74328d322cd8b..aedb7d10b117f01654e127a6f45558b09da1398b05cafdf8fedc65d48664d5a9 100644 --- a/src/cypherpunks.ru/nncp/pkt_test.go +++ b/src/cypherpunks.ru/nncp/pkt_test.go @@ -81,7 +81,12 @@ node2, err := NewNodeGenerate() if err != nil { panic(err) } - f := func(path string, pathSize uint8, data [1 << 16]byte, size, padSize uint16, junk []byte) bool { + f := func( + path string, + pathSize uint8, + data [1 << 16]byte, + size, padSize uint16, + junk []byte) bool { dataR := bytes.NewReader(data[:]) var ct bytes.Buffer if len(path) > int(pathSize) { diff --git a/src/cypherpunks.ru/nncp/sp.go b/src/cypherpunks.ru/nncp/sp.go index c0e1e8e99c89e2aafd66d6b3fdfe85a00d0e4711f9b09866f8d846b74cfce1d2..1dccbd4079eb2dd751d3a1a0f8b0c394e2d5164d947d6e9f8cb5cdc0189b7b78 100644 --- a/src/cypherpunks.ru/nncp/sp.go +++ b/src/cypherpunks.ru/nncp/sp.go @@ -99,6 +99,11 @@ Magic [8]byte Payload []byte } +type FreqWithNice struct { + freq *SPFreq + nice uint8 +} + func init() { var buf bytes.Buffer spHead := SPHead{Type: SPTypeHalt} @@ -169,8 +174,8 @@ csOur *noise.CipherState csTheir *noise.CipherState payloads chan []byte infosTheir map[[32]byte]*SPInfo - infosOurSeen map[[32]byte]struct{} - queueTheir []*SPFreq + infosOurSeen map[[32]byte]uint8 + queueTheir []*FreqWithNice wg sync.WaitGroup RxBytes int64 RxLastSeen time.Time @@ -183,6 +188,8 @@ TxSpeed int64 rxLock *os.File txLock *os.File xxOnly TRxTx + rxRate int + txRate int isDead bool sync.RWMutex } @@ -195,7 +202,8 @@ now := time.Now() if state.maxOnlineTime > 0 && state.started.Add(time.Duration(state.maxOnlineTime)*time.Second).Before(now) { return true } - return uint(now.Sub(state.RxLastSeen).Seconds()) >= state.onlineDeadline && uint(now.Sub(state.TxLastSeen).Seconds()) >= state.onlineDeadline + return uint(now.Sub(state.RxLastSeen).Seconds()) >= state.onlineDeadline && + uint(now.Sub(state.TxLastSeen).Seconds()) >= state.onlineDeadline } func (state *SPState) dirUnlock() { @@ -226,7 +234,7 @@ } return sp.Payload, nil } -func (ctx *Ctx) infosOur(nodeId *NodeId, nice uint8, seen *map[[32]byte]struct{}) [][]byte { +func (ctx *Ctx) infosOur(nodeId *NodeId, nice uint8, seen *map[[32]byte]uint8) [][]byte { var infos []*SPInfo var totalSize int64 for job := range ctx.Jobs(nodeId, TTx) { @@ -243,7 +251,7 @@ Nice: job.PktEnc.Nice, Size: uint64(job.Size), Hash: job.HshValue, }) - (*seen)[*job.HshValue] = struct{}{} + (*seen)[*job.HshValue] = job.PktEnc.Nice } sort.Sort(ByNice(infos)) var payloads [][]byte @@ -266,7 +274,13 @@ } return payloadsSplit(payloads) } -func (ctx *Ctx) StartI(conn net.Conn, nodeId *NodeId, nice uint8, xxOnly TRxTx, onlineDeadline, maxOnlineTime uint) (*SPState, error) { +func (ctx *Ctx) StartI( + conn net.Conn, + nodeId *NodeId, + nice uint8, + xxOnly TRxTx, + rxRate, txRate int, + onlineDeadline, maxOnlineTime uint) (*SPState, error) { err := ctx.ensureRxDir(nodeId) if err != nil { return nil, err @@ -310,11 +324,13 @@ maxOnlineTime: maxOnlineTime, nice: nice, payloads: make(chan []byte), infosTheir: make(map[[32]byte]*SPInfo), - infosOurSeen: make(map[[32]byte]struct{}), + infosOurSeen: make(map[[32]byte]uint8), started: started, rxLock: rxLock, txLock: txLock, xxOnly: xxOnly, + rxRate: rxRate, + txRate: txRate, } var infosPayloads [][]byte @@ -388,7 +404,7 @@ ctx: ctx, hs: hs, nice: nice, payloads: make(chan []byte), - infosOurSeen: make(map[[32]byte]struct{}), + infosOurSeen: make(map[[32]byte]uint8), infosTheir: make(map[[32]byte]*SPInfo), started: started, xxOnly: xxOnly, @@ -422,6 +438,8 @@ ctx.LogE("sp-start", SDS{"peer": peerId}, "unknown") return nil, errors.New("Unknown peer: " + peerId) } state.Node = node + state.rxRate = node.RxRate + state.txRate = node.TxRate state.onlineDeadline = node.OnlineDeadline state.maxOnlineTime = node.MaxOnlineTime sds := SDS{"node": node.Id, "nice": strconv.Itoa(int(nice))} @@ -480,7 +498,10 @@ } return &state, err } -func (state *SPState) StartWorkers(conn net.Conn, infosPayloads [][]byte, payload []byte) error { +func (state *SPState) StartWorkers( + conn net.Conn, + infosPayloads [][]byte, + payload []byte) error { sds := SDS{"node": state.Node.Id, "nice": strconv.Itoa(int(state.nice))} if len(infosPayloads) > 1 { go func() { @@ -563,8 +584,13 @@ state.RUnlock() time.Sleep(100 * time.Millisecond) continue } - freq := state.queueTheir[0] + freq := state.queueTheir[0].freq state.RUnlock() + + if state.txRate > 0 { + time.Sleep(time.Second / time.Duration(state.txRate)) + } + sdsp := SdsAdd(sds, SDS{ "xx": string(TTx), "hash": ToBase32(freq.Hash[:]), @@ -618,7 +644,7 @@ sdsp["size"] = strconv.FormatInt(int64(ourSize), 10) sdsp["fullsize"] = strconv.FormatInt(int64(fullSize), 10) state.ctx.LogP("sp-file", sdsp, "") state.Lock() - if len(state.queueTheir) > 0 && *state.queueTheir[0].Hash == *freq.Hash { + if len(state.queueTheir) > 0 && *state.queueTheir[0].freq.Hash == *freq.Hash { if ourSize == fullSize { state.ctx.LogD("sp-file", sdsp, "finished") if len(state.queueTheir) > 1 { @@ -627,7 +653,7 @@ } else { state.queueTheir = state.queueTheir[:0] } } else { - state.queueTheir[0].Offset += uint64(len(buf)) + state.queueTheir[0].freq.Offset += uint64(len(buf)) } } else { state.ctx.LogD("sp-file", sdsp, "queue disappeared") @@ -639,7 +665,7 @@ "sp-xmit", SdsAdd(sds, SDS{"size": strconv.Itoa(len(payload))}), "sending", ) - conn.SetWriteDeadline(time.Now().Add(10 * time.Second)) + conn.SetWriteDeadline(time.Now().Add(DefaultDeadline * time.Second)) if err := state.WriteSP(conn, state.csOur.Encrypt(nil, nil, payload)); err != nil { state.ctx.LogE("sp-xmit", SdsAdd(sds, SDS{"err": err}), "") break @@ -702,6 +728,9 @@ ) state.payloads <- reply } }() + if state.rxRate > 0 { + time.Sleep(time.Second / time.Duration(state.rxRate)) + } } }() @@ -763,22 +792,23 @@ state.Lock() state.infosTheir[*info.Hash] = &info state.Unlock() state.ctx.LogD("sp-process", sdsp, "stating part") - if _, err = os.Stat(filepath.Join( + pktPath := filepath.Join( state.ctx.Spool, state.Node.Id.String(), string(TRx), ToBase32(info.Hash[:]), - )); err == nil { + ) + if _, err = os.Stat(pktPath); err == nil { state.ctx.LogD("sp-process", sdsp, "already done") replies = append(replies, MarshalSP(SPTypeDone, SPDone{info.Hash})) continue } - fi, err := os.Stat(filepath.Join( - state.ctx.Spool, - state.Node.Id.String(), - string(TRx), - ToBase32(info.Hash[:])+PartSuffix, - )) + if _, err = os.Stat(pktPath + SeenSuffix); err == nil { + state.ctx.LogD("sp-process", sdsp, "already seen") + replies = append(replies, MarshalSP(SPTypeDone, SPDone{info.Hash})) + continue + } + fi, err := os.Stat(pktPath + PartSuffix) var offset int64 if err == nil { offset = fi.Size() @@ -922,9 +952,26 @@ state.ctx.LogD("sp-process", SdsAdd(sdsp, SDS{ "hash": ToBase32(freq.Hash[:]), "offset": strconv.FormatInt(int64(freq.Offset), 10), }), "queueing") - state.Lock() - state.queueTheir = append(state.queueTheir, &freq) - state.Unlock() + nice, exists := state.infosOurSeen[*freq.Hash] + if exists { + state.Lock() + insertIdx := 0 + var freqWithNice *FreqWithNice + for insertIdx, freqWithNice = range state.queueTheir { + if freqWithNice.nice > nice { + break + } + } + state.queueTheir = append(state.queueTheir, nil) + copy(state.queueTheir[insertIdx+1:], state.queueTheir[insertIdx:]) + state.queueTheir[insertIdx] = &FreqWithNice{&freq, nice} + state.Unlock() + } else { + state.ctx.LogD("sp-process", SdsAdd(sdsp, SDS{ + "hash": ToBase32(freq.Hash[:]), + "offset": strconv.FormatInt(int64(freq.Offset), 10), + }), "unknown") + } case SPTypeHalt: sdsp := SdsAdd(sds, SDS{"type": "halt"}) state.ctx.LogD("sp-process", sdsp, "") diff --git a/src/cypherpunks.ru/nncp/toss.go b/src/cypherpunks.ru/nncp/toss.go index 1942efc0140cfb768a79b03c8153f3c74db07e61f364d25793cdc4837dc2246e..0465c1ec56f55857a6f9be5d32e2ed529e183d64a11f5933a4602b41c0ed65e5 100644 --- a/src/cypherpunks.ru/nncp/toss.go +++ b/src/cypherpunks.ru/nncp/toss.go @@ -52,7 +52,10 @@ mime.BEncoding.Encode("UTF-8", subject), )) } -func (ctx *Ctx) Toss(nodeId *NodeId, nice uint8, dryRun, doSeen, noFile, noFreq, noExec, noTrns bool) bool { +func (ctx *Ctx) Toss( + nodeId *NodeId, + nice uint8, + dryRun, doSeen, noFile, noFreq, noExec, noTrns bool) bool { isBad := false for job := range ctx.Jobs(nodeId, TRx) { pktName := filepath.Base(job.Fd.Name()) diff --git a/src/cypherpunks.ru/nncp/tx.go b/src/cypherpunks.ru/nncp/tx.go index e93b7e84085653c08d56a67b0b0c5e95dc1b07d7b247a9c961927e343947856d..d3535b296e0a79498e0e8e5059832829314c741b1d5d1a4ba22a797345d3af23 100644 --- a/src/cypherpunks.ru/nncp/tx.go +++ b/src/cypherpunks.ru/nncp/tx.go @@ -36,7 +36,12 @@ "github.com/davecgh/go-xdr/xdr2" "golang.org/x/crypto/blake2b" ) -func (ctx *Ctx) Tx(node *Node, pkt *Pkt, nice uint8, size, minSize int64, src io.Reader) (*Node, error) { +func (ctx *Ctx) Tx( + node *Node, + pkt *Pkt, + nice uint8, + size, minSize int64, + src io.Reader) (*Node, error) { tmp, err := ctx.NewTmpFileWHash() if err != nil { return nil, err @@ -180,7 +185,12 @@ } return err } -func (ctx *Ctx) TxFileChunked(node *Node, nice uint8, srcPath, dstPath string, minSize int64, chunkSize int64) error { +func (ctx *Ctx) TxFileChunked( + node *Node, + nice uint8, + srcPath, dstPath string, + minSize int64, + chunkSize int64) error { if dstPath == "" { if srcPath == "-" { return errors.New("Must provide destination filename") @@ -312,7 +322,11 @@ } return err } -func (ctx *Ctx) TxFreq(node *Node, nice, replyNice uint8, srcPath, dstPath string, minSize int64) error { +func (ctx *Ctx) TxFreq( + node *Node, + nice, replyNice uint8, + srcPath, dstPath string, + minSize int64) error { dstPath = filepath.Clean(dstPath) if filepath.IsAbs(dstPath) { return errors.New("Relative destination path required") @@ -345,7 +359,13 @@ } return err } -func (ctx *Ctx) TxExec(node *Node, nice, replyNice uint8, handle string, args []string, body []byte, minSize int64) error { +func (ctx *Ctx) TxExec( + node *Node, + nice, replyNice uint8, + handle string, + args []string, + body []byte, + minSize int64) error { path := make([][]byte, 0, 1+len(args)) path = append(path, []byte(handle)) for _, arg := range args {