Exemplo n.º 1
0
def main(queues: Dict[bytes, 'Queue[Any]']) -> None:
    """\
    Read the argument from the command line and launch the data diode simulator.

    This application is the data diode simulator program used to
    visualize data transfer inside the data diode #1 between the Source
    Computer and the Networked Computer, or data transfer inside the
    data diode #2 between the Networked Computer and the Destination
    Computer. The local testing terminal multiplexer configurations that
    use data diode simulators run two instances of this program.

    The visualization is done with an indicator ('<' or '>') that blinks
    when data passes from one program to another. The data diode
    simulator does not provide any of the endpoint security properties
    that the hardware data diodes do.

    The visualization is designed to make data transfer between programs
    slower than is the case with actual serial interfaces. This allows
    the user to track the movement of data from one program to another
    with their eyes.
    """
    time.sleep(0.5)  # Wait for the terminal multiplexer size to stabilize

    argv, input_socket, output_socket = process_arguments()

    io_queue = Queue()  # type: Queue[Any]
    process_list = [
        Process(target=rx_loop, args=(io_queue, input_socket)),
        Process(target=tx_loop, args=(io_queue, output_socket, argv))
    ]

    for p in process_list:
        p.start()

    monitor_processes(process_list, NC, queues, error_exit_code=0)
Exemplo n.º 2
0
    def test_wipe_tails(self, mock_os_system, *_):
        queues = gen_queue_dict()
        process_list = [Process(target=self.mock_process)]

        os.mkdir(DIR_USER_DATA)
        self.assertTrue(os.path.isdir(DIR_USER_DATA))

        for p in process_list:
            p.start()

        def queue_delayer():
            """Place WIPE packet to queue after delay."""
            time.sleep(0.01)
            queues[EXIT_QUEUE].put(WIPE)

        threading.Thread(target=queue_delayer).start()

        with self.assertRaises(SystemExit):
            monitor_processes(process_list, RX, queues)

        mock_os_system.assert_called_with('poweroff')

        # Test that user data wasn't removed
        self.assertTrue(os.path.isdir(DIR_USER_DATA))
        tear_queues(queues)
Exemplo n.º 3
0
    def test_wipe(self, mock_os_system, *_: Any) -> None:
        queues = gen_queue_dict()
        process_list = [Process(target=self.mock_process)]

        os.mkdir(DIR_USER_DATA)
        os.mkdir(DIR_RECV_FILES)
        self.assertTrue(os.path.isdir(DIR_USER_DATA))
        self.assertTrue(os.path.isdir(DIR_RECV_FILES))

        for p in process_list:
            p.start()

        def queue_delayer() -> None:
            """Place WIPE packet to queue after delay."""
            time.sleep(0.01)
            queues[EXIT_QUEUE].put(WIPE)

        threading.Thread(target=queue_delayer).start()

        with self.assertRaises(SystemExit):
            monitor_processes(process_list, RX, queues)
        self.assertFalse(os.path.isdir(DIR_USER_DATA))
        self.assertFalse(os.path.isdir(DIR_RECV_FILES))
        mock_os_system.assert_called_with('systemctl poweroff')

        tear_queues(queues)
Exemplo n.º 4
0
    def test_dying_process(self, *_):
        def mock_process():
            """Function that returns after a moment."""
            time.sleep(0.01)

        queues = gen_queue_dict()
        process_list = [Process(target=mock_process)]

        for p in process_list:
            p.start()

        with self.assertRaises(SystemExit):
            monitor_processes(process_list, RX, queues)

        tear_queues(queues)
Exemplo n.º 5
0
    def test_exit(self, *_):
        queues = gen_queue_dict()
        process_list = [Process(target=self.mock_process)]

        for p in process_list:
            p.start()

        def queue_delayer():
            """Place EXIT packet into queue after delay."""
            time.sleep(0.01)
            queues[EXIT_QUEUE].put(EXIT)

        threading.Thread(target=queue_delayer).start()

        with self.assertRaises(SystemExit):
            monitor_processes(process_list, RX, queues)

        tear_queues(queues)
Exemplo n.º 6
0
def main() -> None:
    """Load persistent data and launch the Transmitter/Receiver Program.

    This function decrypts user data from databases and launches
    processes for Transmitter or Receiver Program. It then monitors the
    EXIT_QUEUE for EXIT/WIPE signals and each process in case one of
    them dies.

    If you're reading this code to get the big picture on how TFC works,
    start by looking at the loop functions below, defined as the target
    for each process, from top to bottom:
        From `input_loop` process, you can see how the Transmitter
    Program processes a message or command from the user, creates
    assembly packets for a message/file/command, and how those are
    eventually pushed into a multiprocessing queue, from where they are
    loaded by the `sender_loop`.
        The `sender_loop` process encrypts outgoing assembly packets,
    and outputs the encrypted datagrams to the Networked Computer. The
    process also sends assembly packets to the `log_writer_loop`.
        The `log_writer_loop` process filters out non-message assembly
    packets and if logging for contact is enabled, stores the message
    assembly packet into an encrypted log database.
        The `noise_loop` processes are used to provide the `sender_loop`
    an interface identical to that of the `input_loop`. The
    `sender_loop` uses the interface to load noise packets/commands when
    traffic masking is enabled.

    Refer to the file `relay.py` to see how the Relay Program on
    Networked Computer manages datagrams between the network and
    Source/Destination Computer.

    In Receiver Program (also launched by this file), the `gateway_loop`
    process acts as a buffer for incoming datagrams. This buffer is
    consumed by the `receiver_loop` process that organizes datagrams
    loaded from the buffer into a set of queues depending on datagram
    type. Finally, the `output_loop` process loads and processes
    datagrams from the queues in the order of priority.
    """
    working_dir = f'{os.getenv("HOME")}/{DIR_TFC}'
    ensure_dir(working_dir)
    os.chdir(working_dir)

    operation, local_test, data_diode_sockets = process_arguments()

    check_kernel_version()
    check_kernel_entropy()

    print_title(operation)

    master_key   = MasterKey(              operation, local_test)
    gateway      = Gateway(                operation, local_test, data_diode_sockets)
    settings     = Settings(   master_key, operation, local_test)
    contact_list = ContactList(master_key, settings)
    key_list     = KeyList(    master_key, settings)
    group_list   = GroupList(  master_key, settings, contact_list)

    if settings.software_operation == TX:
        onion_service = OnionService(master_key)

        queues = {MESSAGE_PACKET_QUEUE:    Queue(),  # Standard              messages
                  COMMAND_PACKET_QUEUE:    Queue(),  # Standard              commands
                  TM_MESSAGE_PACKET_QUEUE: Queue(),  # Traffic masking       messages
                  TM_FILE_PACKET_QUEUE:    Queue(),  # Traffic masking       files
                  TM_COMMAND_PACKET_QUEUE: Queue(),  # Traffic masking       commands
                  TM_NOISE_PACKET_QUEUE:   Queue(),  # Traffic masking noise packets
                  TM_NOISE_COMMAND_QUEUE:  Queue(),  # Traffic masking noise commands
                  RELAY_PACKET_QUEUE:      Queue(),  # Unencrypted datagrams to Networked Computer
                  LOG_PACKET_QUEUE:        Queue(),  # `log_writer_loop` assembly packets to be logged
                  LOG_SETTING_QUEUE:       Queue(),  # `log_writer_loop` logging state management between noise packets
                  TRAFFIC_MASKING_QUEUE:   Queue(),  # `log_writer_loop` traffic masking setting management commands
                  LOGFILE_MASKING_QUEUE:   Queue(),  # `log_writer_loop` logfile masking setting management commands
                  KEY_MANAGEMENT_QUEUE:    Queue(),  # `sender_loop` key database management commands
                  SENDER_MODE_QUEUE:       Queue(),  # `sender_loop` default/traffic masking mode switch commands
                  WINDOW_SELECT_QUEUE:     Queue(),  # `sender_loop` window selection commands during traffic masking
                  EXIT_QUEUE:              Queue()   # EXIT/WIPE signal from `input_loop` to `main`
                  }  # type: Dict[bytes, Queue]

        process_list = [Process(target=input_loop,      args=(queues, settings, gateway, contact_list, group_list,
                                                              master_key, onion_service, sys.stdin.fileno())),
                        Process(target=sender_loop,     args=(queues, settings, gateway, key_list)),
                        Process(target=log_writer_loop, args=(queues, settings)),
                        Process(target=noise_loop,      args=(queues, contact_list)),
                        Process(target=noise_loop,      args=(queues,))]

    else:
        queues = {GATEWAY_QUEUE:             Queue(),  # Buffer for incoming datagrams
                  LOCAL_KEY_DATAGRAM_HEADER: Queue(),  # Local key datagrams
                  MESSAGE_DATAGRAM_HEADER:   Queue(),  # Message   datagrams
                  FILE_DATAGRAM_HEADER:      Queue(),  # File      datagrams
                  COMMAND_DATAGRAM_HEADER:   Queue(),  # Command   datagrams
                  EXIT_QUEUE:                Queue()   # EXIT/WIPE signal from `output_loop` to `main`
                  }

        process_list = [Process(target=gateway_loop,  args=(queues, gateway)),
                        Process(target=receiver_loop, args=(queues, gateway)),
                        Process(target=output_loop,   args=(queues, gateway, settings, contact_list, key_list,
                                                            group_list, master_key, sys.stdin.fileno()))]

    for p in process_list:
        p.start()

    monitor_processes(process_list, settings.software_operation, queues)
Exemplo n.º 7
0
Arquivo: relay.py Projeto: dimwap/tfc
def main() -> None:
    """Load persistent settings and launch the Relay Program.

    This function loads settings from the settings database and launches
    processes for the Relay Program. It then monitors the EXIT_QUEUE for
    EXIT/WIPE signals and each process in case one of them dies.

    If you're reading this code to get the big picture on how TFC works,
    start by looking at `tfc.py` for Transmitter Program functionality.
    After you have reviewed the Transmitter Program's code, revisit the
    code of this program.

    The Relay Program operates multiple processes to enable real time IO
    between multiple data sources and destinations.

    Symbols:
        process_name    denotes the name of the process

        ─>, <─, ↑, ↓    denotes the direction of data passed from one
                        process to another

        (Description)   denotes the description of data passed from one
                        process to another

        ┈, ┊            denotes the link between a description and path
                        of data matching the description

        ▶|, |◀          denotes the gateways where the direction of data
                        flow is enforced with hardware data diodes


                                         Relay Program (Networked Computer)
                    ┏━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━┓
                    ┃                  (1: Onion Service private key)                          ┃
                                       (2: Contact management commands)
                    ┃  ┌──────────────────────────────┬────────────────────────┬────────────┐  ┃
                       │                              │                        ↓┈2          │
                    ┃  │                    ┌─────> relay_command  ┌───> c_req_manager      │  ┃
                       │                    │                  │   │                        │
                    ┃  │     (Relay Program┈│   (Onion Service┈│   │                        │  ┃
                       │      commands)     │    public key)   │   │┈(In: Contact requests) │
                    ┃  │                    │                  ↓   │       ┊              1┈↓  ┃
      Source ───▶|─────(── gateway_loop ─> src_incoming ─> flask_server <─────> onion_service <───> client on contact's
    Computer        ┃  │                            │              ↑       ┊                   ┃     Networked Computer
                       │   (Local keys, commands,   │              │ (Out: msg/file/pubkey/
                    ┃  │    and copies of messages)┄│              │  group mgmt message)      ┃
                       │                            │              │
                    ┃  │                            ↓              │                           ┃
 Destination <──|◀─────(────────────────────── dst_outgoing        │
    Computer        ┃  │                    ┊       ↑              │                           ┃
                       ├──> g_msg_manager   ┊       │              │
                    ┃  │               ↑    ┊       │              │                           ┃
                       │        (Group┈│  (Incoming┈│  (URL token)┈│
                    ┃  │    management │  messages) │              │                           ┃
                       │     messages) │            │              │
                    ┃  ↓┈1,2           │            │              │                           ┃
                      client_scheduler │            │              │
                    ┃         └──> client ──────────┴──────────────┘                           ┃
                                     ↑
                    ┃                │                                                         ┃
                                     └───────────────────────────────────────────────────────────> flask_server on
                    ┃                                        ┊                                 ┃   contact's Networked
                                (In: message/file/public key/group management message              Computer
                    ┃            Out: contact request)                                         ┃
                    ┗━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━┛


    The diagram above gives a rough overview of the structure of the
    Relay Program. The Relay Program acts as a protocol converter that
    reads datagrams from the Source Computer. Outgoing
    message/file/public key datagrams are made available in the user's
    Tor v3 Onion Service. Copies of sent message datagrams as well as
    datagrams from contacts' Onion Services are forwarded to the
    Destination Computer. The Relay-to-Relay encrypted datagrams from
    contacts such as contact requests, public keys and group management
    messages are displayed by the Relay Program.

    Outgoing message datagrams are loaded by contacts from the user's
    Flask web server. To request messages intended for them, each
    contact uses a contact-specific URL token to load the messages.
    The URL token is the X448 shared secret derived from the per-session
    ephemeral X448 values of the two conversing parties. The private
    value stays on the Relay Program -- the public value is obtained by
    connecting to the root domain of contact's Onion Service.
    """
    if platform_is_tails():
        working_dir = f'{os.getenv("HOME")}/{DIR_TAILS_PERS}{DIR_TFC}'
    else:
        working_dir = f'{os.getenv("HOME")}/{DIR_TFC}'

    ensure_dir(working_dir)
    os.chdir(working_dir)

    _, local_test, data_diode_sockets, qubes = process_arguments()

    gateway = Gateway(NC, local_test, data_diode_sockets, qubes)

    print_title(NC)

    url_token_private_key = X448.generate_private_key()
    url_token_public_key = X448.derive_public_key(url_token_private_key).hex()

    queues = \
        {GATEWAY_QUEUE:       Queue(),  # All     datagrams           from `gateway_loop`          to `src_incoming`
         DST_MESSAGE_QUEUE:   Queue(),  # Message datagrams           from `src_incoming`/`client` to `dst_outgoing`
         TX_BUF_KEY_QUEUE:    Queue(),  # Datagram buffer key         from `onion_service`         to `src_incoming`
         RX_BUF_KEY_QUEUE:    Queue(),  # Datagram buffer key         from `onion_service`         to `flask_server`
         SRC_TO_RELAY_QUEUE:  Queue(),  # Command datagrams           from `src_incoming`          to `relay_command`
         DST_COMMAND_QUEUE:   Queue(),  # Command datagrams           from `src_incoming`          to `dst_outgoing`
         CONTACT_MGMT_QUEUE:  Queue(),  # Contact management commands from `relay_command`         to `client_scheduler`
         C_REQ_STATE_QUEUE:   Queue(),  # Contact req. notify setting from `relay_command`         to `c_req_manager`
         URL_TOKEN_QUEUE:     Queue(),  # URL tokens                  from `client`                to `flask_server`
         GROUP_MSG_QUEUE:     Queue(),  # Group management messages   from `client`                to `g_msg_manager`
         CONTACT_REQ_QUEUE:   Queue(),  # Contact requests            from `flask_server`          to `c_req_manager`
         C_REQ_MGMT_QUEUE:    Queue(),  # Contact list management     from `relay_command`         to `c_req_manager`
         GROUP_MGMT_QUEUE:    Queue(),  # Contact list management     from `relay_command`         to `g_msg_manager`
         ONION_CLOSE_QUEUE:   Queue(),  # Onion Service close command from `relay_command`         to `onion_service`
         ONION_KEY_QUEUE:     Queue(),  # Onion Service private key   from `relay_command`         to `onion_service`
         TOR_DATA_QUEUE:      Queue(),  # Open port for Tor           from `onion_service`         to `client_scheduler`
         EXIT_QUEUE:          Queue(),  # EXIT/WIPE signal            from `relay_command`         to `main`
         ACCOUNT_CHECK_QUEUE: Queue(),  # Incorrectly typed accounts  from `src_incoming`          to `account_checker`
         ACCOUNT_SEND_QUEUE:  Queue(),  # Contact requests            from `flask_server`          to `account_checker`
         USER_ACCOUNT_QUEUE:  Queue(),  # User's public key           from `onion_service`         to `account_checker`
         PUB_KEY_CHECK_QUEUE: Queue(),  # Typed public keys           from `src_incoming`          to `pub_key_checker`
         PUB_KEY_SEND_QUEUE:  Queue(),  # Received public keys        from `client`                to `pub_key_checker`
         GUI_INPUT_QUEUE:     Queue()  # User inputs                 from `GUI prompt`            to `account_checker`
         }  # type: Dict[bytes, Queue[Any]]

    process_list = [
        Process(target=gateway_loop, args=(queues, gateway)),
        Process(target=src_incoming, args=(queues, gateway)),
        Process(target=dst_outgoing, args=(queues, gateway)),
        Process(target=client_scheduler,
                args=(queues, gateway, url_token_private_key)),
        Process(target=g_msg_manager, args=(queues, )),
        Process(target=c_req_manager, args=(queues, )),
        Process(target=flask_server, args=(queues, url_token_public_key)),
        Process(target=onion_service, args=(queues, )),
        Process(target=relay_command, args=(
            queues,
            gateway,
        )),
        Process(target=account_checker, args=(queues, sys.stdin.fileno())),
        Process(target=pub_key_checker, args=(queues, local_test))
    ]

    for p in process_list:
        p.start()

    monitor_processes(process_list, NC, queues)
Exemplo n.º 8
0
def main() -> None:
    """Load persistent settings and launch the Relay Program.

    This function loads settings from the settings database and launches
    processes for the Relay Program. It then monitors the EXIT_QUEUE for
    EXIT/WIPE signals and each process in case one of them dies.

    If you're reading this code to get the big picture on how TFC works,
    start by looking at `tfc.py` for Transmitter Program functionality.
    After you have reviewed the Transmitter Program's code, revisit the
    code of this program.

    The Relay Program operates multiple processes to enable real time IO
    between multiple data sources and destinations.

    Symbols:
        process_name    denotes the name of the process

        ─>, <─, ↑, ↓    denotes the direction of data passed from one
                        process to another

        (Description)   denotes the description of data passed from one
                        process to another

        ┈, ┊            denotes the link between a description and path
                        of data matching the description

        ▶|, |◀          denotes the gateways where the direction of data
                        flow is enforced with hardware data diodes


                                         Relay Program (Networked Computer)
                    ┏━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━ ━━┓
                    ┃                                                                         ┃
                                       (Contact management commands)
                    ┃  ┌─────────────────────────────┬─────────────────────┐                  ┃
                       |                             |                     ↓
                    ┃  |                    ┌─────> relay_command   ┌───> c_req_manager       ┃
                       |                    │                  │    |
                    ┃  |                    │   (Onion Service┈│    |┈(Contact requests)      ┃
                       |                    │     private key) │    |
                    ┃  |                    │                  ↓    |                         ┃
                       |                    │            onion_service ───────────────────────────> client on contact's
                    ┃  |     (Relay Program┈│               ↑                ┊                ┃     Networked Computer
                       |          commands) │               │┈(Outgoing msg/file/public key)
                    ┃  |                    │               │                                 ┃
      Source ───▶|─────(── gateway_loop ─> src_incoming ─> flask_server <─┐
    Computer        ┃  |                            |                     |                   ┃
                       |                            |                     |
                    ┃  |    (Local keys, commands,  |                     |                   ┃
                       |    and copies of messages)┄|                     |
                    ┃  |             ┊              ↓                     |                   ┃
 Destination <──|◀─────(────────────────────── dst_outgoing               |
    Computer        ┃  |                    ┊       ↑                     |                   ┃
                       ├──> g_msg_manager   ┊       │                     |
                    ┃  |               ↑    ┊       │                     |                   ┃
                       |        (Group┈│  (Incoming┈│         (URL token)┈|
                    ┃  |    management │  messages) │                     |                   ┃
                       │     messages) │            │                     |
                    ┃  ↓               │            │                     |                   ┃
                      client_scheduler │            │                     |
                    ┃         └──> client ──────────┴─────────────────────┘                   ┃
                                       ↑
                    ┃                  │                                                      ┃
                                       └─────────────────────────────────────────────────────────── flask_server on
                    ┃                                        ┊                                ┃     contact's Networked
                                (Incoming message/file/public key/group management message)         Computer
                    ┃                                                                         ┃
                    ┗━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━  ━━ ━━┛


    The diagram above gives a rough overview of the structure of the
    Relay Program. The Relay Program acts as a protocol converter that
    reads datagrams from the Source Computer. Outgoing
    message/file/public key datagrams are made available in the user's
    Tor v3 Onion Service. Copies of sent message datagrams as well as
    datagrams from contacts' Onion Services are forwarded to the
    Destination Computer. The Relay-to-Relay encrypted datagrams from
    contacts such as contact requests, public keys and group management
    messages are displayed by the Relay Program.

    Outgoing message datagrams are loaded by contacts from the user's
    Flask web server. To request messages intended for them, each
    contact uses a contact-specific URL token to load the messages.
    The URL token is the X448 shared secret derived from the per-session
    ephemeral X448 values of the two conversing parties. The private
    value stays on the Relay Program -- the public value is obtained by
    connecting to the root domain of contact's Onion Service.
    """
    working_dir = f'{os.getenv("HOME")}/{DIR_TFC}'
    ensure_dir(working_dir)
    os.chdir(working_dir)

    _, local_test, data_diode_sockets = process_arguments()

    gateway = Gateway(NC, local_test, data_diode_sockets)

    print_title(NC)

    url_token_private_key = X448PrivateKey.generate()
    url_token_public_key = url_token_private_key.public_key().public_bytes(
        encoding=Encoding.Raw, format=PublicFormat.Raw).hex()  # type: str

    queues = \
        {GATEWAY_QUEUE:      Queue(),  # All     datagrams           from `gateway_loop`          to `src_incoming`
         DST_MESSAGE_QUEUE:  Queue(),  # Message datagrams           from `src_incoming`/`client` to `dst_outgoing`
         M_TO_FLASK_QUEUE:   Queue(),  # Message/pubkey datagrams    from `src_incoming`          to `flask_server`
         F_TO_FLASK_QUEUE:   Queue(),  # File datagrams              from `src_incoming`          to `flask_server`
         SRC_TO_RELAY_QUEUE: Queue(),  # Command datagrams           from `src_incoming`          to `relay_command`
         DST_COMMAND_QUEUE:  Queue(),  # Command datagrams           from `src_incoming`          to `dst_outgoing`
         CONTACT_MGMT_QUEUE: Queue(),  # Contact management commands from `relay_command`         to `client_scheduler`
         C_REQ_STATE_QUEUE:  Queue(),  # Contact req. notify setting from `relay_command`         to `c_req_manager`
         URL_TOKEN_QUEUE:    Queue(),  # URL tokens                  from `client`                to `flask_server`
         GROUP_MSG_QUEUE:    Queue(),  # Group management messages   from `client`                to `g_msg_manager`
         CONTACT_REQ_QUEUE:  Queue(),  # Contact requests            from `flask_server`          to `c_req_manager`
         C_REQ_MGMT_QUEUE:   Queue(),  # Contact list management     from `relay_command`         to `c_req_manager`
         GROUP_MGMT_QUEUE:   Queue(),  # Contact list management     from `relay_command`         to `g_msg_manager`
         ONION_CLOSE_QUEUE:  Queue(),  # Onion Service close command from `relay_command`         to `onion_service`
         ONION_KEY_QUEUE:    Queue(),  # Onion Service private key   from `relay_command`         to `onion_service`
         TOR_DATA_QUEUE:     Queue(),  # Open port for Tor           from `onion_service`         to `client_scheduler`
         EXIT_QUEUE:         Queue()   # EXIT/WIPE signal            from `relay_command`         to `main`
         }  # type: Dict[bytes, Queue[Any]]

    process_list = [
        Process(target=gateway_loop, args=(queues, gateway)),
        Process(target=src_incoming, args=(queues, gateway)),
        Process(target=dst_outgoing, args=(queues, gateway)),
        Process(target=client_scheduler,
                args=(queues, gateway, url_token_private_key)),
        Process(target=g_msg_manager, args=(queues, )),
        Process(target=c_req_manager, args=(queues, )),
        Process(target=flask_server, args=(queues, url_token_public_key)),
        Process(target=onion_service, args=(queues, )),
        Process(target=relay_command,
                args=(queues, gateway, sys.stdin.fileno()))
    ]

    for p in process_list:
        p.start()

    monitor_processes(process_list, NC, queues)