Getting Started with Azure Relay

Requirements: IPWorks MQ

Contents

Introduction to Azure Relay and IPWorks MQ

IPWorks MQ is a suite of components aimed at providing lightweight yet fully-featured implementations of modern messaging protocols. This article will focus specifically on the AzureRelayReceiver, AzureRelaySender and AzureRelayProxy components.

Microsoft Azure Relay is a secure and simple way to expose services running in a corporate network to the public cloud. Configuration does not require network infrastructure changes or the opening of ports in a firewall. Such a service can be accessed by applications in the cloud or other networks and communicate with them via relayed WebSocket connections.

The entity that listens for and accept connections is called the "receiver" or "listener". This functionality is provided by the AzureRelayReceiver component. The entity that initiate connections with the receiver through Azure Relay is called a "sender". This functionality is provided by the AzureRelaySender component.

This article will guide configuration and data transfer for both roles using the corresponding component. It will also discuss extended functionality such as forwarding incoming data from AzureRelayReceiver and the use of AzureRelayProxy to forward received data from any TCP-based client over a connection to Azure Relay.

AzureRelayReceiver

The AzureRelayReceiver component is an easy way to implement the listener role in the Azure Relay service. The component establishes a WebSocket connection with the service, specifically to an Azure Hybrid Connection endpoint in a registered namespace.

Once clients have requested connections and been accepted, data can be directly transferred between the connected client and AzureRelayReceiver.

Authenticating and Listening

To authenticate the component with Azure Relay before listening, the following properties must be set:

  • AccessKey - The Shared Access Key (primary or secondary key of the shared access policy created in the Azure portal)
  • AccessKeyName - The Shared Access Key Name (name of shared access policy)
  • HybridConnection - The name of the hybrid connection created in the Azure portal
  • NamespaceAddress - The fully qualified domain name of the relay namespace hosting the Hybrid Connection

After these values are configured, to connect to the Azure Relay service simply set the Listening property to True. The component will attempt to connect to the service by making a listening request to the Hybrid Connection endpoint and establishing a WebSocket connection to the address returned.

The following code demonstrates authentication and listening:

Azurerelayreceiver listener = new Azurerelayreceiver(); listener.AccessKey = "9oKRDwjl0s440MlLUi4qHxDL34j1FS6K3t5TRoJ216c="; listener.AccessKeyName = "RootManageSharedAccessKey"; listener.NamespaceAddress = "myrelay.servicebus.windows.net"; listener.HybridConnection = "hc1"; listener.Listening = true;

The component is now listening to incoming connections from senders, which would make a connection request via the Azure Relay service. To stop listening for new connections, set Listening to False. To terminate all existing connections and shut down the listener, call the Shutdown method.

During the connection process, various events will fire corresponding to various steps in the connection, such as the connection to the service host, the SSL handshake, and the WebSocket connection.

Accepting Connection Requests

Once the AzureRelayReceiver component is listening, "sender" clients may request connections to the receiver by using the same Azure Relay Service Namespace and Hybrid Connection. When such a request is initiated, the service notifies the receiver, providing information that the receiver can use to determine whether to accept the connection.

At this time, the ConnectionRequest event is fired, and this connection information is provided in the event parameters. The event parameters are used to accept or reject the request. Accept has a default value of True, but should be set to False if the listener determines it wishes to reject the connection. If rejecting, StatusCode and StatusDescription should also be set to appropriate values.

The following code demonstrates the use of the ConnectionRequest event to process requests:

listener.OnConnectionRequest += (s, e) => { Console.WriteLine("Connection Request From: " + e.RemoteAddress + ":" + e.RemotePort); // application logic using event args to determine whether to connect e.Accept = true; // OR e.Accept = false; };

If the connection is accepted, the component will establish a WebSocket connection at the sender-specific URL contained in RendezvousAddress parameter and the ConnectionConnected and ConnectionReadyToSend events will fire. The receiver and listener will then be free to send data via their WebSocket connections and the data will be relayed to the other connection. When a connection to a sender is closed, ConnectionDisconnected will fire.

If the connection is rejected the sender that requested the connection will receive the status code and status description and no connection will be established.

Receiving Data

Data can be received using the ConnectionDataIn event. The event arguments include the ConnectionId of the client which sent the data, the Text of the message, and the DataFormat (UTF-8, binary, or pong packet).

Additionally, there is a boolean EOM parameter which indicates whether or not this is the End Of Message. Larger messages will be sent in fragments, so the ConnectionDataIn event will fire multiple times. To buffer the message internally until the complete message is received, only firing the event on an EOM, set the BufferMessage configuration setting to true.

The following code demonstrates the reception of messages using the ConnectionDataIn event:

listener.OnConnectionDataIn += (s, e) => { Console.WriteLine("Received data from " + e.ConnectionId + ": " + e.Text); // for binary data (e.DataFormat==2) use e.TextB bool EOM = e.EOM; // if listener.Config("BufferMessage=True"); has been set, this will always be true };

Sending Data

When the receiver has accepted an incoming connection, the ConnectionReadyToSend event will fire (it will also fire when the component is ready to accept data after a failed data send). To send data to a connected client, use the SendText, SendFile, or Send method, depending on the data format:

  • SendText - string parameter; the component will UTF-8 encode the supplied text.
  • SendFile - string parameter for file name; this method operates synchronously, so DefaultTimeout or Timeout must be set to a positive value before calling this method.
  • Send - byte array parameter; the component will send the specified binary data

Each method has a string ConnectionId parameter and a data parameter of type corresponding to the data format. The following code demonstrates the sending of different types of data:

string connId = listener.Connections[0].ConnectionId; string textToSend = "IPWorks MQ"; string filenameToSend = GetFilenameToSend(); listener.DefaultTimeout = 10; // must be set for SendFile byte[] bytesToSend = GetBytesToSend(); listener.SendText(connId, textToSend); listener.SendFile(connId, filenameToSend); listener.Send(connId, bytesToSend);

Note that if DefaultTimeout is set to 0, all operations will run uninterrupted until successful completion or an error condition is encountered. If it is set to a positive value, the component will wait for the operation to complete before returning control.

Data can alternatively be sent using the Connections collection property. Connections contains a collection of AzureRelayConnection objects. Setting the DataToSend property of one of these objects will send that data to the client associated with that connection. You can also get the ConnectionId and disconnect by setting Connected to True for any of these objects. For example:

foreach (AzureRelayConnection conn in listener.Connections.Values) { string id = conn.Id; conn.DataToSend = "data to send"; // equivalent to listener.SendText(id, "data to send"); conn.Connected = false; // equivalent to listener.Disconnect(id); }

Forwarding Traffic

The component also supports forwarding traffic to another location, defined by the ForwardingHost and ForwardingPort properties. Both must be specified for traffic to be forwarded. When a connection is made, the component will automatically establish a connection to this location. Data will then flow freely between the connected client and the host.

The following code sets the forwarding properties so that all new connections will be forwarded to the host specified:

listener.ForwardingHost = hostAddress; listener.FowardingPort = hostPort; listener.Listening = true;

AzureRelaySender

The AzureRelaySender component implements the sender role in the Azure Relay interaction model, acting as the client which will initiate a new connection towards a listener client via the Azure Relay service. The component establishes a WebSocket connection with the service, specifically to an Azure Hybrid Connection endpoint in a registered namespace.

Once connected, the component can send and receive data which will be relayed to and from the listener.

Authenticating and Connecting

The four properties required to authenticate the sender component before connecting are the same as the listener component - see AzureRelayReceiver Authenticating and Listening for property details. The corresponding sender code is:

Azurerelaysender sender = new Azurerelaysender(); sender.AccessKey = "9oKRDwjl0s440MlLUi4qHxDL34j1FS6K3t5TRoJ216c="; sender.AccessKeyName = "RootManageSharedAccessKey"; sender.NamespaceAddress = "myrelay.servicebus.windows.net"; sender.HybridConnection = "hc1"; sender.Connect();

If the hybrid connection is configured to accept anonymous sends then AccessKey and AccessKeyName are not required.

Call Connect as shown above, and the component will make a connection request to the Azure Relay service. Assuming the authentication was successful and there is an active listener, the connection request will be relayed to the server. If it accepts, the WebSocket connection to the rendezvous point will be established. Failure at any of these points will result in an error message.

Updates to the connection state will fire the ConnectionStatus event, useful for monitoring the progress of the connection through stages such as remote host connection, SSL handshake and WebSocket connection.

The Connected and ReadyToSend events will also fire on completion of the process. The component is then ready for data transfer via WebSocket and relay. For more details on connection events, see the documentation.

Receiving Data

Data reception is nearly identical to the AzureRelayReceiver component. The differences are that the data received event is called DataIn rather than ConnectionDataIn, and there is no ConnectionId argument since only one listener can be connected.

The Text, DataFormat and EOM arguments operate the same as in the receiver component. For details about these parameters see the AzureRelayReceiver Receiving Data section The following code demonstrates the reception of messages using the DataIn event:

sender.OnDataIn += (s, e) => { Console.WriteLine("Received data: " + e.Text); // for binary data (e.DataFormat==2) use e.TextB bool EOM = e.EOM; // if sender.Config("BufferMessage=True"); has been set, this will always be true };

Sending Data

Sending data with AzureRelaySender is also very similar to doing so with AzureRelayReceiver. The SendText, SendFile and Send method are used. There is only one parameter for each method - the data.

For details on the difference between the send methods, see the AzureRelayReceiver Sending Data section. The following code uses AzureRelaySender to send data:

string textToSend = "IPWorks MQ"; string filenameToSend = GetFilenameToSend(); byte[] bytesToSend = GetBytesToSend(); sender.SendText(textToSend); sender.SendFile(filenameToSend); // will timeout after Timeout property value if set to a positive value (default is 60) sender.Send(bytesToSend);

In the sender component, the Timeout value controls the amount of time the component will spend performing any operation before an exception is thrown. A value of 0 indicates that operations should run uninterrupted until completion or error.

AzureRelayProxy

The AzureRelayProxy component is designed to listen locally and forward received data over a connection to the Azure Relay Service. This allows any TCP-based client to connect and send data to Azure Relay Service without any additional knowledge.

When the component is listening and a client connects, a corresponding connection is made to the configured NamespaceAddress and data then flows freely between the connected client and the Azure Relay Service. Each new connection made to AzureRelayProxy results in a new connection made to the Azure Relay Service. Connections are not shared between clients.

The diagram below illustrates the design of this component.

                         +---------+                       +----------+
Client A <---- TCP ----> |  Azure  | <==== WebSocket ====> |  Azure   |
Client B <---- TCP ----> |  Relay  | <==== WebSocket ====> |  Relay   |
Client C <---- TCP ----> |  Proxy  | <==== WebSocket ====> |  Service |
                         +---------+                       +----------+  

Authenticating and Listening

The four properties required to authenticate the proxy before setting Listening are the same as the AzureRelayReceiver component - see AzureRelayReceiver Authenticating and Listening for details. The corresponding proxy code is:

Azurerelayproxy proxy = new Azurerelayproxy(); proxy.AccessKey = "9oKRDwjl0s440MlLUi4qHxDL34j1FS6K3t5TRoJ216c="; proxy.AccessKeyName = "RootManageSharedAccessKey"; proxy.NamespaceAddress = "myrelay.servicebus.windows.net"; proxy.HybridConnection = "hc1"; proxy.Listening = true;

To use SSL for incoming connections set SSLCert to a valid certificate with private key and set SSLEnabled to True before setting Listening.

proxy.SSLCert = sslCertToUse; proxy.SetSSLEnabled = true; proxy.OnSSLClientAuthentication += (s, e) => proxy.Listening = true;

The DoEvents method should be called in a loop to ensure timely processing of all activity, including connection requests and data transfer.

To stop listening for new connections set Listening to False. To shutdown the server including existing connections call Shutdown.

Handling Connections

When a connection is made the ConnectionRequest event fires with information about the connecting client. From within this event the client connection may be accepted or rejected. Incoming connections are accepted by default, but can be rejected by setting e.Accept to False.

If the client connection is accepted the Connected event fires when the connection completes and is ready to send and receive data. Data will be proxied between the connected client and the Azure Relay Service. No special steps are required.

When the client disconnects the Disconnected event fires. To initiate the client disconnection call Disconnect with the desired ConnectionId as the parameter.

We appreciate your feedback.  If you have any questions, comments, or suggestions about this article please contact our support team at kb@nsoftware.com.