+nncp-ack
nncp-bundle
nncp-call
nncp-caller
* nncp-exec::
* nncp-freq::
* nncp-trns::
+* nncp-ack::
Packets sharing commands
@include cmd/nncp-exec.texi
@include cmd/nncp-freq.texi
@include cmd/nncp-trns.texi
+@include cmd/nncp-ack.texi
@include cmd/nncp-xfer.texi
@include cmd/nncp-bundle.texi
@include cmd/nncp-toss.texi
--- /dev/null
+@node nncp-ack
+@cindex packet acknowledgement
+@pindex nncp-ack
+@section nncp-ack
+
+@example
+$ nncp-ack [options] NODE [PKT|rx]
+@end example
+
+Sent acknowledgement of successful @option{PKT} (Base32-encoded hash)
+packet receipt. If @option{rx} is specified instead, then all
+@option{NODE}'s existing @code{rx} packets will be acknowledged.
+
+General workflow with acknowledgement is following, assuming that
+Alice has some outbound packets for Bob:
+
+@itemize
+
+@item Transfer an encrypted packets, without deleting them locally:
+
+@example
+alice$ nncp-xfer -keep -tx -node bob /mnt/shared
+@end example
+
+@item On Bob's side retrieve those packets:
+
+@example
+bob$ nncp-xfer -rx /mnt/shared
+@end example
+
+That will also check if copied packets checksum is not mismatched.
+
+@item Create ACK packets of received ones:
+
+@example
+bob$ nncp-ack alice rx
+@end example
+
+@item Send those newly created packets back to Alice:
+
+@example
+bob$ nncp-xfer -tx /mnt/shared
+@end example
+
+@item Get those acknowledgement packets and @ref{nncp-toss, toss} them:
+
+@example
+alice$ nncp-xfer -rx /mnt/shared
+alice$ nncp-toss
+@end example
+
+Each ACK packet will remove kept corresponding outbound packets, because
+Bob explicitly confirmed their receipt.
+
+@end itemize
+
+Similarly you can use it with @command{@ref{nncp-bundle}}, but do not
+forget that by default it does not do checksumming of the packets, so
+you should either use its @option{-check} option, or run
+@command{@ref{nncp-check}} after.
@command{nncp-xfer} проверяет сходится ли контрольная сумма
скопированного локально пакета и исходного.
+@item
+Появилась @command{nncp-ack} команда, которая отправляет явное
+подтверждение доставки пакета (ACK пакет). Это подтверждение удаляет
+упомянутый пакет из исходящего spool-а.
+
@item
Появилась возможность отключения @code{fsync} операции
@env{$NNCPNOSYNC=1} переменной окружения.
@command{nncp-xfer} checks if locally copied packet's checksum differs
from the source's one.
+@item
+@command{nncp-ack} command appeared, that sends explicit packet receipt
+acknowledgement (ACK packet). That acknowledgement deletes referenced
+packet from the outbound spool.
+
@item
Ability to turn @code{fsync} operation off using @env{$NNCPNOSYNC=1}
environment variable.
@item trns (transition)
@item exec-fat (uncompressed exec)
@item area (@ref{Multicast, multicast} area message)
+ @item ack (receipt acknowledgement)
@end enumerate
@item Niceness @tab
unsigned integer @tab
@item UTF-8 encoded, zero byte separated, exec's arguments
@item Node's id the transition packet must be relayed on
@item Multicast area's id
+ @item Packet's id (its @ref{MTH} hash)
@end itemize
@end multitable
compressed exec body
@item Whole encrypted packet we need to relay on
@item Multicast area message wrap with another encrypted packet inside
+@item Nothing, if it is acknowledgement packet
@end itemize
Also depending on packet's type, niceness level means:
@end example
See also @ref{Encrypted area, encrypted area packet}.
+@item ack
+@example
+ +------- PATH --------+
+ / \
++-------------------------+
+| PKT ID | 0x00 ... 0x00 |
++-------------------------+
+ \ /
+ PATHLEN
+@end example
+
@end table
чтобы найти все пакеты относящиеся к их узлу и локально скопируют для
дальнейшей обработки. @command{@ref{nncp-xfer}} это единственная команда
используемая с переносными устройствами хранения.
+
+Вы также можете опционально использовать явное подтверждение приёма
+пакетов, как описано в @command{@ref{nncp-ack}}.
to find all packets related to their node and copy them locally for
further processing. @command{@ref{nncp-xfer}} is the only command used with
removable devices.
+
+You can also optionally wait for explicit packets receipt
+acknowledgement as described in @command{@ref{nncp-ack}}.
processing) all inbound queues to receive exec messages, files, file
requests and relay transition packets to other nodes.
+@item Optionally do not forget about explicit receipt acknowledgement
+ability with @command{@ref{nncp-ack}}.
+
@end enumerate
@itemize
PORTNAME= nncp
-DISTVERSION= 7.6.0
+DISTVERSION= 8.6.0
CATEGORIES= net
MASTER_SITES= http://www.nncpgo.org/download/
+bin/nncp-ack
bin/nncp-bundle
bin/nncp-call
bin/nncp-caller
AutoTossNoExec bool
AutoTossNoTrns bool
AutoTossNoArea bool
+ AutoTossNoACK bool
}
func (ctx *Ctx) CallNode(
--- /dev/null
+/*
+NNCP -- Node to Node copy, utilities for store-and-forward data exchange
+Copyright (C) 2016-2022 Sergey Matveev <stargrave@stargrave.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 3 of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// Send packet receipt acknowledgement via NNCP.
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "path/filepath"
+
+ "go.cypherpunks.ru/nncp/v8"
+)
+
+func usage() {
+ fmt.Fprintf(os.Stderr, nncp.UsageHeader())
+ fmt.Fprintf(os.Stderr, "nncp-ack -- send packet receipt acknowledgement\n\n")
+ fmt.Fprintf(os.Stderr, "Usage: %s [options] NODE [PKT|rx]\nOptions:\n", os.Args[0])
+ flag.PrintDefaults()
+}
+
+func main() {
+ var (
+ cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file")
+ niceRaw = flag.String("nice", nncp.NicenessFmt(nncp.DefaultNiceFreq), "Outbound packet niceness")
+ minSizeRaw = 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")
+ logPath = flag.String("log", "", "Override path to logfile")
+ quiet = flag.Bool("quiet", false, "Print only errors")
+ showPrgrs = flag.Bool("progress", false, "Force progress showing")
+ omitPrgrs = flag.Bool("noprogress", false, "Omit progress showing")
+ debug = flag.Bool("debug", false, "Print debug messages")
+ version = flag.Bool("version", false, "Print version information")
+ warranty = flag.Bool("warranty", false, "Print warranty information")
+ )
+ log.SetFlags(log.Lshortfile)
+ flag.Usage = usage
+ flag.Parse()
+ if *warranty {
+ fmt.Println(nncp.Warranty)
+ return
+ }
+ if *version {
+ fmt.Println(nncp.VersionGet())
+ return
+ }
+ if flag.NArg() != 2 {
+ usage()
+ os.Exit(1)
+ }
+ nice, err := nncp.NicenessParse(*niceRaw)
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ ctx, err := nncp.CtxFromCmdline(
+ *cfgPath,
+ *spoolPath,
+ *logPath,
+ *quiet,
+ *showPrgrs,
+ *omitPrgrs,
+ *debug,
+ )
+ if err != nil {
+ log.Fatalln("Error during initialization:", err)
+ }
+ if ctx.Self == nil {
+ log.Fatalln("Config lacks private keys")
+ }
+
+ node, err := ctx.FindNode(flag.Arg(0))
+ if err != nil {
+ log.Fatalln("Invalid NODE specified:", err)
+ }
+
+ nncp.ViaOverride(*viaOverride, ctx, node)
+ ctx.Umask()
+ minSize := int64(*minSizeRaw) * 1024
+
+ if flag.Arg(1) == string(nncp.TRx) {
+ for job := range ctx.Jobs(node.Id, nncp.TRx) {
+ pktName := filepath.Base(job.Path)
+ if err = ctx.TxACK(node, nice, pktName, minSize); err != nil {
+ log.Fatalln(err)
+ }
+ }
+ } else {
+ if err = ctx.TxACK(node, nice, flag.Arg(1), minSize); err != nil {
+ log.Fatalln(err)
+ }
+ }
+}
autoTossNoExec = flag.Bool("autotoss-noexec", false, "Do not process \"exec\" packets during tossing")
autoTossNoTrns = flag.Bool("autotoss-notrns", false, "Do not process \"trns\" packets during tossing")
autoTossNoArea = flag.Bool("autotoss-noarea", false, "Do not process \"area\" packets during tossing")
+ autoTossNoACK = flag.Bool("autotoss-noack", false, "Do not process \"ack\" packets during tossing")
)
log.SetFlags(log.Lshortfile)
flag.Usage = usage
*autoTossNoExec,
*autoTossNoTrns,
*autoTossNoArea,
+ *autoTossNoACK,
)
}
autoTossNoExec = flag.Bool("autotoss-noexec", false, "Do not process \"exec\" packets during tossing")
autoTossNoTrns = flag.Bool("autotoss-notrns", false, "Do not process \"trns\" packets during tossing")
autoTossNoArea = flag.Bool("autotoss-noarea", false, "Do not process \"area\" packets during tossing")
+ autoTossNoACK = flag.Bool("autotoss-noack", false, "Do not process \"ack\" packets during tossing")
)
log.SetFlags(log.Lshortfile)
flag.Usage = usage
call.AutoTossNoExec || *autoTossNoExec,
call.AutoTossNoTrns || *autoTossNoTrns,
call.AutoTossNoArea || *autoTossNoArea,
+ call.AutoTossNoACK || *autoTossNoACK,
)
}
autoTossNoExec = flag.Bool("autotoss-noexec", false, "Do not process \"exec\" packets during tossing")
autoTossNoTrns = flag.Bool("autotoss-notrns", false, "Do not process \"trns\" packets during tossing")
autoTossNoArea = flag.Bool("autotoss-noarea", false, "Do not process \"area\" packets during tossing")
+ autoTossNoACK = flag.Bool("autotoss-noack", false, "Do not process \"ack\" packets during tossing")
)
log.SetFlags(log.Lshortfile)
flag.Usage = usage
*autoTossNoExec,
*autoTossNoTrns,
*autoTossNoArea,
+ *autoTossNoACK,
)
}
<-nodeIdC // call completion
*autoTossNoExec,
*autoTossNoTrns,
*autoTossNoArea,
+ *autoTossNoACK,
)
}
<-nodeIdC // call completion
payloadType = "exec uncompressed"
case nncp.PktTypeArea:
payloadType = "area"
+ case nncp.PktTypeACK:
+ payloadType = "acknowledgement"
}
var path string
switch pkt.Type {
if areaId, err := nncp.AreaIdFromString(path); err == nil {
path = fmt.Sprintf("%s (%s)", path, ctx.AreaName(areaId))
}
+ case nncp.PktTypeACK:
+ path = nncp.Base32Codec.EncodeToString(pkt.Path[:pkt.PathLen])
default:
path = string(pkt.Path[:pkt.PathLen])
}
noExec = flag.Bool("noexec", false, "Do not process \"exec\" packets")
noTrns = flag.Bool("notrns", false, "Do not process \"transitional\" packets")
noArea = flag.Bool("noarea", false, "Do not process \"area\" packets")
+ noACK = flag.Bool("noack", false, "Do not process \"ack\" packets")
spoolPath = flag.String("spool", "", "Override path to spool")
logPath = flag.String("log", "", "Override path to logfile")
quiet = flag.Bool("quiet", false, "Print only errors")
node.Id,
nncp.TRx,
nice,
- *dryRun, *doSeen, *noFile, *noFreq, *noExec, *noTrns, *noArea,
+ *dryRun, *doSeen, *noFile, *noFreq, *noExec, *noTrns, *noArea, *noACK,
) || isBad
if nodeId == *ctx.SelfId {
isBad = ctx.Toss(
node.Id,
nncp.TTx,
nice,
- *dryRun, false, true, true, true, true, *noArea,
+ *dryRun, false, true, true, true, true, *noArea, *noACK,
) || isBad
}
}
nodeId,
nncp.TRx,
nice,
- *dryRun, *doSeen, *noFile, *noFreq, *noExec, *noTrns, *noArea,
+ *dryRun, *doSeen, *noFile, *noFreq, *noExec, *noTrns, *noArea, *noACK,
)
if *nodeId == *ctx.SelfId {
ctx.Toss(
nodeId,
nncp.TTx,
nice,
- *dryRun, false, true, true, true, true, *noArea,
+ *dryRun, false, true, true, true, true, *noArea, *noACK,
)
}
}
const Base32Encoded32Len = 52
var (
- Version string = "8.5.0"
+ Version string = "8.6.0"
Base32Codec *base32.Encoding = base32.StdEncoding.WithPadding(base32.NoPadding)
)
PktTypeTrns PktType = iota
PktTypeExecFat PktType = iota
PktTypeArea PktType = iota
+ PktTypeACK PktType = iota
MaxPathSize = 1<<8 - 1
pktSize uint64,
jobPath string,
decompressor *zstd.Decoder,
- dryRun, doSeen, noFile, noFreq, noExec, noTrns, noArea bool,
+ dryRun, doSeen, noFile, noFreq, noExec, noTrns, noArea, noACK bool,
) error {
defer pipeR.Close()
sendmail := ctx.Neigh[*ctx.SelfId].Exec["sendmail"]
uint64(pktSizeWithoutEnc(int64(pktSize))),
"",
decompressor,
- dryRun, doSeen, noFile, noFreq, noExec, noTrns, noArea,
+ dryRun, doSeen, noFile, noFreq, noExec, noTrns, noArea, noACK,
)
}()
_, _, _, err = PktEncRead(
}
}
+ case PktTypeACK:
+ if noACK {
+ return nil
+ }
+ hsh := Base32Codec.EncodeToString(pkt.Path[:MTHSize])
+ les := append(les, LE{"Type", "ack"}, LE{"Pkt", hsh})
+ logMsg := func(les LEs) string {
+ return fmt.Sprintf("Tossing ack %s/%s: %s", sender.Name, pktName, hsh)
+ }
+ ctx.LogD("rx-ack", les, logMsg)
+ pktPath := filepath.Join(ctx.Spool, sender.Id.String(), string(TTx), hsh)
+ if _, err := os.Stat(pktPath); err == nil {
+ if !dryRun {
+ if err = os.Remove(pktPath); err != nil {
+ ctx.LogE("rx-ack", les, err, func(les LEs) string {
+ return logMsg(les) + ": removing packet"
+ })
+ return err
+ }
+ }
+ } else {
+ ctx.LogD("rx-ack", les, func(les LEs) string {
+ return logMsg(les) + ": already disappeared"
+ })
+ }
+ if !dryRun && doSeen {
+ if err := ensureDir(filepath.Dir(jobPath), SeenDir); err != nil {
+ return err
+ }
+ if fd, err := os.Create(jobPath2Seen(jobPath)); err == nil {
+ fd.Close()
+ if err = DirSync(filepath.Dir(jobPath)); err != nil {
+ ctx.LogE("rx-dirsync", les, err, func(les LEs) string {
+ return fmt.Sprintf(
+ "Tossing file %s/%s (%s): %s: dirsyncing",
+ sender.Name, pktName,
+ humanize.IBytes(pktSize),
+ filepath.Base(jobPath),
+ )
+ })
+ return err
+ }
+ }
+ }
+ if !dryRun {
+ if err = os.Remove(jobPath); err != nil {
+ ctx.LogE("rx", les, err, func(les LEs) string {
+ return logMsg(les) + ": removing job"
+ })
+ return err
+ } else if ctx.HdrUsage {
+ os.Remove(JobPath2Hdr(jobPath))
+ }
+ }
+ ctx.LogI("rx", les, func(les LEs) string {
+ return fmt.Sprintf("Got ACK packet from %s of %s", sender.Name, hsh)
+ })
+
default:
err = errors.New("unknown type")
ctx.LogE(
nodeId *NodeId,
xx TRxTx,
nice uint8,
- dryRun, doSeen, noFile, noFreq, noExec, noTrns, noArea bool,
+ dryRun, doSeen, noFile, noFreq, noExec, noTrns, noArea, noACK bool,
) bool {
dirLock, err := ctx.LockDir(nodeId, "toss")
if err != nil {
uint64(pktSizeWithoutEnc(job.Size)),
job.Path,
decompressor,
- dryRun, doSeen, noFile, noFreq, noExec, noTrns, noArea,
+ dryRun, doSeen, noFile, noFreq, noExec, noTrns, noArea, noACK,
)
}()
pipeWB := bufio.NewWriter(pipeW)
func (ctx *Ctx) AutoToss(
nodeId *NodeId,
nice uint8,
- doSeen, noFile, noFreq, noExec, noTrns, noArea bool,
+ doSeen, noFile, noFreq, noExec, noTrns, noArea, noACK bool,
) (chan struct{}, chan bool) {
dw, err := ctx.NewDirWatcher(
filepath.Join(ctx.Spool, nodeId.String(), string(TRx)),
case <-dw.C:
bad = !ctx.Toss(
nodeId, TRx, nice, false,
- doSeen, noFile, noFreq, noExec, noTrns, noArea) || bad
+ doSeen, noFile, noFreq, noExec, noTrns, noArea, noACK) || bad
}
}
}()
continue
}
ctx.Toss(ctx.Self.Id, TRx, DefaultNiceExec-1,
- false, false, false, false, false, false, false)
+ false, false, false, false, false, false, false, false)
if len(dirFiles(rxPath)) == 0 {
return false
}
ctx.Neigh[*nodeOur.Id].Exec = make(map[string][]string)
ctx.Neigh[*nodeOur.Id].Exec[handle] = []string{"/bin/sh", "-c", "false"}
ctx.Toss(ctx.Self.Id, TRx, DefaultNiceExec,
- false, false, false, false, false, false, false)
+ false, false, false, false, false, false, false, false)
if len(dirFiles(rxPath)) == 0 {
return false
}
),
}
ctx.Toss(ctx.Self.Id, TRx, DefaultNiceExec,
- false, false, false, false, false, false, false)
+ false, false, false, false, false, false, false, false)
if len(dirFiles(rxPath)) != 0 {
return false
}
rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx))
os.Rename(filepath.Join(spool, ctx.Self.Id.String(), string(TTx)), rxPath)
ctx.Toss(ctx.Self.Id, TRx, DefaultNiceFile,
- false, false, false, false, false, false, false)
+ false, false, false, false, false, false, false, false)
if len(dirFiles(rxPath)) == 0 {
return false
}
ctx.Neigh[*nodeOur.Id].Incoming = &incomingPath
if ctx.Toss(ctx.Self.Id, TRx, DefaultNiceFile,
- false, false, false, false, false, false, false) {
+ false, false, false, false, false, false, false, false) {
return false
}
if len(dirFiles(rxPath)) != 0 {
os.Rename(filepath.Join(spool, ctx.Self.Id.String(), string(TTx)), rxPath)
ctx.Neigh[*nodeOur.Id].Incoming = &incomingPath
ctx.Toss(ctx.Self.Id, TRx, DefaultNiceFile,
- false, false, false, false, false, false, false)
+ false, false, false, false, false, false, false, false)
expected := make(map[string]struct{})
expected["samefile"] = struct{}{}
for i := 0; i < files-1; i++ {
os.Rename(txPath, rxPath)
os.MkdirAll(txPath, os.FileMode(0700))
ctx.Toss(ctx.Self.Id, TRx, DefaultNiceFreq,
- false, false, false, false, false, false, false)
+ false, false, false, false, false, false, false, false)
if len(dirFiles(txPath)) != 0 || len(dirFiles(rxPath)) == 0 {
return false
}
ctx.Neigh[*nodeOur.Id].FreqPath = &spool
ctx.Toss(ctx.Self.Id, TRx, DefaultNiceFreq,
- false, false, false, false, false, false, false)
+ false, false, false, false, false, false, false, false)
if len(dirFiles(txPath)) != 0 || len(dirFiles(rxPath)) == 0 {
return false
}
}
}
ctx.Toss(ctx.Self.Id, TRx, DefaultNiceFreq,
- false, false, false, false, false, false, false)
+ false, false, false, false, false, false, false, false)
if len(dirFiles(txPath)) == 0 || len(dirFiles(rxPath)) != 0 {
return false
}
}
}
ctx.Toss(ctx.Self.Id, TRx, 123,
- false, false, false, false, false, false, false)
+ false, false, false, false, false, false, false, false)
if len(dirFiles(rxPath)) != 0 {
return false
}
os.Symlink(nodePath, filepath.Join(ctx.Spool, node.Name))
return err
}
+
+func (ctx *Ctx) TxACK(
+ node *Node,
+ nice uint8,
+ hsh string,
+ minSize int64,
+) error {
+ hshRaw, err := Base32Codec.DecodeString(hsh)
+ if err != nil {
+ return err
+ }
+ if len(hshRaw) != MTHSize {
+ return errors.New("Invalid packet id size")
+ }
+ pkt, err := NewPkt(PktTypeACK, nice, []byte(hshRaw))
+ if err != nil {
+ return err
+ }
+ src := bytes.NewReader([]byte{})
+ _, _, err = ctx.Tx(node, pkt, nice, 0, minSize, MaxFileSize, src, hsh, nil)
+ les := LEs{
+ {"Type", "ack"},
+ {"Node", node.Id},
+ {"Nice", int(nice)},
+ {"Pkt", hsh},
+ }
+ logMsg := func(les LEs) string {
+ return fmt.Sprintf("ACK to %s of %s is sent", ctx.NodeName(node.Id), hsh)
+ }
+ if err == nil {
+ ctx.LogI("tx", les, logMsg)
+ } else {
+ ctx.LogE("tx", les, err, logMsg)
+ }
+ return err
+}