Implementing a Policy Server for Silverlight Sockets

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • Frinavale
    Recognized Expert Expert
    • Oct 2006
    • 9749

    Implementing a Policy Server for Silverlight Sockets

    Introduction

    This article is part of the article about Using Silverlight Sockets. If you haven't already reviewed this article, please do so before continuing because it explains more about why a Policy Server is required.

    Reiterating why a Policy Server's Required

    Silverlight enforces cross-domain opt-in policies. This means that a policy is required in order to make a connection using a Silverlight Socket. This is very interesting because Silverlight automatically makes a request for the policy when you try to make a connection using a Socket. The request for the policy is made to the same IP endpoint that the Socket is connecting to but it's made on port 943. This port (943) is hardwired into Silverlight and cannot be configured/modified by you as developer.

    This means that we have to implement a Policy Socket Server using a Windows Application that listens for Socket connections on port 943 and sends the client a policy file.

    The policy file is a simple XML file that specifies what port the Socket is allowed to connect to, and the protocol used (TCP). Here is an example of a Policy file that grants a Socket permission to connect on port 4509 (remember the valid port range is between 4502 and 4532):
    Code:
    <?xml version="1.0" encoding="utf-8" ?>
    <access-policy>
      <cross-domain-access>
        <policy>
          <allow-from>
            <domain uri="*" />
          </allow-from>
          <grant-to>
            <!--valid port range: 4502-4534-->
            <socket-resource port="4509" protocol="tcp" />
          </grant-to>
        </policy>
      </cross-domain-access>
    </access-policy>
    Walkthrough

    You could use any Windows application (even a Windows Service application) to develop this application; however, for simplicity sake, I'm going to use a Console application.

    So, please create a new Console application now and give it a meaningful name (like "SocketPolicySe rver").

    Now, add a new class to the project and name it "PolicyConnecti on". This class is used to manage a single socket connection. The reason I've encapsulated the following into one class is because of the asynchronous nature of the Sockets we're using. Without encapsulating the following code into one class it is very hard to manage connections as soon as more than one asynchronous Socket connects (trust me, I learned the hard way).

    Anyways, copy the following over the new PolicyConnectio n class you've created. Please refer to the comments for an explanation of what I'm doing and why.

    Code:
    Imports System.Net
    Imports System.Net.Sockets
    Imports System.Threading
    Imports System.IO
    
    
    ''' <summary>
    ''' Manages the state of a single connection from a client
    ''' </summary>
    ''' <remarks></remarks>
    Public Class PolicyConnection
        Private _connection As Socket 'the connnection to the client
        Private Const _policyRequestString = "<policy-file-request/>" 'the request we're expecting from the client
        Private _expectedPolicyRequestLength As Integer = _policyRequestString.Length
        Private _buffer(_expectedPolicyRequestLength) As Byte 'used to retrieve the request from the client
        Private _received As Integer 'the amount of information recieved so far
        'Private _policy() As Byte 'contains the policy that is returned to the client
    
    
        Dim appExecutionPath As String = System.Reflection.Assembly.GetEntryAssembly().Location
        Dim path As String = appExecutionPath.Remove(appExecutionPath.LastIndexOf("\"), appExecutionPath.Length - appExecutionPath.LastIndexOf("\"))
        Private _pathToPolicy As String = path + "\SocketClientAccessPolicy.xml" 'the path to the policy file
    
        ''' <summary>
        ''' Initializes a new instance of the PolicyConnection.
        ''' </summary>
        ''' <param name="client">The client that the connection is associated with</param>
        ''' <remarks></remarks>
        Public Sub New(ByVal client As Socket)
            _connection = client
    
            'Loading the policy file into memory
            'Using fs As FileStream = New FileStream(_pathToPolicy, FileMode.Open)
            '    ReDim _policy(fs.Length)
            '    fs.Read(_policy, 0, _policy.Length)
            'End Using
            ReDim _buffer(_expectedPolicyRequestLength)
    
            'Trying to retrieve the request form the client
            Try
                Console.WriteLine()
                Console.WriteLine("Receiving request....")
                _connection.BeginReceive(_buffer, 0, _expectedPolicyRequestLength, SocketFlags.None, New AsyncCallback(AddressOf OnReceive), Nothing)
            Catch ex As SocketException
                Console.WriteLine("An error occurred while receiving the request: ")
                Console.WriteLine(ex.Message)
            End Try
    
        End Sub
    
        ''' <summary>
        ''' Called when receiving data from the client
        ''' </summary>
        ''' <param name="res">Stores state information and any user defined data for this asynchronous operation</param>
        ''' <remarks></remarks>
        Private Sub OnReceive(ByVal res As IAsyncResult)
            Try
                _received += _connection.EndReceive(res)
    
                If _received < _expectedPolicyRequestLength Then
                    'haven't received the full request yet: receive again.
                    _connection.BeginReceive(_buffer, _received, _expectedPolicyRequestLength - _received, SocketFlags.None, New AsyncCallback(AddressOf OnReceive), Nothing)
                    Return
                End If
    
                Console.WriteLine("Request received.")
    
                Dim clientRequest As String = System.Text.Encoding.UTF8.GetString(_buffer, 0, _received)
                If String.Compare(clientRequest, _policyRequestString, True) <> 0 Then
                    'the request is not what we're expecting
                    Console.WriteLine("Request was not for a policy. Closing connection to client.")
                    _connection.Close()
                    Exit Sub
                End If
    
                'sending the policy
                Console.WriteLine("Sending policy...")
                '_connection.BeginSend(_policy, 0, _policy.Length, SocketFlags.None, New AsyncCallback(AddressOf OnSendCompleted), Nothing)
                _connection.BeginSendFile(_pathToPolicy, New AsyncCallback(AddressOf OnSendCompleted), Nothing)
            Catch ex As SocketException
                Console.WriteLine("An error occurred while receiving the policy request:")
                Console.WriteLine(ex.Message)
                _connection.Close()
            End Try
        End Sub
    
        ''' <summary>
        ''' Called after sending the policy
        ''' </summary>
        ''' <param name="res">Stores state information and any user defined data for this asynchronous operation</param>
        ''' <remarks></remarks>
        Private Sub OnSendCompleted(ByVal res As IAsyncResult)
            Try
                Console.WriteLine("Policy sent to client.")
                '_connection.EndSend(res)
                _connection.EndSendFile(res)
            Catch ex As SocketException
                _connection.Close()
                Console.WriteLine("An error occurred while finishing the send policy:")
                Console.WriteLine(ex.Message)
            Finally
                _connection.Close()
            End Try
        End Sub
    End Class
    Now that we have a class to manage a single socket connection, add a new class to the project and name it "PolicyServ er". This class is going to encapsulate the the Server's responsibilitie s. It's going listen for new Socket connections and pass the responsibility of sending the policy file off to the PolicyConnectio n class we've just created.

    Copy the following code over your new class. Again, please refer to the comments in the code for an explanation of what I'm doing and why.
    Code:
    Imports System.Net
    Imports System.Net.Sockets
    Imports System.Threading
    
    Public Class PolicyServer
        Private _listener As Socket
        Private _endpoint As IPEndPoint
    
        ''' <summary>
        ''' Creates a new instance of the PolicyServer class.
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub New()
            _endpoint = New IPEndPoint(IPAddress.Any, 943)
            'Create the Listening Socket
            _listener = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
    
            _listener.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, 0)
    
            _listener.Bind(_endpoint)
            _listener.Listen(10)
            Console.WriteLine("Listening for client request")
            _listener.BeginAccept(New AsyncCallback(AddressOf OnConnection), Nothing)
        End Sub
    
        ''' <summary>
        ''' Called when a connection is received from a client.
        ''' </summary>
        ''' <param name="res">Stores state information and any user defined data for this asynchronous operation</param>
        ''' <remarks></remarks>
        Public Sub OnConnection(ByVal res As IAsyncResult)
            'Retrieving the client that made the reuest
            Dim client As Socket = Nothing
            Try
                client = _listener.EndAccept(res)
            Catch ex As SocketException
                Console.WriteLine("An error occurred while retrieving the client:")
                Console.WriteLine(ex.Message)
                Exit Sub
            End Try
    
            'Handle the policy request using the PolicyConnection class.
            Dim pc As New PolicyConnection(client)
    
            'look for more connections.
            _listener.BeginAccept(New AsyncCallback(AddressOf OnConnection), Nothing)
        End Sub
    
        Public Sub Close()
            Try
                _listener.Close()
            Catch ex As SocketException
                Console.WriteLine("An error occurred while closing the listener:")
                Console.WriteLine(ex.Message)
            End Try
        End Sub
    End Class

    Ok, now add a new XML file to your project named "SocketClientAc cessPolicy.xml" . This file will be the policy file sent to the client that allows it to establish a connection with a Socket Server.

    When you run this application the policy file that must exist in the same directory as the executing console application in order for the PolicyConnectio n class to find it. This means that you may have to manually copy the file to the Debug Folder when using Visual Studio to run this application.

    This policy file indicates that the Socket is allowed to connect to a Socket Server listening on port 4509. (Remember that the policy server is listening on port 943.)
    Code:
    <?xml version="1.0" encoding="utf-8" ?>
    <access-policy>
      <cross-domain-access>
        <policy>
          <allow-from>
            <domain uri="*" />
          </allow-from>
          <grant-to>
            <!--valid port range: 4502-4534-->
            <socket-resource port="4509" protocol="tcp" />
          </grant-to>
        </policy>
      </cross-domain-access>
    </access-policy>
    Ok, now we have all the components to get this to run.

    All you have to do is edit the console application's Main() method so that it instantiates an instance of the Policy Server (which starts the server) and then puts the thread to sleep while the server manages policy requests.
    Code:
    Imports System.Threading
    Module Module1
        Sub Main()
            Console.WriteLine("Picture Pusher Server Starting")
            Dim ps As New PusherServer
            System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite)
        End Sub
    End Module
    Cheers!

    -Frinny


    (Remember:

    Update this so that it properly implements the IDisposable interface to clean up Socket Connection resources

    Remove the stuff that's mysteriously not working.

    )
Working...