rpc25519

package module
v1.0.80 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 17, 2024 License: BSD-3-Clause Imports: 41 Imported by: 1

README

rpc25519: Edwards curve ed25519 based identity RPC for Go/golang

Motivation: I needed a small, simple, and compact RPC system with modern, strong cryptography for goq. Other RPC systems were too sprawling, had bugs their devs would not address, or could not be properly or easily secured. To that end, rpc25519 only uses ed25519 Edwards curve based public-key cryptography. A second pre-shared-key layer, based on XChaCha20-Poly1305 symmetric stream encryption, can also be added.

Excitedly, I am delighted to report this package also supports QUIC as a transport. QUIC is very fast even though it is always encrypted. This is due to its 0-RTT design and the mature quic-go implementation of the protocol. QUIC allows a local client and server in the same process to share a UDP port. This feature can be super useful for conserving ports and connecting across networks.

In the broadest of strokes:

rpc25519 is a Remote Procedure Call system with two APIs.

We offer both a traditional net/rpc style API, and a generic []byte oriented API for carrying untyped []byte payloads (in Message.JobSerz).

Using the rpc25519.Message based API:

Using the net/rpc API:

See the net/rpc docs for full guidance on using that API.

  • Extended method types:

Callback methods in the net/rpc style traditionally look like this first NoContext example. We allow a context.Context as an additional first parameter. The ctx will have an "HDR" value set on it giving a pointer to the rpc25519.HDR header from the incoming Message.

func (s *Service) NoContext(args *Args, reply *Reply) error

func (s *Service) GetsContext(ctx context.Context, args *Args, reply *Reply) error {
if hdr := ctx.Value("HDR"); hdr != nil {
     h, ok := hdr.(*rpc25519.HDR)
     if ok {
        fmt.Printf("GetsContext called with HDR = '%v'; "+
           "HDR.Nc.RemoteAddr() gives '%v'; HDR.Nc.LocalAddr() gives '%v'\n", 
           h.String(), h.Nc.RemoteAddr(), h.Nc.LocalAddr())
      }
   } else {
      fmt.Println("HDR not found")
   }
}

TODO/deferred: The client does not transmit context to server at the moment. Although the context package clearly describes this is an intended use, I don't have a use case myself for it, and so I'm not sure how the implementation should work. How are cancellation requests coordinated across a network? Do they initiate their own messages?

The net/rpc API is implemented as a layer on top of the rpc25519.Message based API. Both can be used concurrently if desired.

Server push is available too. Use Client.GetReadIncomingCh or Client.GetReads on the client side to receive server initiated messages. To push from the server (in a callback func), see Server.SendMessage. An live application example of server push is here, in the ServerCallbackMgr.pushToClient() method.

See the full source for my distributed job-queuing server goq as an example application that uses most all features of this, the rpc25519 package.

In the following we'll look at choice of transport, why public-key certs are preferred, and how to use the included selfy tool to easily generate self-signed certificates.

TLS-v1.3 over TCP

Three transports are available: TLS-v1.3 over TCP, plain TCP, and QUIC which uses TLS-v1.3 over UDP.

QUIC is so much faster than even plain TCP, it should probably be your default choice.

The only difficulty comes from IPv6 networks with VPNs. They are not very friendly to QUIC. Typically they have minimal 1280 MTU, so we'll have a problem if any kind of VPN layer is in use. Tailscale/Wireguard add an extra 28 bytes of ICMP/IP headers and try to keep packets 1:1, thus going over the MTU and not really working. IPv6 cannot fragment packets in the network, so they just get rejected outright. This is my surmize. I could be misunderstanding the root cause. The fact remains that in testing over IPv6 VPN links, rpc25519's QUIC nodes could not talk to each other.

Context: https://github.com/tailscale/tailscale/issues/2633

Comments/links: https://gist.github.com/jj1bdx/1adac3e305d0fb6dee90dd5b909513ed

At the moment we will detect and panic in the case an IPv6 network is used with QUIC to warn you of this issue.

How to KYC or Know Your Clients

How do we identify our clients in an RPC situation?

We have clients and servers in RPC. For that matter, how do we authenticate the server too?

The identity of an both client and server, either end of a connection, is established with a private-key/public-key pair in which the remote party proves posession of a private-key and we can confirm that the associated public-key has been signed by our certificate authority. Public keys signed in this way are more commonly called certificates or "certs".

An email address for convenience of identification of jobs can be listed in the certificate.

This is a much saner approach than tying a work-load identity to a specific machine, domain name, or machine port. These incidental details are artifacts of the network design, easily spoofed or phished, and are not reliable or desirable identitiers. Only access to the private key corresponding to our cert should convey identity during a TLS handshake. The later part of the handshake verifies that the key was signed by our CA. This more than suffices.

Such a requirement maintains the strongest security but still allows elastic clouds to grow and shrink and migrate the workloads of both clients and servers. It allows for sane development and deployment on any hardware a developer can access with ssh, without compromising on encryption on either end.

TOFU or not

By creating (touch will do) a known_client_keys file on the server directory where the server is running, you activate the key tracking system on the server.

Similarly, by touching the known_server_keys file in the directory where the client is running, you tell the client to record the server certificates it has seen.

Without these files, no history of seen certs is recorded. While certs are still checked (assuming you have left SkipVerifyKeys at the default false value in the config), they are not remembered.

In a typical use case, touch the file to record the certs in use, then make the known keys file read-only to block out any new certs.

This works because clients are accepted or rejected depending on the writability of the known_client_keys file, and the presence of their certificates in that file.

Trust on first use, or TOFU, is used if the known_client_keys file is writable. When the file is writable and a new client arrives, their public key and email (if available) are stored. This is similar to how the classic ~/.ssh/known_hosts file works, but with a twist.

The twist is that once the desired identities have been recorded, the file can be used to reject any new or unknown certs from new or unkown clients.

If the file has been made unwritable (say with chmod -w known_client_keys), then we are "locked down". In this case, TOFU is not allowed and only existing keys will be accepted. Any others whose public keys are not in the known_client_hosts file will be dropped during the TLS handshake.

These unknown clients are usually just attackers that should in fact be rejected soundly. In case of misconfiguration however, all clients should be prepared to timeout as, if they are not on the list, they will be shunned and shown no further attention or packets. Without a timeout, they may well hang indefinitely waiting for network activity.

encryption details

Modern Ed25519 keys are used with TLS-1.3. The TLS_CHACHA20_POLY1305_SHA256 cipher suite is the only one configured. This is similar to the crypto suite used in Wireguard (TailScale), the only difference being that Wireguard uses Blake2s as a hash function (apparently faster than SHA-256 when hardware support SHA-extensions/SHA-NI instructions are not available). The ChaCha20, Poly1305, and Curve25519 parts are the same.

Config.PreSharedKeyPath allows specifying a 32 byte pre-shared key file for further security. An additional, independent layer of XChaCha20-Poly1305 stream cipher/AEAD will be applied based on that key. The same key content must be available to both client and server.

security posture for both extremes

The strength of security is controlled by the Config options to NewServer() and NewClient().

See the cli.go file.

By default security is very strong, requiring TLS-1.3 and valid signed client certs, but allowing TOFU for previously unseen clients who come bearing valid certs; those signed by our CA.

This allows one to test one's initial setup with a minimum of fuss.

Further hardening (into a virtual fortress) can then be accomplished by making read-only the set of already seen clients; with chmod -w known_client_keys.

With this done, only those clients who we have already seen will be permitted in; and these are clients bearing proper certs signed by our CA private key; this will be verified during the TLS handshake. Any others will be rejected.

On the other extreme, setting TCPonly_no_TLS to true means we will use only TCP, no TLS at all, and everything will be transmitted in clear text.

In the middle of the road, setting Config.SkipVerifyKeys to true means the server will act like an HTTPS web server: any client with any key will be allowed to talk to the server, but TLS-1.3 will encrypt the traffic. Whenever TLS is used, it is always TLS-1.3 and set to the tls.TLS_CHACHA20_POLY1305_SHA256 cipher suite. Thus this setting is of no use for authenticating clients; it should almost never be used since it opens up your server to the world. But for web servers, this may be desirable.

Note that both sides of the connection (client and server) must agree to -skip-verify (Config.SkipVerifyKeys = true); otherwise the signing authority (CA) must agree. Under the default, SkipVerifyKeys = false, by signing only your own keys, with your own CA, that you keep private from the world, you maintain very tight control over access to your server.

We only create and only accept Ed25519 keys (with SkipVerifyKeys off/false, the default). (See selfy below, or the included gen.sh script to create keys).

// Config says who to contact (for a client), or
// where to listen (for a server); and sets how
// strong a security posture we adopt.
type Config struct {

	// ServerAddr host:port where the server should listen.
	ServerAddr string

	// optional. Can be used to suggest that the 
	// client use a specific host:port. NB: For QUIC, by default, the client and
	// server will share the same port if they are in the same process.
	// In that case this setting will definitely be ignored.
	ClientHostPort string

	// Who the client should contact
	ClientDialToHostPort string

	// TCP false means TLS-1.3 secured. true here means do TCP only; with no encryption.
	TCPonly_no_TLS bool

	// UseQUIC cannot be true if TCPonly_no_TLS is true.
	UseQUIC bool

	// path to certs/ like certificate directory 
	// on the live filesystem. If left
	// empty then the embedded certs/ from build-time, those 
	// copied from the on-disk certs/ directory and baked 
	// into the executable as a virtual file system with
	// the go:embed directive are used.
	CertPath string

	// SkipVerifyKeys true allows any incoming
	// key to be signed by
	// any CA; it does not have to be ours. Obviously
	// this discards almost all access control; it
	// should rarely be used unless communication
	// with the any random agent/hacker/public person
	// is desired.
	SkipVerifyKeys bool
	
	// This is not a Config option, but creating
	// the known key file on the client/server is
	// typically the last security measure in hardening.
	//
	// If known_client_keys exists on the server,
	// then we will read from it.
	// Likewise, if known_server_keys exists on
	// the client, then we will read from it.
	//
	// If the known keys file is read-only: Read-only
	// means we are in lockdown mode and no unknown
	// client certs will be accepted, even if they
	// have been properly signed by our CA.
	//
	// If the known keys file is writable then we are
	// Trust On First Use mode, and new remote parties
	// are recorded in the file if their certs are valid (signed
	// by us/our CA).
	//
	// Note if the known_client_keys is read-only, it
	// had better not be empty or nobody will be
	// able to contact us. The server will notice
	// this and crash since why bother being up.

	...
}

The selfy tool: create new keys quickly; view certificates

Certificates are great, but making them has traditionally been a massive pain. That is why I wrote selfy.

The selfy command is an easy way to create private keys, certificates, and self-signed certficate authories. It is vastly more usable than the mountain of complexity that is openssl. It is more limited in scope. In our opinion, this a good thing.

If you are in a hurry to get started, the most basic use of selfy is to create a new ed25519 key-pair:

$ selfy -k name_of_your_identity -e your@email.here.org

With other tools you would need to already have a CA (Certificate Authority).

But we've got your back. If you lack a CA in the default directory (see -p below), then, for your convenience, a self-signed CA will be auto-generated for you. It will then be used to sign the new cert. Hence the above command is all that a typical developer needs to get started. Yay(!)

If you want to first create a CA manually, you can do that with the -ca flag. The -p flag will let you put it somewhere other than the default directory.

selfy -ca # make a new self-signed Certificate Authority

Your newly created cert can be viewed with the selfy -v flag, just give it the path to your .crt file.

$ selfy -v certs/name_of_your_identity.crt

By default, the CA is stored in the ./my-keep-private-dir/ca.{crt,key}, while the -k named identifying cert is stored in certs/name.crt. The corresponding private key is stored in certs/name.key.

Update: we have added pass-phrase protection to the private keys by default. In order to forgo this protection and use the original behavior, supply the selfy --nopass flag.

$ selfy -h

Usage of selfy:
  -ca
    	create a new self-signed certificate authority (1st of 2 
		steps in making new certs). Written to the -p directory 
		(see selfy -p flag, where -p is for private).
		The CA uses ed25519 keys too.
		
  -e string
    	email to write into the certificate (who to contact 
		about this job) (strongly encouraged when making 
		new certs! defaults to name@host if not given)
		
  -k string
    	2nd of 2 steps: -k {key_name} ; create a new ed25519 
		key pair (key and cert), and save it to this name. 
		The pair will be saved under the -o directory; 
		we strongly suggest you also use the 
		-e your_email@actually.org flag to 
		describe the job and/or provide the owner's email 
		to contact when needed. A CA will be auto-generated 
		if none is found in the -p directory, which has 
		a default name which warns the user to protect it.

  -nopass
    	by default we request a password and use 
		it with Argon2id to encrypt private key files (CA & identity). 
		Setting -nopass means we generate an un-encrypted 
		private key; this is not recommended.

  -o string
    	directory to save newly created certs into. (default "certs")
		
  -p string
    	directory to find the CA in. If doing -ca, we will save 
		the newly created Certificate Authority (CA) private 
		key to this directory. If doing -k to create a new key, 
		we'll look for the CA here. (default "my-keep-private-dir")  
		
  -v string
    	path to cert to view. Similar output as the openssl 
		command: 'openssl x509 -in certs/client.crt  -text -noout', 
		which you could use instead; just replace certs/client.crt 
		with the path to your cert.

The openssl commands in the included gen.sh script do the same things as selfy does, but it is more painful to incorporate an email because you have to modify the openssl-san.cnf file to do so each time.

generating keys with emails inside

See the selfy -e flag above for details.

Delightfully, email addresses can be stored in certificates!

This provides a fantastically convenient way to identify the job and/or the owner of the job. Importantly, if you are supporting work loads from many people, this can be critical in telling who needs to know when something goes wrong with a job.

A localhost by any other name would not smell as sweet

The server name will always be 'localhost' on selfy generated certificates, and this is critical to allowing our processes to run on any machine. By doing so, we leverage the SNI technology[1], to break the troublesome adhesion of IP address and certificate. SNI was developed to let a single IP address host many different web sites. We want multiple IP addresses to run many different jobs, and to be able to migrate jobs between hosts. Brilliantly, SNI lets us move our clients and servers between machines without having to re-issue the certificates. The certs are always issued to 'localhost' and clients always request ServerName 'localhost' in their tls.Config.ServerName field.

This is much more like the convenience and usability ssh. To me ssh has always been so much easier to deal with than certificates. rpc25519 aims for usability on par with ssh. Leveraging SNI is one way we get there.

[1] https://en.wikipedia.org/wiki/Server_Name_Indication ,


Author: Jason E. Aten, Ph.D.

License: See the LICENSE file for the 3-clause BSD-style license; the same as Go.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrDone = fmt.Errorf("done channel closed")
View Source
var ErrHandshakeQUIC = fmt.Errorf("quic handshake failure")
View Source
var ErrNetConnectionNotFound = fmt.Errorf("error: net.Conn not found")
View Source
var ErrNetRpcShutdown = errors.New("connection is shut down")

ErrNetRpcShutdown is from net/rpc, and still distinct from ErrShutdown to help locate when and where the error was generated. It indicates the system, or at least the network connection or stream, is closed or shutting down.

View Source
var ErrNotFound = fmt.Errorf("known_tls_hosts file not found")
View Source
var ErrShutdown = fmt.Errorf("shutting down")
View Source
var ErrTooLong = fmt.Errorf("message message too long:  over 2GB; encrypted client vs an un-encrypted server?")

Functions

func SelfyNewKey added in v1.0.3

func SelfyNewKey(createKeyPairNamed, odir string) error

SelfyNewKey will generate a self-signed certificate authority, a new ed25519 key pair, sign the public key to create a cert, and write these four new files to disk. The directories odir/my-keep-private-dir and odir/certs will be created, based on the odir argument. For a given createKeyPairNamed name, we will create odir/certs/name.crt and odir/certs/name.key files. The odir/certs/name.key and my-keep-private-dir/ca.key files contain private keys and should be kept confidential. The `selfy` command in this package can be used to produce the same keys but with password protection, which is recommended.

Types

type Call added in v1.0.42

type Call struct {
	ServiceMethod string     // The name of the service and method to call.
	Args          any        // The argument to the function (*struct).
	Reply         any        // The reply from the function (*struct).
	Error         error      // After completion, the error status.
	Done          chan *Call // Receives *Call when Go is complete.
}

Call represents an active net/rpc RPC.

type Client

type Client struct {
	// contains filtered or unexported fields
}

A Client starts requests, and (might) wait for responses.

func NewClient

func NewClient(name string, config *Config) (c *Client, err error)

NewClient attemps to connect to config.ClientDialToHostPort; err will come back with any problems encountered. The name setting allows users to track multiple instances of Clients, and the Client.Name() method will retreive it.

func (*Client) Call added in v1.0.42

func (c *Client) Call(serviceMethod string, args any, reply any) error

Call implements the net/rpc Client.Call() API; its docs:

Call invokes the named function, waits for it to complete, and returns its error status.

func (*Client) Close

func (c *Client) Close() error

Close shuts down the Client.

func (*Client) Err

func (c *Client) Err() error

Err returns any Client stored error.

func (*Client) GetOneRead

func (c *Client) GetOneRead(seqno uint64, ch chan *Message)

GetOneRead responds on ch with the first incoming message whose Seqno matches seqno, then auto unregisters itself after that single send on ch.

func (*Client) GetReadIncomingCh

func (c *Client) GetReadIncomingCh() (ch chan *Message)

GetReadIncomingCh creates and returns a buffered channel that reads incoming messages that are server-pushed (not associated with a round-trip rpc call request/response pair).

func (*Client) GetReads

func (c *Client) GetReads(ch chan *Message)

GetReads registers to get any received messages on ch. It is similar to GetReadIncomingCh but for when ch already exists and you do not want a new one.

func (*Client) Go added in v1.0.42

func (c *Client) Go(serviceMethod string, args any, reply any, done chan *Call) *Call

Go implements the net/rpc Client.Go() API; its docs:

Go invokes the function asynchronously. It returns the Call structure representing the invocation. The done channel will signal when the call is complete by returning the same Call object. If done is nil, Go will allocate a new channel. If non-nil, done must be buffered or Go will deliberately crash.

func (*Client) LocalAddr

func (c *Client) LocalAddr() string

LocalAddr retreives the local host/port that the Client is calling from.

func (*Client) Name added in v1.0.39

func (c *Client) Name() string

Name reports the name the Client was created with.

func (*Client) OneWaySend

func (c *Client) OneWaySend(msg *Message, doneCh <-chan struct{}) (err error)

OneWaySend sends a message without expecting or waiting for a response. The doneCh is optional, and can be nil.

func (*Client) SendAndGetReply

func (c *Client) SendAndGetReply(req *Message, doneCh <-chan struct{}) (reply *Message, err error)

SendAndGetReply starts a round-trip RPC call. We will wait for a response before retuning. The doneCh is optional; it can be nil. A context.Done() like channel can be supplied there to stop waiting before a reply comes back.

func (*Client) SendAndGetReplyWithTimeout

func (c *Client) SendAndGetReplyWithTimeout(timeout time.Duration, req *Message) (reply *Message, err error)

SendAndGetReplyWithTimeout expires the call after timeout.

func (*Client) UngetReads

func (c *Client) UngetReads(ch chan *Message)

UngetReads reverses what GetReads does: un-register and have ch be deaf from now on. Idempotent: if ch is already gone, no foul is reported.

type ClientCodec added in v1.0.42

type ClientCodec interface {
	WriteRequest(*Request, any) error
	ReadResponseHeader(*Response) error
	ReadResponseBody(any) error

	Close() error
}

ClientCodec is part of the net/rpc API. Its docs:

A ClientCodec implements writing of RPC requests and reading of RPC responses for the client side of an RPC session. The client calls [ClientCodec.WriteRequest] to write a request to the connection and calls [ClientCodec.ReadResponseHeader] and [ClientCodec.ReadResponseBody] in pairs to read responses. The client calls [ClientCodec.Close] when finished with the connection. ReadResponseBody may be called with a nil argument to force the body of the response to be read and then discarded. See NewClient's comment for information about concurrent access.

type Config

type Config struct {

	// ServerAddr host:port where the server should listen.
	ServerAddr string

	// optional. Can be used to suggest that the
	// client use a specific host:port. NB: For QUIC, by default, the client and
	// server will share the same port if they are in the same process.
	// In that case this setting will definitely be ignored.
	ClientHostPort string

	// Who the client should contact
	ClientDialToHostPort string

	// TCP false means TLS-1.3 secured. true here means do TCP only; with no encryption.
	TCPonly_no_TLS bool

	// UseQUIC cannot be true if TCPonly_no_TLS is true.
	UseQUIC bool

	// path to certs/ like certificate
	// directory on the live filesystem. If left
	// empty then the embedded certs/ from build-time, those
	// copied from the on-disk certs/ directory and baked
	// into the executable as a virtual file system with
	// the go:embed directive are used.
	CertPath string

	// SkipVerifyKeys true allows any incoming
	// key to be signed by
	// any CA; it does not have to be ours. Obviously
	// this discards almost all access control; it
	// should rarely be used unless communication
	// with the any random agent/hacker/public person
	// is desired.
	SkipVerifyKeys bool

	ClientKeyPairName string // default "client" means use certs/client.crt and certs/client.key
	ServerKeyPairName string // default "node" means use certs/node.crt and certs/node.key

	// PreSharedKeyPath locates an optional pre-shared
	// hex written in hex that must be 32 bytes (or more) long
	// (so 64 hex characters). Only the first 32 bytes will
	// be used to create a symmetric 2nd encryption layer.
	PreSharedKeyPath string

	// These are timeouts for connection and transport tuning.
	// The defaults of 0 mean wait forever.
	ConnectTimeout time.Duration
	ReadTimeout    time.Duration
	WriteTimeout   time.Duration
	// contains filtered or unexported fields
}

Config is the same struct type for both NewClient and NewServer setup.

Config says who to contact (for a client), or where to listen (for a server and/or client); and sets how strong a security posture we adopt.

Copying a Config is fine, but it should be a simple shallow copy to preserve the shared *sharedTransport struct. See/use the Config.Clone() method if in doubt.

nitty gritty details/dev note: the `shared` pointer here is the basis of port (and file handle) reuse where a single process can maintain a server and multiple clients in a "star" pattern. This only works with QUIC of course, and is one of the main reasons to use QUIC.

The shared pointer is reference counted and the underlying net.UDPConn is only closed when the last instance in use is Close()-ed.

func NewConfig added in v1.0.14

func NewConfig() *Config

NewConfig should be used to create Config for use in NewClient or NewServer setup.

func (*Config) Clone added in v1.0.72

func (cfg *Config) Clone() *Config

Clone returns a copy of cfg. This is a shallow copy to enable shared transport between a QUIC client and a QUIC server on the same port.

type HDR added in v1.0.42

type HDR struct {
	Nc net.Conn `msg:"-"` // registered func can query caller details.

	Created  string `zid:"0"`  // HDR creation time stamp.
	From     string `zid:"1"`  // originator host:port address.
	To       string `zid:"2"`  // destination host:port address.
	Subject  string `zid:"3"`  // in net/rpc, the "Service.Method" ServiceName
	IsRPC    bool   `zid:"4"`  // in rpc25519 Message API, is this a TwoWayFunc call?
	IsLeg2   bool   `zid:"5"`  // in rpc25519 Message API, is TwoWayFunc reply?
	Serial   int64  `zid:"6"`  // serially incremented tracking number
	CallID   string `zid:"7"`  // 40-byte crypto/rand base-58 coded string (same on response).
	PID      int64  `zid:"8"`  // Process ID of originator.
	Seqno    uint64 `zid:"9"`  // client set sequence number for each call (same on response).
	IsNetRPC bool   `zid:"10"` // is net/rpc API in use for this request/response?
}

HDR provides header information and details about the transport. It is the first thing in every Message. It is public so that clients can understand the context of their calls. Traditional `net/rpc` API users can use the `ctx context.Context` first argument form of callback methods and get an *HDR with ctx.Value("HDR") as in the README.md introduction. Reproduced here:

func (s *Service) GetsContext(ctx context.Context, args *Args, reply *Reply) error {
  if hdr := ctx.Value("HDR"); hdr != nil {
     h, ok := hdr.(*rpc25519.HDR)
     if ok {
       fmt.Printf("GetsContext called with HDR = '%v'; "+
          "HDR.Nc.RemoteAddr() gives '%v'; HDR.Nc.LocalAddr() gives '%v'\n",
          h.String(), h.Nc.RemoteAddr(), h.Nc.LocalAddr())
     }
  } else {
     fmt.Println("HDR not found")
  }
}

func HDRFromBytes added in v1.0.42

func HDRFromBytes(jsonData []byte) (*HDR, error)

func HDRFromGreenpack added in v1.0.42

func HDRFromGreenpack(header []byte) (*HDR, error)

HDRFromGreenpack will unmarshal the header into the returned struct. The [greenpack format](https://github.com/glycerine/greenpack) is expected.

func HDRFromOpaqueURLFriendly added in v1.0.42

func HDRFromOpaqueURLFriendly(s string) (*HDR, error)

func NewHDR added in v1.0.42

func NewHDR(from, to, subject string, isRPC bool, isLeg2 bool) (m *HDR)

NewHDR creates a new HDR header.

func Unbytes

func Unbytes(jsonData []byte) *HDR

Unbytes reverses Bytes.

func (*HDR) AsGreenpack added in v1.0.42

func (hdr *HDR) AsGreenpack(scratch []byte) (o []byte, err error)

AsGreenpack will marshall hdr into the o output bytes. The scratch bytes can be nil or reused and returned to avoid allocation. The [greenpack format](https://github.com/glycerine/greenpack) is used.

func (*HDR) Bytes added in v1.0.42

func (m *HDR) Bytes() []byte

Bytes serializes to compact JSON formatted bytes.

func (*HDR) Compact added in v1.0.42

func (m *HDR) Compact() string

Compact is all on one line.

func (*HDR) DecodeMsg added in v1.0.42

func (z *HDR) DecodeMsg(dc *msgp.Reader) (err error)

DecodeMsg implements msgp.Decodable We treat empty fields as if we read a Nil from the wire.

func (*HDR) EncodeMsg added in v1.0.42

func (z *HDR) EncodeMsg(en *msgp.Writer) (err error)

EncodeMsg implements msgp.Encodable

func (*HDR) Equal added in v1.0.42

func (a *HDR) Equal(b *HDR) bool

Equal compares two *HDR structs field by field for structural equality

func (*HDR) JSON added in v1.0.42

func (m *HDR) JSON() []byte

JSON serializes to JSON.

func (*HDR) MarshalMsg added in v1.0.42

func (z *HDR) MarshalMsg(b []byte) (o []byte, err error)

MarshalMsg implements msgp.Marshaler

func (*HDR) Msgsize added in v1.0.42

func (z *HDR) Msgsize() (s int)

Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message

func (*HDR) OpaqueURLFriendly added in v1.0.42

func (m *HDR) OpaqueURLFriendly() string

func (*HDR) Pretty added in v1.0.42

func (m *HDR) Pretty() string

Pretty shows in pretty-printed JSON format.

func (*HDR) String added in v1.0.42

func (m *HDR) String() string

func (*HDR) UnmarshalMsg added in v1.0.42

func (z *HDR) UnmarshalMsg(bts []byte) (o []byte, err error)

UnmarshalMsg implements msgp.Unmarshaler

func (*HDR) UnmarshalMsgWithCfg added in v1.0.42

func (z *HDR) UnmarshalMsgWithCfg(bts []byte, cfg *msgp.RuntimeConfig) (o []byte, err error)

type Message

type Message struct {

	// HDR contains header information.
	HDR HDR `zid:"0"`

	// JobSerz is the "body" of the message.
	// The user provides and interprets this.
	JobSerz []byte `zid:"1"`

	// JobErrs returns error information from the server-registered
	// user-defined callback functions.
	JobErrs string `zid:"2"`

	// Err is not serialized on the wire by the server.
	// It communicates only local (client side) information. Callback
	// functions should convey errors in JobErrs or in-band within
	// JobSerz.
	Err error `msg:"-"`

	// DoneCh will receive this Message itself when the call completes.
	// It must be buffered, with at least capacity 1.
	// NewMessage() automatically allocates DoneCh correctly and
	// so should be the preferred for creating Message(s).
	DoneCh chan *Message `msg:"-"`
}

Message transports JobSerz []byte slices for the user, who can de-serialize them they wish. The HDR header field provides transport details.

func MessageFromGreenpack added in v1.0.28

func MessageFromGreenpack(by []byte) (*Message, error)

MessageFromGreenpack unmarshals the by slice into a Message and returns it. The [greenpack format](https://github.com/glycerine/greenpack) is expected.

func NewMessage

func NewMessage() *Message

NewMessage allocates a new Message with a DoneCh properly created (buffered 1).

func NewMessageFromBytes

func NewMessageFromBytes(by []byte) (msg *Message)

NewMessageFromBytes calls NewMessage() and sets by as the JobSerz field.

func (*Message) AsGreenpack added in v1.0.29

func (m *Message) AsGreenpack(scratch []byte) (o []byte, err error)

AsGreenpack marshalls m into o. The scrach workspace can be nil or reused to avoid allocation. The [greenpack format](https://github.com/glycerine/greenpack) is used.

func (*Message) DecodeMsg added in v1.0.26

func (z *Message) DecodeMsg(dc *msgp.Reader) (err error)

DecodeMsg implements msgp.Decodable We treat empty fields as if we read a Nil from the wire.

func (*Message) EncodeMsg added in v1.0.26

func (z *Message) EncodeMsg(en *msgp.Writer) (err error)

EncodeMsg implements msgp.Encodable

func (*Message) MarshalMsg added in v1.0.26

func (z *Message) MarshalMsg(b []byte) (o []byte, err error)

MarshalMsg implements msgp.Marshaler

func (*Message) Msgsize added in v1.0.26

func (z *Message) Msgsize() (s int)

Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message

func (*Message) String

func (msg *Message) String() string

String returns a string representation of msg.

func (*Message) UnmarshalMsg added in v1.0.26

func (z *Message) UnmarshalMsg(bts []byte) (o []byte, err error)

UnmarshalMsg implements msgp.Unmarshaler

func (*Message) UnmarshalMsgWithCfg added in v1.0.26

func (z *Message) UnmarshalMsgWithCfg(bts []byte, cfg *msgp.RuntimeConfig) (o []byte, err error)

type NetConnWrapper added in v1.0.5

type NetConnWrapper struct {
	quic.Stream
	quic.Connection
}

NetConnWrapper is exported so that clients like `goq` and others that want to inspect that context of their calls can do so.

type OneWayFunc added in v1.0.39

type OneWayFunc func(req *Message)

OneWayFunc is the simpler sibling to the above. A OneWayFunc will not return anything to the sender.

As above req.JobSerz [] byte contains the job payload.

type Request added in v1.0.42

type Request struct {
	ServiceMethod string // format: "Service.Method"
	Seq           uint64 // sequence number chosen by client
	// contains filtered or unexported fields
}

Request is part of the net/rpc API. Its docs:

Request is a header written before every RPC call. It is used internally but documented here as an aid to debugging, such as when analyzing network traffic.

type Response added in v1.0.42

type Response struct {
	ServiceMethod string // echoes that of the Request
	Seq           uint64 // echoes that of the request
	Error         string // error, if any.
	// contains filtered or unexported fields
}

Response is part of the net/rpc API. Its docs:

Response is a header written before every RPC return. It is used internally but documented here as an aid to debugging, such as when analyzing network traffic.

type Server

type Server struct {

	// RemoteConnectedCh sends the remote host:port address
	// when the server gets a new client,
	// See srv_test.go Test004_server_push for example,
	// where it is used to avoid a race/panic.
	RemoteConnectedCh chan string
	// contains filtered or unexported fields
}

Servers read and respond to requests. Two APIs are available.

Using the rpc25519.Message based API:

Register1Func() and Register2Func() register callbacks.

Using the net/rpc API:

Server.Register() registers structs with callback methods on them.

The net/rpc API is implemented as a layer on top of the rpc25519.Message based API. Both can be used concurrently if desired.

func NewServer

func NewServer(name string, config *Config) *Server

NewServer will keep its own copy of config. If config is nil, the server will make its own upon Start().

func (*Server) Close

func (s *Server) Close() error

Close asks the Server to shut down.

func (*Server) Register added in v1.0.42

func (s *Server) Register(rcvr any) error

Register implements the net/rpc Server.Register() API. Its docs:

Register publishes in the server the set of methods of the receiver value that satisfy the following conditions:

  • exported method of exported type
  • two arguments, both of exported type
  • the second argument is a pointer
  • one return value, of type error

It returns an error if the receiver is not an exported type or has no suitable methods. It also logs the error using package log. The client accesses each method using a string of the form "Type.Method", where Type is the receiver's concrete type.

rpc25519 addendum:

Callback methods in the `net/rpc` style traditionally look like this first `NoContext` example below. We now allow a context.Context as an additional first parameter. The ctx will have an "HDR" value set on it giving a pointer to the `rpc25519.HDR` header from the incoming Message.

func (s *Service) NoContext(args *Args, reply *Reply) error

* new:

func (s *Service) GetsContext(ctx context.Context, args *Args, reply *Reply) error {
   if hdr := ctx.Value("HDR"); hdr != nil {
      h, ok := hdr.(*rpc25519.HDR)
      if ok {
        fmt.Printf("GetsContext called with HDR = '%v'; "+
           "HDR.Nc.RemoteAddr() gives '%v'; HDR.Nc.LocalAddr() gives '%v'\n",
           h.String(), h.Nc.RemoteAddr(), h.Nc.LocalAddr())
      }
   } else {
      fmt.Println("HDR not found")
   }
}

func (*Server) Register1Func added in v1.0.39

func (s *Server) Register1Func(callme1 OneWayFunc)

Register1Func tells the server about a func or method that will not reply. See the OneWayFunc definition.

func (*Server) Register2Func added in v1.0.39

func (s *Server) Register2Func(callme2 TwoWayFunc)

Register2Func tells the server about a func or method that will have a returned Message value. See the TwoWayFunc definition.

func (*Server) RegisterName added in v1.0.42

func (s *Server) RegisterName(name string, rcvr any) error

RegisterName is like [Register] but uses the provided name for the type instead of the receiver's concrete type.

func (*Server) SendMessage

func (s *Server) SendMessage(callID, subject, destAddr string, data []byte, seqno uint64) error

SendMessage can be used on the server to push data to one of the connected clients; that found at destAdddr.

A NewMessage() Message will be created and JobSerz will contain the data. The HDR fields Subject, CallID, and Seqno will also be set from the arguments.

If the destAddr is not already connected to the server, the ErrNetConnectionNotFound error will be returned.

func (*Server) Start

func (s *Server) Start() (serverAddr net.Addr, err error)

Start has the Server begin receiving and processing RPC calls. The Config.ServerAddr tells us what host:port to bind and listen on.

type ServerCodec added in v1.0.42

type ServerCodec interface {
	ReadRequestHeader(*Request) error
	ReadRequestBody(any) error
	WriteResponse(*Response, any) error

	// Close can be called multiple times and must be idempotent.
	Close() error
}

ServerCodec is part of the net/rpc API. Its docs:

A ServerCodec implements reading of RPC requests and writing of RPC responses for the server side of an RPC session. The server calls [ServerCodec.ReadRequestHeader] and [ServerCodec.ReadRequestBody] in pairs to read requests from the connection, and it calls [ServerCodec.WriteResponse] to write a response back. The server calls [ServerCodec.Close] when finished with the connection. ReadRequestBody may be called with a nil argument to force the body of the request to be read and discarded. See NewClient's comment for information about concurrent access.

type ServerError added in v1.0.42

type ServerError string

ServerError represents an error that has been returned from the remote side of the RPC connection.

func (ServerError) Error added in v1.0.42

func (e ServerError) Error() string

type TwoWayFunc added in v1.0.39

type TwoWayFunc func(req *Message, reply *Message) error

TwoWayFunc is the user's own function that they register with the server for remote procedure calls.

The user's Func may not want to return anything. In that case they should register a OneWayFunc instead.

req.JobSerz []byte contains the job payload.

Implementers of TwoWayFunc should assign their return []byte to reply.JobSerz. reply.Jobserz can also be left nil, of course.

Any errors can be returned on reply.JobErrs; this is optional. Note that JobErrs is a string value rather than an error.

The system will overwrite the reply.HDR field when sending the reply, so the user should not bother trying to alter it.

Directories

Path Synopsis
cmd
cli
srv

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL