javaandroidudpssdp

Unable to receive proper UDP packets using SSDP


I'm trying to implement a very simple SSDP functionality into my android app taken from here.

My application sends some UDP packets containing a relevant M-SEARCH message to the broadcasting address without any issue. The problem is, I should be getting a proper response back from other devices that's running a UPNP server. For some reason, I'm only receiving the exact same packets back I sent from my android device.

MainActivity.java

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        WifiManager wm = (WifiManager)getSystemService(Context.WIFI_SERVICE);
        WifiManager.MulticastLock multicastLock = wm.createMulticastLock("multicastLock"); 
        multicastLock.setReferenceCounted(true);
        multicastLock.acquire();

        setContentView(R.layout.activity_main);

        ((Button)this.findViewById(R.id.btnSendSSDPSearch)).setOnClickListener(this);
    }

@Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.btnSendSSDPSearch:
            new Thread(new Runnable() {

                @Override
                public void run() {
                    SendMSearchMessage();
                }
            }).start();

        default:
            break;
        }
    }

private void SendMSearchMessage() {

                SSDPSearchMsg searchContentDirectory = new SSDPSearchMsg(SSDPConstants.ST_ContentDirectory);
    SSDPSearchMsg searchAVTransport = new SSDPSearchMsg(SSDPConstants.ST_AVTransport);
    SSDPSearchMsg searchProduct = new SSDPSearchMsg(SSDPConstants.ST_Product);

    SSDPSocket sock;
    try {
        sock = new SSDPSocket();
        for (int i = 0; i < 2; i++) { 
            sock.send(searchContentDirectory.toString());
            sock.send(searchAVTransport.toString());
            sock.send(searchProduct.toString());
        }

      while (true) {
            DatagramPacket dp = sock.receive(); //Here, I only receive the same packets I initially sent above
            String c = new String(dp.getData());
            System.out.println(c); 
        }
    } catch (IOException e) {
        // TODO Auto-generated catch block
        Log.e("M-SEARCH", e.getMessage());

    }

SSDPSocket.java where the UDP packet transmission is actually done

public class SSDPSocket {
    SocketAddress mSSDPMulticastGroup;
    MulticastSocket mSSDPSocket;
    InetAddress broadcastAddress;

    public SSDPSocket() throws IOException {
        mSSDPSocket = new MulticastSocket(55325); //Bind some random port for receiving datagram
        broadcastAddress  = InetAddress.getByName(SSDPConstants.ADDRESS);
        mSSDPSocket.joinGroup(broadcastAddress);
    }

    /* Used to send SSDP packet */
    public void send(String data) throws IOException {
        DatagramPacket dp = new DatagramPacket(data.getBytes(), data.length(),
                broadcastAddress,SSDPConstants.PORT);

        mSSDPSocket.send(dp);
    }

    /* Used to receive SSDP packet */
    public DatagramPacket receive() throws IOException {
        byte[] buf = new byte[1024];
        DatagramPacket dp = new DatagramPacket(buf, buf.length);

        mSSDPSocket.receive(dp);

        return dp;
    }

    public void close() {
        if (mSSDPSocket != null) {
            mSSDPSocket.close();
        }
    }
}

SSDPSearchMsg.java for constructing SSDP Broadcast string (Probably unrelated to the problem I'm experiencing but just in case)

public class SSDPSearchMsg {
    static final String HOST = "Host:" + SSDP.ADDRESS + ":" + SSDP.PORT;
    static final String MAN = "Man:ssdp:discover";
    static final String NEWLINE = System.getProperty("line.separator");

    int mMX = 3;    /* seconds to delay response */
    String mST;     /* Search target */

    public SSDPSearchMsg(String ST) {
        mST = ST;
    }

    public int getmMX() {
        return mMX;
    }

    public void setmMX(int mMX) {
        this.mMX = mMX;
    }

    public String getmST() {
        return mST;
    }

    public void setmST(String mST) {
        this.mST = mST;
    }

    @Override
    public String toString() {
        StringBuilder content = new StringBuilder();

        content.append(SSDP.SL_MSEARCH).append(NEWLINE);
        content.append(HOST).append(NEWLINE);
        content.append(MAN).append(NEWLINE);
        content.append(mST).append(NEWLINE);
        content.append("MX:" + mMX).append(NEWLINE);
        content.append(NEWLINE);

        return content.toString();
    }
}

SSDPConstants.java

public class SSDPConstants {
    /* New line definition */
    public static final String NEWLINE = "\r\n";

    public static final String ADDRESS = "239.255.255.250";
    public static final int PORT = 1900;

    /* Definitions of start line */
    public static final String SL_NOTIFY = "NOTIFY * HTTP/1.1";
    public static final String SL_MSEARCH = "M-SEARCH * HTTP/1.1";
    public static final String SL_OK = "HTTP/1.1 200 OK";

    /* Definitions of search targets */
    public static final String ST_RootDevice = "St: rootdevice";
    public static final String ST_ContentDirectory = "St: urn:schemas-upnp-org:service:ContentDirectory:1";
    public static final String ST_AVTransport = "St: urn:schemas-upnp-org:service:AVTransport:1";
    public static final String ST_Product = "St: urn:av-openhome-org:service:Product:1";

    /* Definitions of notification sub type */
    public static final String NTS_ALIVE = "NTS:ssdp:alive";
    public static final String NTS_BYE = "NTS:ssdp:byebye";
    public static final String NTS_UPDATE = "NTS:ssdp:update";
}

I also made sure that the manifest includes relevant permissions:

 <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

I'm testing the app on an actual device, not on emulator.

Any assistance would be appreciated.

Edit upon comment:

Multicast itself should work without issue. I've downloaded an app called BubbleUPNP to test the SSDP functionality. Sure enough, wireshark properly captures all messages sent from the phone to the broadcasting address in SSDP Protocol:

M-SEARCH * HTTP/1.1

Man: "ssdp:discover"

Mx: 3

Host: 239.255.255.250:1900

St: urn:schemas-upnp-org:service:AVTransport:1

And the response

HTTP/1.1 200 OK

ST:urn:schemas-upnp-org:service:ContentDirectory:1

USN:uuid:d5829e90-73ce-4213-9ad1-4e75dbdd0232::urn:schemas-upnp-org:service:ContentDirectory:1

Location:http://10.175.95.4:2869/upnphost/udhisapi.dll?content=uuid:d5829e90-73ce-4213-9ad1-4e75dbdd0232

OPT:"http://schemas.upnp.org/upnp/1/0/"; ns=01

01-NLS:05f3dd08b4b4b5aafa1fe983fa447f49

Cache-Control:max-age=900

Server:Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host/1.0

So yeah, this is without a doubt, an implementation issue. Nothing wrong with the device.


Solution

  • Weird. I fixed the issue but I'm genuinely not sure what made it work.

    Here are some changes that I made:

    Instead of assigning a fixed port, I made it dynamically allocate an available port.

    public class SSDPSocket {
        SocketAddress mSSDPMulticastGroup;
        MulticastSocket mSSDPSocket;
        InetAddress broadcastAddress;
    
        public SSDPSocket() throws IOException {
    
            mSSDPSocket = new MulticastSocket();
            broadcastAddress  = InetAddress.getByName(SSDPConstants.ADDRESS);
            mSSDPSocket.joinGroup(broadcastAddress);
        }
    ...
    }
    

    I also changed the M-Search message structure, including its order.

    public class SSDPSearchMsg {
        static final String HOST = "Host: " + SSDPConstants.ADDRESS + ":" + SSDPConstants.PORT;
        static final String MAN = "Man: \"ssdp:discover\"";
        static final String NEWLINE = "\r\n";
    
        int mMX = 3;    /* seconds to delay response */
        String mST;     /* Search target */
    
        public SSDPSearchMsg(String ST) {
            mST = ST;
        }
    
        public int getmMX() {
            return mMX;
        }
    
        public void setmMX(int mMX) {
            this.mMX = mMX;
        }
    
        public String getmST() {
            return mST;
        }
    
        public void setmST(String mST) {
            this.mST = mST;
        }
    
        @Override
        public String toString() {
            StringBuilder content = new StringBuilder();
    
            content.append(SSDPConstants.SL_MSEARCH).append(NEWLINE);
            content.append(MAN).append(NEWLINE);
            content.append("Mx: " + mMX).append(NEWLINE);
            content.append(HOST).append(NEWLINE);
            content.append(mST).append(NEWLINE);
            content.append(NEWLINE);
    
            return content.toString();
        }
    }
    

    And everything suddenly works. Why it works is beyond me. My previous implementation follows the SSDP protocol as far as I can tell.