ifrit

package module
v0.0.0-...-7e24c04 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2021 License: MIT Imports: 7 Imported by: 5

README

Ifrit

Go library implementing the Fireflies protocol, https://dl.acm.org/citation.cfm?id=2701418. Ifrit provides a membership, message, signature, and gossip service. Applications have a full membership view and can send messages directly to their destination.

GoDoc

Starting a Ifrit network

To start an Ifrit network, you need to deploy the Ifrit certificate authority, responsible for signing certificates and acting as an entry point into the network.

The certificate authority serves a single HTTP endpoint for certificate signing requests, piggybacking certificates of existing participants on responses. To start a network, simply deploy it.

Firstly, import the certificate authority package:

"github.com/joonnna/ifrit/cauth"

Then create and start a CA instance:

ca, err := cauth.NewCa()
if err != nil {
    panic(err)
}

go ca.Start()

Furthermore, you can customize parameters by changing the generated config file, located at /var/tmp/ca_config. It contains three parameters: boot_nodes, num_rings, and port.

  • boot_nodes: how many participant certificates the certificate authority stores locally (default: 10).
  • num_rings: how many Ifrit rings will be used by participants, embedded in all certificates (default: 10).
  • port: which port to bind to (default: 8300).
Starting a client

Firstly you'll have to populate the client config with the address (ip:port) of the certificate authority. Create the file "ifrit_config.yml", and place it under the /var/tmp folder. Fill in the following variables:

  • use_ca: true
  • ca_addr: "insert ca address here"

Now you'll want to import the library:

"github.com/joonnna/ifrit"

Now you can create and start a client

c, err := ifrit.NewClient(udpPort, tcpPort)
if err != nil {
    panic(err)
}

go c.Start()

After participating in the network for some time you will learn of all other participants in the network, you can retreive their addresses as follows:

allNetworkMembers := c.Members()
Sending a message

After joining an Ifrit network you can send messages to anyone in it:

members := client.Members()

randomMember := members[rand.Int() % len(members)]

ch := client.SendTo(randomMember, msg)

response := <-ch

The response will eventually be propagated through the returned channel.

To receive messages, you can register a message handler:

client.RegisterMsgHandler(yourMessageHandler)

// This callback will be invoked on each received message.
func yourMessageHandler(data []byte) ([]byte, error) {
    // Do message logic
    return yourResponse, yourError
}

The response, or error if its non-nil, will be propagated back to the sender.

Adding gossip

You can also gossip with neighboring peers in the Ifrit ring mesh. All incoming gossip is from neighbors, and all outgoing gossip is only sent to neighbors. To add gossip, you simply attach your message to the client, it will then be sent to a neighboring peer at each gossip interval.

client.SetGossipContent(yourGossipMsg)

To receive incoming gossip messages and responses you register two handlers:

client.RegisterGossipHandler(yourGossipHandler)
client.RegisterResponseHandler(yourResponseHandler)

// This callback will be invoked on each received gossip message.
func yourGossipHandler(data []byte) ([]byte, error) {
    // Do your stuff
    return yourResponse, yourError
}


// This callback will be invoked on each received gossip response.
func yourResponseHandler(data []byte) {
    // Do your stuff
}

Note that gossip messages has seperate message and response handlers than that of normal messages.

Adding streaming

Ifrit supports bi-directional streaming. The sender invokes client.OpenStream() which returns two buffered channels. The first channel is used to send messages to the server and the second channel is used to receive messages from the server. Specify the callback handler on the receiving side - client.RegisterStreamHandler(yourStreamingHandler). The handler uses two unbuffered channels for the server side to use.

func yourStreamingHandler(data []byte) {
    members := client.Members()
    randomMember := members[rand.Int()%len(members)]
    input, reply := client.OpenStream(randomMember, 10, 10)
    // Use the channels
    close(input)
}

func  streamHandler(input, reply chan []byte) {
    // Use the channels
    close(reply)
}

To avoid untimely closing of channels, the application should implement a means of acknowledgement before closing any streams.

NOTE: The reply stream at the sending side must not block so that the resources can be released. See the fully-working example of streaming here.

Config details

Ifrit clients relies on a config file which should either be placed in your current working directory or /var/tmp/ifrit_config. Ifrit will generate all default values, but relies on two user inputs as explained earlier. We will now present all configuration variables:

  • use_ca (bool): if a ca should be contacted on startup.
  • ca_addr (string): ip:port of the ca, has to be populated if use_ca is set to true.
  • gossip_interval (uint32): How often (in seconds) the ifrit client should gossip with a neighboring peer (default: 10). Ifrit gossips with one neighbor per interval.
  • monitor_interval (uint32): How often (in seconds) the ifrit client should monitor other peers (default: 10).
  • ping_limit (uint32): How many failed pings before peers are considered dead (default: 3).
  • max_concurrent_messages (uint32): The maximum concurrent outgoing messages through the messaging service at any time (default: 50).
  • removal_timeout (uint32): How long (in seconds) the ifrit client waits after discovering an unresponsive peer before removing it from its live view (default: 60).

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

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

func NewClient

func NewClient(config *Config) (*Client, error)

Creates and returns a new ifrit client instance.

func (*Client) Addr

func (c *Client) Addr() string

Returns the address(ip:port) of the ifrit client. Can be directly used as entry addresses in the config.

func (*Client) Id

func (c *Client) Id() string

Returns ifrit's internal ID generated by the trusted CA

func (*Client) Members

func (c *Client) Members() []string

Returns the address (ip:port, rpc endpoint) of all other ifrit clients in the network which is currently believed to be alive.

func (*Client) OpenStream

func (c *Client) OpenStream(dest string) (chan []byte, chan []byte)

Returns a pair of channels used for bi-directional streams, given the destination. The first channel is the input stream to the server and the second stream is the reply stream from the server. To close the stream, close the input channel. The reply stream is open as long as the server sends messages back to the client. The caller must ensure that the reply stream does not block by draining the buffer so that the stream session can complete. Note: it is adviced to implement an aknowledgement mechanism to avoid an untimely closing of a channel and loss of messages.

func (*Client) RegisterGossipHandler

func (c *Client) RegisterGossipHandler(gossipHandler func([]byte) ([]byte, error))

Registers the given function as the gossip handler. Invoked each time ifrit receives application gossip. The returned byte slice will be sent back as the response. If the callback returns a non-nil error, it will be sent back as the response instead.

func (*Client) RegisterMsgHandler

func (c *Client) RegisterMsgHandler(msgHandler func([]byte) ([]byte, error))

Registers the given function as the message handler. Invoked each time the ifrit client receives an application message (another client sent it through SendTo), this callback will be invoked. The returned byte slice will be sent back as the response. If error is non-nil, it will be returned as the response. All responses will be received on the sending side through a channel, see SendTo documentation for details.

func (*Client) RegisterResponseHandler

func (c *Client) RegisterResponseHandler(responseHandler func([]byte))

Registers the given function as the gossip response handler. Invoked when ifrit receives a response after gossiping application data. All responses originates from a gossip handler invocation. If the ResponseHandler is not registered or nil, responses will be discarded.

func (*Client) RegisterStreamHandler

func (c *Client) RegisterStreamHandler(streamHandler func(chan []byte, chan []byte))

Registers the given function as the stream handler. Invoked when the client opens a stream. The callback accepts two channels - an unbuffered input channel and an unbuffered channel used for replying to the client. The caller must close the reply channel to signal that the stream is closing. See the note in OpenStream().

func (*Client) SaveCertificate

func (c *Client) SaveCertificate() error

func (*Client) SavePrivateKey

func (c *Client) SavePrivateKey() error

func (*Client) SendTo

func (c *Client) SendTo(dest string, data []byte) chan *core.Message

Sends the given data to the given destination. The caller must ensure that the given data is not modified after calling this function. The returned channel will be populated with the response. The data and error values are contained in the *core.Message type. If the destination could not be reached or timeout occurs, nil will be sent through the channel. The response data can be safely modified after receiving it. The message instance is nil if the recipient is unavailable. Ifrit will close the channel after receiving the response message.

func (*Client) SendToId

func (c *Client) SendToId(destId []byte, data []byte) (chan *core.Message, error)

Same as SendTo, but destination is now the Ifrit id of the receiver. Returns an error if no observed peer has the specified destination id.

func (*Client) SetGossipContent

func (c *Client) SetGossipContent(data []byte) error

Replaces the gossip set with the given data. This data will be exchanged with neighbors in each gossip interaction. Recipients will receive it through the message handler callback. The response generated by the message handler callback will be sent back and invoke the response handler callback.

func (*Client) Sign

func (c *Client) Sign(content []byte) ([]byte, []byte, error)

Signs the provided content with the internal private key of ifrit.

func (*Client) Start

func (c *Client) Start()

Client starts operating.

func (*Client) Stop

func (c *Client) Stop()

Stops client operations. The client cannot be used after callling Close.

func (*Client) VerifySignature

func (c *Client) VerifySignature(r, s, content []byte, id string) bool

Checks if the given content is correctly signed by the public key belonging to the given node id. The id represents another node in the Fireflies network, if the id is not recongnized false is returned.

type Config

type Config struct {
	// If set to true, the client will create new cryptographic resources and store it in
	// CryptoUnitPath. Otherwise, it will use the existing resources.
	New bool

	// TCP port of the Ifrit client.
	TCPPort int

	// UDP port of the Ifrit client.
	UDPPort int

	// Hostname used in the X.509 certificate.
	Hostname string

	// The directory of the cryptographic resources.
	CryptoUnitPath string
}

Directories

Path Synopsis
cmd
ca

Jump to

Keyboard shortcuts

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