Add an xC relay protocol analyzer
This commit is contained in:
parent
7ba17a0161
commit
80af5c22d6
10
xN/xN.go
10
xN/xN.go
@ -247,16 +247,16 @@ func main() {
|
|||||||
flag.PrintDefaults()
|
flag.PrintDefaults()
|
||||||
}
|
}
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if flag.NArg() < 1 {
|
|
||||||
flag.Usage()
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *version {
|
if *version {
|
||||||
fmt.Printf("%s %s\n", projectName, projectVersion)
|
fmt.Printf("%s %s\n", projectName, projectVersion)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if flag.NArg() < 1 {
|
||||||
|
flag.Usage()
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
text, err := io.ReadAll(os.Stdin)
|
text, err := io.ReadAll(os.Stdin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
|
2
xR/.gitignore
vendored
Normal file
2
xR/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/xR
|
||||||
|
/proto.go
|
17
xR/Makefile
Normal file
17
xR/Makefile
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
.POSIX:
|
||||||
|
AWK = env LC_ALL=C awk
|
||||||
|
|
||||||
|
tools = ../liberty/tools
|
||||||
|
generated = proto.go
|
||||||
|
outputs = xR $(generated)
|
||||||
|
all: $(outputs)
|
||||||
|
generate: $(generated)
|
||||||
|
|
||||||
|
proto.go: $(tools)/lxdrgen.awk $(tools)/lxdrgen-go.awk ../xC.lxdr
|
||||||
|
$(AWK) -f $(tools)/lxdrgen.awk -f $(tools)/lxdrgen-go.awk \
|
||||||
|
-v PrefixCamel=Relay ../xC.lxdr > $@
|
||||||
|
xR: xR.go ../xK-version $(generated)
|
||||||
|
go build -ldflags "-X 'main.projectVersion=$$(cat ../xK-version)'" -o $@ \
|
||||||
|
-gcflags=all="-N -l"
|
||||||
|
clean:
|
||||||
|
rm -f $(outputs)
|
5
xR/go.mod
Normal file
5
xR/go.mod
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module janouch.name/xK/xR
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.24.0
|
41
xR/xR.adoc
Normal file
41
xR/xR.adoc
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
xR(1)
|
||||||
|
=====
|
||||||
|
:doctype: manpage
|
||||||
|
:manmanual: xK Manual
|
||||||
|
:mansource: xK {release-version}
|
||||||
|
|
||||||
|
Name
|
||||||
|
----
|
||||||
|
xR - xC relay protocol analyzer
|
||||||
|
|
||||||
|
Synopsis
|
||||||
|
--------
|
||||||
|
*xR* [_OPTION_]... RELAY-ADDRESS...
|
||||||
|
|
||||||
|
Description
|
||||||
|
-----------
|
||||||
|
*xR* connects to an *xC* relay and prints all incoming events one per line
|
||||||
|
in JSON format. The JSON objects have two additional fields:
|
||||||
|
|
||||||
|
when::
|
||||||
|
The time of reception (or sending) as a nanosecond precision
|
||||||
|
RFC 3339 UTC timestamp.
|
||||||
|
raw::
|
||||||
|
The incoming event (or outgoing command) in raw binary form.
|
||||||
|
|
||||||
|
Options
|
||||||
|
-------
|
||||||
|
*-debug*::
|
||||||
|
Print any outgoing commands as well, which may help in debugging any issues.
|
||||||
|
|
||||||
|
*-version*::
|
||||||
|
Output version information and exit.
|
||||||
|
|
||||||
|
Reporting bugs
|
||||||
|
--------------
|
||||||
|
Use https://git.janouch.name/p/xK to report bugs, request features,
|
||||||
|
or submit pull requests.
|
||||||
|
|
||||||
|
See also
|
||||||
|
--------
|
||||||
|
*xC*(1)
|
134
xR/xR.go
Normal file
134
xR/xR.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
// Copyright (c) 2025, Přemysl Eric Janouch <p@janouch.name>
|
||||||
|
// SPDX-License-Identifier: 0BSD
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
debug = flag.Bool("debug", false, "enable debug output")
|
||||||
|
version = flag.Bool("version", false, "show version and exit")
|
||||||
|
projectName = "xR"
|
||||||
|
projectVersion = "?"
|
||||||
|
)
|
||||||
|
|
||||||
|
func now() string {
|
||||||
|
return time.Now().UTC().Format(time.RFC3339Nano)
|
||||||
|
}
|
||||||
|
|
||||||
|
func relayReadFrame(r io.Reader) bool {
|
||||||
|
var length uint32
|
||||||
|
if err := binary.Read(
|
||||||
|
r, binary.BigEndian, &length); errors.Is(err, io.EOF) {
|
||||||
|
return false
|
||||||
|
} else if err != nil {
|
||||||
|
log.Fatalln("Event receive failed: " + err.Error())
|
||||||
|
}
|
||||||
|
b := make([]byte, length)
|
||||||
|
if _, err := io.ReadFull(r, b); errors.Is(err, io.EOF) {
|
||||||
|
return false
|
||||||
|
} else if err != nil {
|
||||||
|
log.Fatalln("Event receive failed: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
m := struct {
|
||||||
|
When string `json:"when"`
|
||||||
|
Binary []byte `json:"raw"`
|
||||||
|
RelayEventMessage
|
||||||
|
}{
|
||||||
|
When: now(),
|
||||||
|
Binary: b,
|
||||||
|
}
|
||||||
|
|
||||||
|
if after, ok := m.RelayEventMessage.ConsumeFrom(b); !ok {
|
||||||
|
log.Println("Event deserialization failed")
|
||||||
|
} else if len(after) != 0 {
|
||||||
|
log.Println("Event deserialization failed: trailing data")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
j, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Event marshalling failed: " + err.Error())
|
||||||
|
}
|
||||||
|
fmt.Printf("%s\n", j)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(addressConnect string) {
|
||||||
|
conn, err := net.Dial("tcp", addressConnect)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Connection failed: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
// We can only support this one protocol version
|
||||||
|
// that proto.go has been generated for.
|
||||||
|
m := RelayCommandMessage{CommandSeq: 0, Data: RelayCommandData{
|
||||||
|
Variant: &RelayCommandDataHello{Version: RelayVersion},
|
||||||
|
}}
|
||||||
|
|
||||||
|
b, ok := m.AppendTo(make([]byte, 4))
|
||||||
|
if !ok {
|
||||||
|
log.Fatalln("Command serialization failed")
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint32(b[:4], uint32(len(b)-4))
|
||||||
|
if _, err := conn.Write(b); err != nil {
|
||||||
|
log.Fatalln("Command send failed: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can differentiate the direction by the presence
|
||||||
|
// of .data.command or .data.event.
|
||||||
|
if *debug {
|
||||||
|
j, err := json.Marshal(struct {
|
||||||
|
When string `json:"when"`
|
||||||
|
Binary []byte `json:"raw"`
|
||||||
|
RelayCommandMessage
|
||||||
|
}{
|
||||||
|
When: now(),
|
||||||
|
Binary: b,
|
||||||
|
RelayCommandMessage: m,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Command marshalling failed: " + err.Error())
|
||||||
|
}
|
||||||
|
fmt.Printf("%s\n", j)
|
||||||
|
}
|
||||||
|
|
||||||
|
for relayReadFrame(conn) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Usage = func() {
|
||||||
|
fmt.Fprintf(flag.CommandLine.Output(),
|
||||||
|
"Usage: %s [OPTION...] CONNECT\n\n", os.Args[0])
|
||||||
|
flag.PrintDefaults()
|
||||||
|
}
|
||||||
|
flag.Parse()
|
||||||
|
if *version {
|
||||||
|
fmt.Printf("%s %s (relay protocol version %d)\n",
|
||||||
|
projectName, projectVersion, RelayVersion)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if flag.NArg() != 1 {
|
||||||
|
flag.Usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(p): This program should be able to run as a filter as well.
|
||||||
|
run(flag.Arg(0))
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user