Question

I have an Eclipse plugin that needs to open a pair of sockets to a flash application running on the local machine. Flash requires a policy file (blob of XML) giving permissions to access the ports in question. Flash prefers to get this policy file over port 843, Java treats ports < 1024 as privileged ports and Mac OS X and Linux similarly restrict access to ports < 1024. I don't want to run my Eclipse plugin with root permissions, so serving up the policy file on port 843 is not an option. According to Adobe documentation, if Flash can't get the policy file on port 843, it falls back to requesting the policy file on the port to which it's trying to connect. The ActionScript code looks like this:

/**
  * Connecting to some port to communicate with the debugger. We initiate the
  * connection because Flex doesn't allow us to listen to any ports.
  */
private function initSockets():void
{
    requestSocket = new Socket();
    requestSocket.addEventListener(Event.CONNECT, requestConnected);
    requestSocket.addEventListener(Event.CLOSE, closed);
    requestSocket.addEventListener(ProgressEvent.SOCKET_DATA, processRequestData);
    requestSocket.addEventListener(IOErrorEvent.IO_ERROR, ioError);
    requestSocket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityError);
    requestSocket.connect("localhost", SCConstants.DEBUG_LESSON_REQUEST_PORT);

    eventSocket = new Socket();
    eventSocket.addEventListener(Event.CONNECT, eventConnected);
    eventSocket.addEventListener(Event.CLOSE, closed);
    eventSocket.addEventListener(ProgressEvent.SOCKET_DATA, processEventData);
    eventSocket.addEventListener(IOErrorEvent.IO_ERROR, ioError);
    eventSocket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityError);
    eventSocket.connect("localhost", SCConstants.DEBUG_LESSON_EVENT_PORT);
}

On the Eclipse plugin side I've inherited some code that works most of the time on OS X, but sometimes fails on Windows. Running on Wi-Fi rather than wired ethernet also tends to fail, although I have no idea why this should matter.

public Boolean connect() throws DebugException {
    try {
        try {
            // connection code
            fRequestServerSocket = new ServerSocket(requestPort);
            fRequestServerSocket.setSoTimeout(ACCEPT_TIMEOUT);
            fEventServerSocket = new ServerSocket(eventPort);
            fEventServerSocket.setSoTimeout(ACCEPT_TIMEOUT);

            TWBLogger.logInfo("Open socket request server:" + fRequestServerSocket);
            TWBLogger.logInfo("Open socket event server:" + fEventServerSocket);

            String policy = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<cross-domain-policy>\n" +
                "<allow-access-from domain=\"*\" to-ports=\"5000,5001\" secure=\"false\" />\n" +
                "</cross-domain-policy>\0";

            // Because of the Flash security policy the first thing
            // that will accept on the socket will be the Flash Player 
            // trying to verify us. The Flash player will request security 
            // policy file with the following string: <policy-file-request/>\0
            // We will serve back the above policy file and then close the socket
            // The next thing to accept is our process in the VM.
            fRequestSocket = fRequestServerSocket.accept();

            fRequestWriter = new PrintWriter(fRequestSocket.getOutputStream());
            fRequestReader = new BufferedReader(new InputStreamReader(fRequestSocket.getInputStream()));

            // Wait some time before giving flash the policy file. Otherwise they don't get it. ;(
            // 3 is too much ... ;(
            Thread.sleep(100);

            fRequestWriter.print(policy);
            fRequestWriter.flush();
            fRequestSocket.close();

            // this should be the real connection
            fRequestSocket = fRequestServerSocket.accept();
            TWBLogger.logInfo("Open socket request:" + fRequestSocket);

            fRequestWriter = new PrintWriter(fRequestSocket.getOutputStream());
            fRequestReader = new BufferedReader(new InputStreamReader(fRequestSocket.getInputStream()));

            // the same situation for the EventSocket 
            fEventSocket = fEventServerSocket.accept();
            fEventReader = new BufferedReader(new InputStreamReader(fEventSocket.getInputStream()));
            TWBLogger.logInfo("Open socket event:" + fEventSocket);
        } catch (SocketTimeoutException e) {
            TWBLogger.logWaring("Connection to the Client Timed out.");
            cleanSockets();
            return false;
            requestFailed("Connection to the VM timed out. Please close any other running lessons that you debug and try again", e);
        } catch (SocketSecurityException e) {
            requestFailed("Security error occured when connecting to the VM", e);
        } catch (Exception e) {
            if (!fTerminated)
            requestFailed("Error occured when connecting to the VM. Please close any other running lessons that you debug.", e);
        } 
    } catch (DebugException e) {
        // close the sockets so that we can debug another application
        cleanSockets();
        throw e;
    }

    // our VM is single threaded
    fThread = new TWBThread(this);
    fThreads = new IThread[] {fThread};

    // start listening for events from the VM
    fEventDispatch = new EventDispatchJob();
    fEventDispatch.schedule();

    // start listening for breakpoints
    IBreakpointManager breakpointManager = getBreakpointManager();
    breakpointManager.addBreakpointListener(this);

    breakpointManager.addBreakpointManagerListener(this);
    return true;
}

This code looks wrong. It doesn't wait for the message from Flash and instead just jams the policy response into the port. As I said, it works most of the time, but it fails sometimes and doesn't seem to comply with Adobe's documentation.

I tried listening for request packets on each port and sending a port specific response. I watched socket traffic using WireShark on the loopback interface (Mac OS X). I saw policy requests coming in and responses getting sent, but Flash still gave me Security Sandbox Violation on both ports.

I also tried adding this line at the beginning of initSockets shown above:

Security.loadPolicyFile("xmlsocket://localhost:5002");

Then I added code in my plugin to listen on port 5002 and send the following master policy file content:

private final static String FLASH_POLICY_RESPONSE = 
    "<?xml version=\"1.0\"?>\n" +
    "<!DOCTYPE cross-domain-policy SYSTEM \"/xml/dtds/cross-domain-policy.dtd\">\n" +
    "<cross-domain-policy>\n" +
    "<site-control permitted-cross-domain-policies=\"master-only\"/>\n" +
    "<allow-access-from domain=\"*\" to-ports=\"5000,5001\"/>\n" +
    "</cross-domain-policy>\0";         

Again I saw the request come in and the response go out, but Flash didn't seem to respond to it. I didn't get the Security Sandbox Violation errors, but there was also no traffic over the ports.

Can anyone enlighten me on the correct approach to opening sockets between Java and Flash?

Was it helpful?

Solution

I found the solution to this. I made a mistake early on and used BufferedReader.readLine to read the policy request. This isn't appropriate since policy requests are null terminated, not new line terminated. This was confusing since it does return when the underlying stream closes. Thus I got the request and sent a response, but the response was sent after the ActionScript code had already decided that the request had failed.

On the Java side I used the following code to establish communication on the ports:

// Create server sockets.
fRequestServerSocket = new ServerSocket(REQUEST_PORT);
fRequestServerSocket.setSoTimeout(ACCEPT_TIMEOUT);
TWBLogger.logInfo("Open socket request server:" + fRequestServerSocket);

fEventServerSocket = new ServerSocket(EVENT_PORT);
fEventServerSocket.setSoTimeout(ACCEPT_TIMEOUT);
TWBLogger.logInfo("Open socket event server:" + fEventServerSocket);

// Serve up the Flash policy file.
serveFlashPolicy();

// Connect request socket.
fRequestSocket = fRequestServerSocket.accept();
TWBLogger.logInfo("Open socket request:" + fRequestSocket);

fRequestWriter = new PrintWriter(fRequestSocket.getOutputStream());
fRequestReader = new BufferedReader(new InputStreamReader(fRequestSocket.getInputStream()));

// Connect event socket.
fEventSocket = fEventServerSocket.accept();
TWBLogger.logInfo("Open socket event:" + fEventSocket);

fEventReader = new BufferedReader(new InputStreamReader(fEventSocket.getInputStream()));                

Serving up the policy file is handled as follows:

private void serveFlashPolicy() {
    ServerSocket serverSocket = null;
    Socket socket = null;
    TWBLogger.logInfo("Waiting for flash policy request on port " + FLASH_POLICY_PORT);
    try {
        serverSocket = new ServerSocket(FLASH_POLICY_PORT);
        serverSocket.setSoTimeout(ACCEPT_TIMEOUT);

        socket = serverSocket.accept();

        PrintWriter writer = new PrintWriter(socket.getOutputStream());
        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

        StringBuilder request = new StringBuilder();
        int c;
        while (0 < (c = reader.read())) {
            request.append((char) c);
        }

        String policyRequest = request.toString();
        if (policyRequest.startsWith(FLASH_POLICY_REQUEST)) {
            writer.print(FLASH_POLICY_RESPONSE);
            writer.print("\0");
            writer.flush();
        }
    } catch (IOException e) {
        TWBLogger.logWaring("IOException on port " + FLASH_POLICY_PORT + ": " + e.toString());
        e.printStackTrace();
    } finally {
        if (null != socket) {
            try {
                socket.close();
            } catch (Exception e) {
                // Ignore
            }
        }

        if (null != serverSocket) {
            try {
                serverSocket.close();
            } catch (Exception e) {
                // Ignore
            }
        }
    }

    TWBLogger.logInfo("Flash policy complete on port " + FLASH_POLICY_PORT);
}

The Flash policy response looks like this:

private final static String FLASH_POLICY_RESPONSE = 
    "<?xml version=\"1.0\"?>\n" +
    "<!DOCTYPE cross-domain-policy SYSTEM \"/xml/dtds/cross-domain-policy.dtd\">\n" +
    "<cross-domain-policy>\n" +
    "<allow-access-from domain=\"*\" to-ports=\"5000,5001\"/>\n" +
    "</cross-domain-policy>";           

The site-control tag I had previously been sending is only allowed in master policy files served from port 843.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top