|
11 | 11 | import java.io.IOException; |
12 | 12 | import java.net.*; |
13 | 13 |
|
| 14 | +import java.util.Enumeration; |
| 15 | +import org.apache.http.conn.util.*; |
| 16 | + |
14 | 17 | /** |
15 | 18 | * Created by arm on 4/11/15. |
16 | 19 | */ |
17 | 20 | @Component |
18 | 21 | public class UpnpListener { |
19 | | - private Logger log = Logger.getLogger(UpnpListener.class); |
20 | | - private static final int UPNP_DISCOVERY_PORT = 1900; |
21 | | - private static final String UPNP_MULTICAST_ADDRESS = "239.255.255.250"; |
22 | | - |
23 | | - @Value("${upnp.response.port}") |
24 | | - private int upnpResponsePort; |
25 | | - |
26 | | - @Value("${server.port}") |
27 | | - private int httpServerPort; |
28 | | - |
29 | | - @Value("${upnp.config.address}") |
30 | | - private String responseAddress; |
31 | | - |
32 | | - @Autowired |
33 | | - private ApplicationContext applicationContext; |
34 | | - |
35 | | - @Scheduled(fixedDelay = Integer.MAX_VALUE) |
36 | | - public void startListening(){ |
37 | | - log.info("Starting UPNP Discovery Listener"); |
38 | | - |
39 | | - try (DatagramSocket responseSocket = new DatagramSocket(upnpResponsePort); |
40 | | - MulticastSocket upnpMulticastSocket = new MulticastSocket(UPNP_DISCOVERY_PORT)) { |
41 | | - |
42 | | - InetAddress upnpGroupAddress = InetAddress.getByName(UPNP_MULTICAST_ADDRESS); |
43 | | - upnpMulticastSocket.joinGroup(upnpGroupAddress); |
44 | | - |
45 | | - while(true){ //trigger shutdown here |
46 | | - byte[] buf = new byte[1024]; |
47 | | - DatagramPacket packet = new DatagramPacket(buf, buf.length); |
48 | | - upnpMulticastSocket.receive(packet); |
49 | | - String packetString = new String(packet.getData()); |
50 | | - if(isSSDPDiscovery(packetString)){ |
51 | | - log.debug("Got SSDP Discovery packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort()); |
52 | | - sendUpnpResponse(responseSocket, packet.getAddress(), packet.getPort()); |
53 | | - } |
54 | | - } |
55 | | - } catch (IOException e) { |
56 | | - log.error("UpnpListener encountered an error. Shutting down", e); |
57 | | - ConfigurableApplicationContext context = (ConfigurableApplicationContext) UpnpListener.this.applicationContext; |
58 | | - context.close(); |
59 | | - |
60 | | - } |
61 | | - log.info("UPNP Discovery Listener Stopped"); |
62 | | - |
63 | | - } |
64 | | - |
65 | | - /** |
66 | | - * very naive ssdp discovery packet detection |
67 | | - * @param body |
68 | | - * @return |
69 | | - */ |
70 | | - protected boolean isSSDPDiscovery(String body){ |
71 | | - if(body != null && body.startsWith("M-SEARCH * HTTP/1.1") && body.contains("MAN: \"ssdp:discover\"")){ |
72 | | - return true; |
73 | | - } |
74 | | - return false; |
75 | | - } |
76 | | - |
77 | | - String discoveryTemplate = "HTTP/1.1 200 OK\r\n" + |
78 | | - "CACHE-CONTROL: max-age=86400\r\n" + |
79 | | - "EXT:\r\n" + |
80 | | - "LOCATION: http://%s:%s/upnp/amazon-ha-bridge/setup.xml\r\n" + |
81 | | - "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" + |
82 | | - "01-NLS: %s\r\n" + |
83 | | - "ST: urn:Belkin:device:**\r\n" + |
84 | | - "USN: uuid:Socket-1_0-221438K0100073::urn:Belkin:device:**\r\n\r\n"; |
85 | | - protected void sendUpnpResponse(DatagramSocket socket, InetAddress requester, int sourcePort) throws IOException { |
86 | | - String discoveryResponse = String.format(discoveryTemplate, responseAddress, httpServerPort, getRandomUUIDString()); |
87 | | - DatagramPacket response = new DatagramPacket(discoveryResponse.getBytes(), discoveryResponse.length(), requester, sourcePort); |
88 | | - socket.send(response); |
89 | | - } |
90 | | - |
91 | | - protected String getRandomUUIDString(){ |
92 | | - return "88f6698f-2c83-4393-bd03-cd54a9f8595"; // https://xkcd.com/221/ |
93 | | - } |
| 22 | + private Logger log = Logger.getLogger(UpnpListener.class); |
| 23 | + private static final int UPNP_DISCOVERY_PORT = 1900; |
| 24 | + private static final String UPNP_MULTICAST_ADDRESS = "239.255.255.250"; |
| 25 | + |
| 26 | + @Value("${upnp.response.port}") |
| 27 | + private int upnpResponsePort; |
| 28 | + |
| 29 | + @Value("${server.port}") |
| 30 | + private int httpServerPort; |
| 31 | + |
| 32 | + @Value("${upnp.config.address}") |
| 33 | + private String responseAddress; |
| 34 | + |
| 35 | + @Autowired |
| 36 | + private ApplicationContext applicationContext; |
| 37 | + |
| 38 | + @Scheduled(fixedDelay = Integer.MAX_VALUE) |
| 39 | + public void startListening(){ |
| 40 | + log.info("Starting UPNP Discovery Listener"); |
| 41 | + |
| 42 | + try (DatagramSocket responseSocket = new DatagramSocket(upnpResponsePort); |
| 43 | + MulticastSocket upnpMulticastSocket = new MulticastSocket(UPNP_DISCOVERY_PORT);) { |
| 44 | + InetSocketAddress socketAddress = new InetSocketAddress(UPNP_MULTICAST_ADDRESS, UPNP_DISCOVERY_PORT); |
| 45 | + Enumeration<NetworkInterface> ifs = NetworkInterface.getNetworkInterfaces(); |
| 46 | + |
| 47 | + while (ifs.hasMoreElements()) { |
| 48 | + NetworkInterface xface = ifs.nextElement(); |
| 49 | + Enumeration<InetAddress> addrs = xface.getInetAddresses(); |
| 50 | + String name = xface.getName(); |
| 51 | + int IPsPerNic = 0; |
| 52 | + |
| 53 | + while (addrs.hasMoreElements()) { |
| 54 | + InetAddress addr = addrs.nextElement(); |
| 55 | + log.debug(name + " ... has addr " + addr); |
| 56 | + if (InetAddressUtils.isIPv4Address(addr.getHostAddress())) { |
| 57 | + IPsPerNic++; |
| 58 | + } |
| 59 | + } |
| 60 | + log.debug("Checking " + name + " to our interface set"); |
| 61 | + if (IPsPerNic > 0) { |
| 62 | + upnpMulticastSocket.joinGroup(socketAddress, xface); |
| 63 | + log.debug("Adding " + name + " to our interface set"); |
| 64 | + } |
| 65 | + } |
| 66 | + |
| 67 | + while(true){ //trigger shutdown here |
| 68 | + byte[] buf = new byte[1024]; |
| 69 | + DatagramPacket packet = new DatagramPacket(buf, buf.length); |
| 70 | + upnpMulticastSocket.receive(packet); |
| 71 | + String packetString = new String(packet.getData()); |
| 72 | + if(isSSDPDiscovery(packetString)){ |
| 73 | + log.debug("Got SSDP Discovery packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort()); |
| 74 | + sendUpnpResponse(responseSocket, packet.getAddress(), packet.getPort()); |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + } catch (IOException e) { |
| 79 | + log.error("UpnpListener encountered an error. Shutting down", e); |
| 80 | + ConfigurableApplicationContext context = (ConfigurableApplicationContext) UpnpListener.this.applicationContext; |
| 81 | + context.close(); |
| 82 | + |
| 83 | + } |
| 84 | + log.info("UPNP Discovery Listener Stopped"); |
| 85 | + |
| 86 | + } |
| 87 | + |
| 88 | + /** |
| 89 | + * very naive ssdp discovery packet detection |
| 90 | + * @param body |
| 91 | + * @return |
| 92 | + */ |
| 93 | + protected boolean isSSDPDiscovery(String body){ |
| 94 | + if(body != null && body.startsWith("M-SEARCH * HTTP/1.1") && body.contains("MAN: \"ssdp:discover\"")){ |
| 95 | + return true; |
| 96 | + } |
| 97 | + return false; |
| 98 | + } |
| 99 | + |
| 100 | + String discoveryTemplate = "HTTP/1.1 200 OK\r\n" + |
| 101 | + "CACHE-CONTROL: max-age=86400\r\n" + |
| 102 | + "EXT:\r\n" + |
| 103 | + "LOCATION: http://%s:%s/upnp/amazon-ha-bridge/setup.xml\r\n" + |
| 104 | + "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" + |
| 105 | + "01-NLS: %s\r\n" + |
| 106 | + "ST: urn:Belkin:device:**\r\n" + |
| 107 | + "USN: uuid:Socket-1_0-221438K0100073::urn:Belkin:device:**\r\n\r\n"; |
| 108 | + protected void sendUpnpResponse(DatagramSocket socket, InetAddress requester, int sourcePort) throws IOException { |
| 109 | + String discoveryResponse = String.format(discoveryTemplate, responseAddress, httpServerPort, getRandomUUIDString()); |
| 110 | + DatagramPacket response = new DatagramPacket(discoveryResponse.getBytes(), discoveryResponse.length(), requester, sourcePort); |
| 111 | + socket.send(response); |
| 112 | + } |
| 113 | + |
| 114 | + protected String getRandomUUIDString(){ |
| 115 | + return "88f6698f-2c83-4393-bd03-cd54a9f8595"; // https://xkcd.com/221/ |
| 116 | + } |
94 | 117 | } |
0 commit comments