Note on documentation: The programming API has changed and some details about the installation procedure may have changed. The current documentation is still useful for getting a sense of what Marinda is, but it's not yet a reliable source of information about using Marinda until it can be updated.

Overview

How you install Marinda depends on how you intend to use it. For the purposes of installation and configuration, the most important question is, "On how many different machines do I want to run clients that need to communicate with each other?" This question is important because the Marinda system architecture consists of and distinguishes between global and local tuple spaces.

  • If all your clients will run on a single machine (that is, if all communication will happen within a single machine), then you can simply install Marinda (the local tuple space) on that one host, and you’re done.

  • If your clients will run on multiple machines (that is, if you need to support communication across machines), then you need to install Marinda on each client machine (for the local tuple space) and on a server machine (for the global tuple space). You don’t have to dedicate a separate machine to run the global tuple space—you can simply reuse one of the machines hosting clients, if you wish.

The global and local tuple spaces are provided by the global and local Marinda servers, respectively. Marinda is distributed as a RubyGem. The same gem is used to install both the global and local servers. Because all clients must run on a machine that has a running local server, this same gem also provides the Ruby client binding.

Marinda is known to work on various Unix systems (MacOS X, Linux, FreeBSD), but it will not work on systems like Windows that lack Unix domain sockets. Marinda works with Ruby 1.8.6, 1.8.7, and 1.9.x. It will not work with JRuby (for lack of support for C extensions and Unix domain sockets).

Warning
Some Ruby 1.8.x releases suffer from memory leaks. If process size becomes a problem and you can’t use Ruby 1.9.x, then you might try running Ruby 1.8.7 with Brent Roman’s MBARI patches.

Required Packages

All installations of Marinda (for the global and local tuple-space servers and for the client) need the

  • openssl module in the Ruby standard library

  • Eva RubyGem, and

  • (optional but strongly recommended) Judy 1.0.5 and the RJudy RubyGem.

The global tuple-space server installation further needs the

OpenSSL

Most Ruby deployments should have this already installed, but some may not (such as Debian-based Linux distributions), so it’s worth checking in the following way:

$ ruby -ropenssl -e 'puts "ok"'

You should see ok if openssl is installed. Otherwise, you’ll see

$ ruby -ropenssl -e 'puts "ok"'
ruby: no such file to load -- openssl (LoadError)

Eva

Eva provides an event-based I/O framework using libev. Install Eva in the standard way for RubyGems:

$ sudo gem install eva-0.4.1.gem

If you get an error saying that gem is not a command, then you need to install RubyGems.

Judy 1.0.5 and RJudy

Judy is an implementation of fast and efficient sparse dynamic arrays. Marinda can work without Judy, but it will be considerably faster with Judy (10x faster), so using Judy is strongly recommended.

Judy is available in MacPorts as judy, in FreeBSD ports as devel/judy, and on Debian-based Linux (Ubuntu) as libjudy-dev.

RJudy is a Ruby binding to Judy. RJudy was originally written by Lyle Johnson in 2002, but it has since become unmaintained. RJudy was mostly re-written in 2010 by Young Hyun at CAIDA, and it is this version that you should install (this updated version will be renamed in the future, but for the moment, it continues to be called RJudy). Be careful not to install the FreeBSD ruby-rjudy ports package, which is the old version.

Install the Judy header and library first and then the RJudy Ruby binding. If the Judy header is installed in /usr/include, /usr/local/include, or /opt/local/include (for MacPorts) and the library is installed in the corresponding lib directory (e.g., /usr/lib), then you can install RJudy with

$ sudo gem install rjudy-1.0.2.gem

Otherwise, you’ll need to specify the paths to Judy; for example,

$ sudo gem install rjudy-1.0.2.gem -- \
  --with-judy-include=/usr/local/pkg/judy-1.0.5/include \
  --with-judy-lib=/usr/local/pkg/judy-1.0.5/lib

SQLite and Amalgalite

For global server installations (only), Marinda needs the SQLite database and the Amalgalite Ruby binding to SQLite. The Marinda global server uses SQLite to checkpoint its state in controlled shutdown/restart scenarios, such as to upgrade Marinda, patch the operating system, or perform other scheduled system maintenance.

Note
Be sure to use SQLite v3.x, not the older v2 series.

SQLite3 is available in MacPorts as sqlite3, in FreeBSD ports as databases/sqlite3, and on Debian-based Linux (Ubuntu) as libsqlite3-dev.

Once SQLite is installed, you can install Amalgalite through RubyGems. The following command will automatically fetch the latest gem from the Ruby community’s RubyGems repository and install it (this will also install the arrayfields prerequisite of Amalgalite):

$ sudo gem install amalgalite
Note
You cannot substitute the sqlite3-ruby Ruby binding for Amalgalite.

Installation

Marinda is packaged as a single RubyGem, and the installation process is the same for servers and clients. Simply execute the following commands on each machine:

$ tar xvzf marinda-0.13.3.tar.gz
$ cd marinda-0.13.3
$ sudo gem install marinda-0.13.3.gem
Note

If you’re upgrading Marinda, then be sure to first uninstall any previous version with

$ sudo gem uninstall marinda

You can check whether an older Marinda is installed with

$ gem list

This installs the Marinda servers (local and global) and the Ruby client binding. Marinda servers are implemented largely as Ruby libraries, which is why they are installed as a RubyGem and why it is harmless to install the global server on a client-only machine. To actually start up Marinda, you need to execute the marinda-ls and/or marinda-gs scripts included in the Marinda distribution package (see below for more details on configuration and execution). These frontend Ruby scripts call into the server libraries for the actual implementation.

Configuration

As mentioned in the overview, there are global and local tuple-space servers in Marinda. The global server is optional and only needed if your clients need to communicate from multiple machines. The following sections describe how to configure the global and local servers for several deployment scenarios.

Tip
If you want to start playing with Marinda as quickly as possible, then skip ahead to the Starting Servers section, since the Marinda distribution package comes pre-configured for the local tuple space only mode of use.

You configure the global and local servers with the configuration files global-config.yaml and local-config.yaml. These are written in the YAML structured text format, which is easy to read and write because of its human-oriented design. You do not need to understand much of YAML to read or write Marinda configuration files, since only basic YAML elements are used. You can find samples of these files in the marinda-0.13.3 directory created from the Marinda distribution package. You can directly execute Marinda from the marinda-0.13.3 directory using these configuration files, or if you like, you can move the configuration files to some other location and execute Marinda from there (we cover these topics later in Starting Servers).

Please read through Scenario 1 below, even if a different scenario applies to you, because Scenario 1 discusses some common elements.

Scenario 1: Local tuple space only

Choose this configuration if all your clients will run on a single machine. You only need to configure and start the local tuple-space server.

Tip
The Marinda distribution contains a sample local-config.yaml with this configuration, so there’s nothing more you need to do.
local-config.yaml
---
socket: /tmp/localts.sock
node_id: 1
localspace_only: true
use_judy: true

The first line consisting entirely of three dashes (---) is a requirement of YAML—it marks the beginning of a document. The socket line specifies the rendezvous path for a Unix domain socket. Local Marinda clients will open this socket to connect to the local tuple space. The socket does not have to be in /tmp, but it needs to be in a location that is accessible to all clients. (You should keep the path relatively short, since many operating systems impose a restrictive limit, around 100 characters.)

Note

Access control to the local tuple space is determined solely by the standard Unix permissions set on this socket file. By default, the socket is owned by the user who starts the local server and has permissions like rwxr-xr-x (depending on your umask). If you need to grant access to other users, then one approach is to create a common group for these users and open up the group permissions to rwxrwx---. A simpler but less secure way of granting access to other users is to set the permissions to rwxrwxrwx. For example, you can set the permissions to rwxrwx--- with the socket_access option, which accepts the permissions as an octal value (the leading zero is required):

---
socket: /tmp/localts.sock
socket_access: 0770
node_id: 1
localspace_only: true
use_judy: true

The Unix permissions of this socket also determine access to the global tuple space, since the local server acts as a proxy for the global tuple space.

The node_id line specifies an arbitrary numeric node ID chosen by you for this client machine. The node ID must be in the range 1 to 215, inclusive (1, 2, … , 32768). Each client machine that connects to a given global tuple-space server must have a unique node ID.

You can specify the optional name of the node with a node_name line; for example: node_name: nibbler. Node names should be strings of letters, numbers, dashes, or underscores. Client programs can query Marinda for the local node’s name and use this information for various purposes, such as for dynamic configuration.

The use_judy line enables use of Judy. If you do not have Judy installed, then set this option to false or comment out the line by preceding it with a # character (#use_judy: true).

Scenario 2: Global and local tuple spaces with basic security

Choose this configuration if your clients will run on multiple machines. In this configuration, communication will be unencrypted and unauthenticated between the global server and the local servers running on client machines. This opens up the possibility of eavesdropping and man-in-the-middle attacks. However, the global server will only accept connections from the client IP addresses configured in global-config.yaml. This provides the same protection as a basic firewall, so having unencrypted and unauthenticated communication is not as bad as it seems.

If all Marinda traffic will stay within your organization’s network, then this level of security is probably good enough. However, if Marinda nodes need to communicate over the global Internet, then you may wish to look at Scenario 3 for stronger authentication, privacy, and traffic integrity. Regardless of whether you choose Scenario 2 or 3, you should look over this section to learn about details common to both.

Tip
The Marinda distribution contains a sample global-config.yaml with this configuration as a starting point (it is also available as global-config-nossl.yaml). Simply edit it as needed.
global-config.yaml
---
server_port: 8742
use_judy: true
nodes:
  10.0.2.3: 1
  10.0.2.4: 2
  10.0.2.5: 3

This configures the global server to listen on TCP port 8742 (which is not reserved for any application—see IANA port assignments) for incoming connections from the local tuple-space servers running on remote client machines.

The nodes config lines specify the allowed client IP addresses and their corresponding node IDs. In the above example, node 1 has IP address 10.0.2.3, node 2 has IP address 10.0.2.4, and so on.

Note
Be careful to indent all <IP-address>: <node-ID> lines in global-config.yaml with the exact same amount of whitespace (but never use tabs in YAML files) because YAML uses the indentation to determine nesting. Also, be careful that you don’t repeat an IP address, since the YAML parser may not warn you about it (and the global server has no way to detect it). However, the global server will detect and warn you about duplicate node IDs and malformed IP addresses.
local-config.yaml
---
socket: /tmp/localts.sock
node_id: 1
global_server_addr: foo.example.com
global_server_port: 8742
use_judy: true
Tip
The sample local-config-nossl.yaml contains the above config, so you can copy it to local-config.yaml and edit it as needed.

The global_server_addr line specifies the hostname or IP address of the global server, and global_server_port specifies the global server’s listening port (that is, the server_port value in global-config.yaml). The node_id value must be consistent with the nodes mapping in global-config.yaml, or the global server will reject a connection attempt.

Scenario 3: Global and local tuple spaces with SSL

Choose this configuration if your clients will run on multiple machines and you wish to have strong guarantees of authenticity, privacy, and traffic integrity. In this configuration, communication will be encrypted with SSL and authenticated with certificates. Note that this is protecting the communication between the global server and the local servers running on client machines. It is always the local servers that open the connection to the global server, and thus, in the terminology of SSL, the global server is the server and each local server is the client. The local servers verify the server certificate of the global server, and the global server verifies the client certificate of the local servers. This two-way authentication ensures that only authorized nodes are able to connect to the global server.

You will need to create a self-signed Certificate-Authority certificate (a CA cert) and then create the server and client SSL certificates using this CA cert. For security, you should configure Marinda to only use this self-signed CA cert as the trust anchor.

Warning
Don’t obtain client and server SSL certificates from an established certificate authority like VeriSign. If you do, then an adversary could obtain certificates in the same way and potentially become rogue nodes in the system (if they can become a man-in-the-middle or if they can poison DNS).

The client certificate should have the IP address of the client machine in the Common Name field. The server certificate should have the fully-qualified domain name of the server machine in the Common Name field. You need to follow these conventions for the post-connection certificate validations to succeed. Here are examples of client and server certificates following these conventions for the client 192.168.1.1 and server foo.example.com:

sample-client-cert.pem
Certificate:
    Data:
...
        Issuer: C=US, ST=California, O=CAIDA, OU=Measurement Infrastructure,
                CN=Octopus CA/emailAddress=asdf@caida.org
        Subject: C=US, ST=California, L=San Diego, O=CAIDA, OU=Measurement Infrastructure,
                 CN=192.168.1.1/emailAddress=asdf@caida.org
...
sample-server-cert.pem
Certificate:
    Data:
...
        Issuer: C=US, ST=California, O=CAIDA, OU=Measurement Infrastructure,
                CN=Octopus CA/emailAddress=asdf@caida.org
        Subject: C=US, ST=California, L=San Diego, O=CAIDA, OU=Measurement Infrastructure,
                 CN=foo.example.com/emailAddress=asdf@caida.org
...
global-config.yaml
---
server_port: 8742
use_ssl: true
check_client_name: true
cert: sample-server-cert.pem
key: sample-server-key.INSECURE.pem
ca_file: my-cacert.pem
#ca_path: hashedCA
nodes:
  10.0.2.3: 1
  10.0.2.4: 2
  10.0.2.5: 3
Tip
The sample global-config-ssl.yaml contains the above config, so you can copy it to global-config.yaml and edit it as needed.

The check_client_name line specifies whether the server should check the client’s IP address against the IP address stored in the Common Name field of the client’s certificate. If this line is missing, or if the value is false, then the server only checks that the client’s certificate is valid and signed by the trusted CA cert. Using separate client certificates for each client machine leads to the most secure system, but when this becomes too inconvenient, the next best thing is to use a single generic client certificate and disable check_client_name.

The cert line specifies the path to the server’s certificate in the PEM format. Other certificate formats may be supported (see the openssl documentation).

The key line specifies the path to the private key for the server’s certificate. This key file must not be password protected (which explains the "INSECURE" substring in the filename, a convention). The recommended permissions for this file is rwx------. There is no support yet for interactively entering the password for the key file.

The ca_file line specifies the path to the CA cert. This CA cert will be the only trusted CA cert in the system. If you need to support multiple CA certs for some reason, then store them in a directory with special hashed names and provide the directory path with a ca_path line (see the openssl documentation). In the above sample global-config.yaml, the ca_path line has been commented out, since a ca_file line is provided.

local-config.yaml
---
socket: /tmp/localts.sock
node_id: 1
global_server_addr: foo.example.com
global_server_port: 8742
use_ssl: true
cert: sample-client-cert.pem
key: sample-client-key.INSECURE.pem
ca_file: my-cacert.pem
#ca_path: hashedCA
Tip
The sample local-config-ssl.yaml contains the above config, so you can copy it to local-config.yaml and edit it as needed.

If the local server is configured to use SSL, then it will always check that the fully-qualified domain name of the server matches the Common Name field of the server’s certificate. There is no configuration option to disable this check.

Note
The local server may perform a reverse lookup of the server’s IP address to find the fully-qualified domain name of the server—that is, it doesn’t just use the value of the global_server_addr line for certificate validation. Be sure to check that there is a correct, working entry in the reverse DNS zone for the server.

Firewall Configuration

If your organization uses firewalls, then you’ll need to allow incoming TCP connections to port 8742 (the value of server_port in global-config.yaml) on the machine hosting the global server. The global server is the only component of a Marinda deployment that listens on a TCP port. In particular, the local servers only open outgoing connections (to the global server), so no special firewall rules are needed for them.

Syslog Configuration

The global and local servers use syslog for logging. The global server logs to the local1 facility, and the local server to the local0 facility. Both servers are somewhat chatty and continually write diagnostic messages at the debug level and higher by default.

Here is how syslog is configured by default on the following platforms:

MacOS X

The default syslog configuration logs all Marinda messages: local0 goes to /var/log/appfirewall.log, and local1 to /var/log/ipfw.log. Marinda error messages will show up in /var/log/system.log.

Linux (Ubuntu)

The default syslog configuration logs all Marinda messages. Both local0 and local1 go to /var/log/syslog.

FreeBSD

Some configuration changes are needed to see all Marinda messages (such as local0 messages and debug level messages). Please see the FreeBSD syslog documentation.

You can enable even greater debug logging with a variety of configuration options for both the global and local servers.

global-config.yaml
# ...

debug_commands: true
debug_io_messages: true
debug_io_select: true
debug_io_bytes: true

The above options control the logging of the interaction between the global and local servers.

local-config.yaml
# ...

debug_client_commands = true
debug_client_io_bytes = true

debug_mux_commands = true
debug_mux_io_messages = true
debug_mux_io_bytes = true

debug_io_select: true

The debug_client options control the logging of the interaction between the local server and client programs. The debug_mux options control the logging of the interaction between the local and global servers.

Note
The volume of syslog messages can be quite high with the above debugging options, which might cause a problem if there is limited disk space or if syslog messages are forwarded to remote hosts.

Starting Servers

The easiest way to start up the global and local servers is directly from the marinda-0.13.3 directory created from the Marinda distribution package.

If you configured Marinda for local tuple space only, then all you have to do is execute:

$ ./marinda-ls -d
starting marinda-ls in background
Note
If you are using Ruby 1.9.x under FreeBSD, then the -d option will not start the process in the background, since multithreaded processes cannot perform a fork under FreeBSD. In this case, use the daemon command.

For all other Marinda configurations, you will need to start up the global server as well as all of the local servers. If you do not need to start the global server, then you should jump ahead and read the Client Programming Guide to learn how to use Marinda.

The process for starting up the global server is just as simple as for the local server. Go to the machine where you want to run the global server and then execute the following command from the marinda-0.13.3 directory:

$ ./marinda-gs -d
starting marinda-gs in background

You will see the following new files in the marinda-0.13.3 directory:

  • globalserver-state.db: an SQLite database containing the persistent state of the global server, including user-triggered checkpoints of the contents of the tuple space, and

  • globalserver.LOCK: a lock file to prevent multiple global servers from running concurrently.

Now go to each client machine and start up the local server with the command ./marinda-ls -d as above.

That’s all there is to it. You may proceed to the Client Programming Guide.

Starting Marinda from a different location

You can copy the Marinda start up scripts (marinda-gs and marinda-ls) and configuration files anywhere you wish. If the configuration files are not in the current directory when you invoke a start up script, then you can use the --root option to specify the directory containing the configuration files; for example:

$ cp marinda-gs /usr/local/bin
$ cp global-config.yaml /usr/local/pkg/marinda
$ /usr/local/bin/marinda-gs --root=/usr/local/pkg/marinda

The root directory is also used to store globalserver-state.db and globalserver.LOCK, so you must have write permission to the directory.

Adding new client nodes

If you deploy a new client machine (that is, a machine running a local server) at some later point, add the IP address and node ID to global-config.yaml and then tell the global server to reload its config with SIGHUP; that is, if the process ID of the global server is 1234, then execute:

$ kill -HUP 1234
Local server startup error: Address already in use

You may come across the following error while starting up the local server:

./marinda-ls:279:in `initialize': Address already in use -
                     /tmp/localts.sock (Errno::EADDRINUSE)

This simply means that the /tmp/localts.sock socket file already exists. This file may exist if marinda-ls previously crashed (marinda-ls will delete the file on normal exit). If you’re certain that there is no other local server process running, then delete /tmp/localts.sock and re-execute marinda-ls.