-
Notifications
You must be signed in to change notification settings - Fork 7
/
MulticastServerThread.java
311 lines (264 loc) · 11.4 KB
/
MulticastServerThread.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
/**
* Copyright 2009 Marc Stogaitis and Mimi Sun
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gmote.server;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import org.gmote.common.MulticastClient;
import org.gmote.common.Protocol.UdpPacketTypes;
import org.gmote.server.settings.PreferredPorts;
/**
* Listens on a Multicast port and returns the name and ip of this host.
* @author Marc
*
*/
public class MulticastServerThread implements Runnable {
final private static Logger LOGGER = Logger.getLogger(MulticastServerThread.class.getName());
public static final int MULTICAST_LISTENING_PORT = 9901;
public static final String GROUP_NAME = "230.0.0.1";
// The port used to exchange udp data. This is where we exchange data such as
// mouse packets. It can be the same as the udp service discovery port, or
// different.
private static int serverUdpListeningPort = MULTICAST_LISTENING_PORT;
private String groupName;
// The listening port of the current socket.
private int socketListeningPort;
private static InetAddress connectedClientIp = null;
/**
* Creates a thread that will send out discovery notifications. We explicitly
* listen on each local interface since this resolves the following bug which
* is related to having multiple network cards:
* <p>
* 1. Listen on socket without providing a specific ip (by default, java will
* listen on all network interfaces)
* </p>
* <p>
* 2. Receive an IP request from a client
* </p>
* <p>
* 3. If we have multiple local ip's (ex: a wifi connection and wired
* connection), we won't know which ip to return (and will often return the
* wrong one if we simply call InetAddress.getLocalHost)
*
* Note: If we pass in null for 'localIpAddress', the we will revert back to
* listening on all local ip addresses and take a guess as to which ip we
* should return to the client. This mechanism should only be used if there is
* a problem listening on local interfaces.
* </p>
*
*/
public MulticastServerThread(String groupName, int socketListeningPort) {
this.groupName = groupName;
this.socketListeningPort = socketListeningPort;
}
public void run() {
MulticastSocket socket = join(groupName, socketListeningPort);
if (socket == null) {
String message = "Unable to join the multicast socket. Is the computer connected to a network? Exiting Gmote.";
LOGGER.severe(message);
JOptionPane.showMessageDialog(null, message);
System.exit(1);
}
byte[] inBuffer = new byte[500];
LOGGER.info("Listening for udp packets on " + socketListeningPort);
int errorCount = 0;
while (true) {
try {
receivePacket(socket, inBuffer);
} catch (Exception e) {
// Catching all exceptions here since this is the top level of our
// multicast thread and we never want it to die.
LOGGER.log(Level.SEVERE, e.getMessage(), e);
errorCount++;
if (errorCount >= 10) {
JOptionPane.showMessageDialog(null, "Too many errors in udp class. Please see the logs for more details or visit http://www.gmote.org/faq -- " + e.getMessage());
System.exit(1);
}
}
}
}
private MulticastSocket join(String groupName, int udpPort) {
try {
MulticastSocket msocket;
msocket = new MulticastSocket(udpPort);
if (groupName != null) {
InetAddress group = InetAddress.getByName(groupName);
msocket.joinGroup(group);
}
return msocket;
} catch (IOException e) {
LOGGER.log(Level.SEVERE,e.getMessage(),e);
}
return null;
}
public void receivePacket(MulticastSocket multicastSocket, byte[] inBuffer) {
try {
DatagramPacket packet = new DatagramPacket(inBuffer, inBuffer.length);
// Wait for packet
LOGGER.fine("Waiting for udp packet request on port " + multicastSocket.getLocalPort());
multicastSocket.receive(packet);
if (inBuffer[0] == UdpPacketTypes.SERVICE_DISCOVERY.getId()) {
handleServiceDiscoveryRequest(multicastSocket, packet, inBuffer);
} else if (inBuffer[0] == UdpPacketTypes.MOUSE_MOVE.getId()) {
handleMouseMoveRequest(packet, inBuffer);
} else {
LOGGER.info("Received unrecognized udp packet. Ignoring it.");
//handleLegacyServiceDiscoveryRequest(multicastSocket, packet);
}
} catch (IOException e) {
LOGGER.log(Level.SEVERE,e.getMessage(),e);
}
}
/**
* Handles a mouse move request from the client. In order to accept the mouse
* move event, the client must have an active tcp connection with our server
* (we verify this by matching the ip address).
*
* TODO(mstogaitis): Consider a better security mechanism such as signing the
* mouse packets with the password.
*
* @param packet
* @param data
* a 5 byte packet, byte 0 contains an identifier, bytes 1 and 2
* contain a 'short' describing the XMovement, and bytes 3 and 4
* contain a short describing the YMovement.
*/
private void handleMouseMoveRequest(DatagramPacket packet, byte[] data) {
InetAddress clientAddress = packet.getAddress();
if (isCorrectIp(clientAddress)) {
if (packet.getLength() == 5) {
short xDiff = data[2];
xDiff = (short) ((xDiff << 8) & 0xFF00);
xDiff = (short) (xDiff | (data[1] & 0x00FF));
short yDiff = data[4];
yDiff = (short) ((yDiff << 8 & 0xFF00));
yDiff = (short) (yDiff | (data[3] & 0x00FF));
TrackpadHandler.instance().handleMoveMouseCommand(xDiff, yDiff);
}
} else {
LOGGER
.warning("Received a mouse move request from an ip who is not connected to us: packetIp="
+ clientAddress + " tcpConnectionIp=" + connectedClientIp + ". Ignoring the packet.");
}
}
private static synchronized boolean isCorrectIp(InetAddress clientAddress) {
return clientAddress.equals(connectedClientIp);
}
public static synchronized void setConnectedClientIp(InetAddress clientIp) {
connectedClientIp = clientIp;
}
/**
* Sends a packet to the client identifying our server's name and ip.
*
* @param multicastSocket
* @param packet
* @param data
* Byte 0 is the packet id, bytes 1 to 4 is an int identifying the
* port of the client to which we should send a reply.
* @throws IOException
* @throws UnknownHostException
*/
private void handleServiceDiscoveryRequest(MulticastSocket multicastSocket,
DatagramPacket packet, byte data[]) throws UnknownHostException, IOException {
LOGGER.info("Received an ip request");
if (packet.getLength() == 5) {
int port = data[4] << (24 & 0xFF000000);
port = port | ((data[3] << 16) & 0x00FF0000);
port = port | ((data[2] << 8) & 0x0000FF00);
port = port | (data[1] & 0x000000FF);
sendDiscoveryReply(multicastSocket, packet, port);
}
}
/**
* In version <= 1.2 of the client, we did not use the convention of having
* the first byte of a udp packet identify the packet type. Therefore, for
* backwards compatibility, we'll verify this packet starts with 'gmoteping|'
* which was our original message identifier. If it doesn't we just ignore the
* message.
*
* @param multicastSocket
* @param packet
* @throws IOException
*/
/* private void handleLegacyServiceDiscoveryRequest(MulticastSocket multicastSocket,
DatagramPacket packet) throws IOException {
LOGGER.info("Received an ip request");
// TODO(mstogaitis): Investigate possible security issue here.
String messageRecieved = new String(packet.getData()).trim();
if (messageRecieved.startsWith(CLIENT_PING_PREFIX)) {
String port = messageRecieved.substring(CLIENT_PING_PREFIX.length());
// The client
int remotePort = Integer.parseInt(port);
sendDiscoveryReply(multicastSocket, packet, remotePort);
}
}*/
private void sendDiscoveryReply(MulticastSocket multicastSocket, DatagramPacket packet,
int remotePort) throws UnknownHostException, IOException {
// Reply with all local IPs
byte[] replyBuff;
List<InetAddress> addresses = ServerUtil.findAllLocalIpAddresses(true);
for (InetAddress address : addresses) {
if (!TcpConnectionHandler.instance().isListeningOnAddress(address)) {
LOGGER.warning("Multicase server thread noticed a local ip that we are not listening on. Adding it to the listening pool");
TcpConnectionHandler.instance().addConnectionListener(address);
}
Integer port = PreferredPorts.instance().getPreferredPort(address.getHostAddress());
if (port == null) {
LOGGER.severe("Prefered port is null for connection that should have been added. "
+ address.getHostAddress() + " " + PreferredPorts.instance().getPreferedPorts());
continue;
}
replyBuff = createIpReply(address.getHostAddress(), InetAddress.getLocalHost()
.getHostName(), port);
DatagramPacket replyPacket = new DatagramPacket(replyBuff, replyBuff.length);
replyPacket.setAddress(packet.getAddress());
replyPacket.setPort(remotePort);
LOGGER.info("Sending packet to: " + packet.getAddress());
multicastSocket.send(replyPacket);
}
}
/**
* Create an IP | Hostname reply packet to send to the clients. We can't just
* send the hostName since Android clients run under linux which has problems
* with windows host names.
* @param port
*
* @return
*/
private byte[] createIpReply(String localAddress, String hostName, int port) {
return (localAddress + MulticastClient.FIELD_SEPARATOR + hostName
+ MulticastClient.FIELD_SEPARATOR + port + MulticastClient.FIELD_SEPARATOR + serverUdpListeningPort).getBytes();
}
public static void listenForIpRequests(int udpPort) {
// Ports that the server is listening on.
serverUdpListeningPort = udpPort;
MulticastServerThread multiCon;
if (udpPort != MULTICAST_LISTENING_PORT) {
// The user has chosen to listen for mouse events on a different port than
// service discovery. Make sure we listen on that port as well.
multiCon = new MulticastServerThread(null, udpPort);
new Thread(multiCon, "UdpMouseThread").start();
}
multiCon = new MulticastServerThread(GROUP_NAME, MULTICAST_LISTENING_PORT);
new Thread(multiCon, "MulticastThread").start();
}
}