-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRouter.java
More file actions
358 lines (309 loc) · 13.3 KB
/
Router.java
File metadata and controls
358 lines (309 loc) · 13.3 KB
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
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
import java.net.DatagramSocket;
import java.io.IOException;
import java.net.DatagramPacket;
import java.util.ArrayList;
import java.util.Timer;
import tcdIO.*;
/**
*
* Router class
*
* In the project instructions, Routers were described as having 2 our more sockets.
* The node class that I am using (from Assignment 1) is built around the assumption
* that there is one socket, but it would be very time-consuming to make the changes
* to that class to implement more than one socket variable for this given router,
* which would ultimately have the same result in my program as just using a single
* socket that deals with all the communication. I understand the theory behind having
* multiple sockets, but as a router always creates a SNDContent from a received packet
* in order to check its destination, then I can do a simple check to see if the destination
* matches the host that is connected to this router, in which case it will send it to
* that host, or else it will check its flow chart to see which router it should forward
* the packet to. This is effectively the same process as having multiple sockets.
*
*/
public class Router extends Node {
static int thisRouterSRCPort;
static final String DEFAULT_DST_NODE = "localhost";
static ArrayList<SNDContent> waitingToSend = new ArrayList<SNDContent>();
static RouterFlowTable routerFlowTable;
String flowRequestInformationToSendToController;
String routerName;
// For keeping track of the controller, host and all the routers directly connected to this router
static ArrayList<NodeData> connectedRouters = new ArrayList<NodeData>();
NodeData connectedController;
NodeData connectedHost;
boolean setupComplete;
boolean hostReturnedHello;
static Terminal terminal;
Router(Terminal terminal, String[] routerArguments, int controllerPort) {
try{
Router.terminal = terminal;
setupComplete = false;
hostReturnedHello = false;
// The parameters of this router are the arguments included when running this program, in the form
// of a string array.
routerName = routerArguments[0];
thisRouterSRCPort = Integer.parseInt(routerArguments[1]);
socket = new DatagramSocket(thisRouterSRCPort);
routerFlowTable = new RouterFlowTable();
// Create a node data for the controller
connectedController = new NodeData(socket, controllerPort, "CONTROLLER", "CONTROLLER", 0);
// We will start a string called informationToSendToController, which will consist of
// the routers name, the name of the host connected to it, then a list of connected
// routers and their distance to this router. We will continuously add to this
// string over the course of this method.
flowRequestInformationToSendToController = routerName;
// Register the connected router as a NodeData. If the router is not connected to a host,
// then this value remains null
if(!(routerArguments[2].equals("00")))
connectedHost = new NodeData(socket, Integer.parseInt(routerArguments[3]), "HOST", routerArguments[2], 0);
flowRequestInformationToSendToController += routerArguments[2]; // May be a Hosts name, or "00" if no host connected
// We need to register each connected router as a NodeData. This is done by iterating through
// the arguments array. In our arguments, the the connected routers are written as
// [router1name][router1port][router1distance][router2name][router2port][router2distance]...
// So this for loop creates Node data under the assumption that the arguments are in this form.
for(int i = 4; i < routerArguments.length; i = i + 3)
{
String connectedRouterName = routerArguments[i];
int connectedRouterSocket = Integer.parseInt(routerArguments[i+1]);
int connectedRouterDistance = Integer.parseInt(routerArguments[i+2]);
connectedRouters.add(new NodeData(socket, connectedRouterSocket, "ROUTER", connectedRouterName, connectedRouterDistance));
// Append the connected router's name and distance to our informationToSendToController string
flowRequestInformationToSendToController +=
connectedRouterName + ((connectedRouterDistance < 10) ? "0" + connectedRouterDistance : connectedRouterDistance);
}
listener.go();
}
catch (java.lang.Exception e) { e.printStackTrace(); }
}
// Assume that incoming packets contain a String, create PacketContent which
// sets the variables.
public synchronized void onReceipt(DatagramPacket receivedPacket) {
// First we need to find the router that this packet was delivered from
// so we can communicate back later
int portDeliveredFrom = receivedPacket.getPort();
NodeData nodeDeliveredFrom = findNode(portDeliveredFrom);
// First check its an ACK, else it is a SND
ACKContent potentialACK = new ACKContent(receivedPacket);
// First we have to check the case that the packet is from our connected host
if(potentialACK.isValidACK())
acceptACKs(nodeDeliveredFrom, potentialACK.getACKNumber());
else
{
SNDContent newPacket = new SNDContent(receivedPacket);
if(newPacket.isValid() && nodeDeliveredFrom != null)
{
if(newPacket.getPacketNumber() == nodeDeliveredFrom.getNextExpectedPackNum())
{
// Immediately send ACK back
nodeDeliveredFrom.incrementNextExpectedPackNum();
sendACK(nodeDeliveredFrom);
if (nodeDeliveredFrom.getNodeType().equals("CONTROLLER")) // if new Job Listing
processControllerInstruction(newPacket);
else if(nodeDeliveredFrom.getNodeType().equals("ROUTER"))
processRouterInstruction(newPacket);
else if(nodeDeliveredFrom.getNodeType().equals("HOST"))
processHostInstruction(newPacket);
}
else
{
// Send an ACK with next expected packet number if this node
// has already sent us this packet. We don't increment the
// next expected packet number here.
if(nodeDeliveredFrom != null) sendACK(nodeDeliveredFrom);
}
}
}
this.notify();
}
public NodeData findNode(int portDeliveredFrom)
{
if(connectedHost != null)
{
if(portDeliveredFrom == connectedHost.getDSTPort())
return connectedHost;
}
if(portDeliveredFrom == connectedController.getDSTPort())
return connectedController;
for(NodeData iterationRouter : connectedRouters)
{
if(iterationRouter.getDSTPort() == portDeliveredFrom)
return iterationRouter;
}
return null;
}
private void acceptACKs(NodeData nodeDeliveredFrom, int latestACK) {
// Decrement ACK Packet Number by 1
int iterationACK = (15 + latestACK) % 16;
// find last timer placed in Go-Back-N Window
Timer packetTimerIteration = nodeDeliveredFrom.goBackNWindow[iterationACK];
// until we reach the null element...
while (packetTimerIteration != null)
{
// cancel timer and nullify it on array
packetTimerIteration.cancel();
nodeDeliveredFrom.goBackNWindow[iterationACK] = null;
// Continue the while loop, so we can also ACK all previous messages to this router
packetTimerIteration = nodeDeliveredFrom.goBackNWindow[iterationACK];
nodeDeliveredFrom.goBackNWindowSize--;
// equation for cycling backwards through a cyclical array of size 16
iterationACK = (15 + iterationACK) % 16;
}
}
private void sendACK(NodeData nodeDeliveredFrom)
{
// ACKs are typically only sent once, so we do not have to designate a timer
// to them, and we do not have to add them to our Go-Back-N window.
ACKContent newACK = new ACKContent(nodeDeliveredFrom.getNextExpectedPackNum());
DatagramPacket ackPacket = newACK.toDatagramPacket();
ackPacket.setSocketAddress(nodeDeliveredFrom.getDstAddress());
try {
socket.send(ackPacket);
} catch (IOException e) { e.printStackTrace(); };
}
public void forwardPacket(SNDContent packetToForward, String destinationHost)
{
if(connectedHost != null)
{
if(destinationHost.equals(connectedHost.getNodeName()))
{ // For the specific case that we need to forward this packet to our connected host
forwardToHost(packetToForward);
return;
}
}
String accessRouterName = routerFlowTable.getAccessRouter(destinationHost);
NodeData nextRouter = null;
for(NodeData iterationRouter : connectedRouters)
{
if(iterationRouter.getNodeName().equals(accessRouterName))
{
nextRouter = iterationRouter;
break;
}
}
if(nextRouter != null)
{
nextRouter.sendPacket(packetToForward);
terminal.println(routerName + ": Packet forwarded to " + nextRouter.getNodeName()
+ " (Src: " + packetToForward.getSourceHostName() +
", Dst: " + packetToForward.getDestinationHostName()
+ ", Content: " + packetToForward.getPacketContent() +")");
}
}
public void processControllerInstruction(SNDContent newPacket)
{
if(newPacket.getContentType().equals("HELLO"))
{
terminal.println(routerName + ": Controller said hello back!");
}
else if(newPacket.getContentType().equals("FETRQ"))
{
terminal.println("Controller is requesting a FeatureReply...");
SNDContent featureReply = new SNDContent("SND00FETRP0000"
+ flowRequestInformationToSendToController + '\u0003');
connectedController.sendPacket(featureReply);
terminal.println(routerName + ": FeatureReply sent, setup complete.");
setupComplete = true;
// Now we must send a "Hello" to the host, so they know they can
// start sending strings
SNDContent helloToHost = new SNDContent("SND00HELLO0000" + '\u0003');
if(connectedHost != null) connectedHost.sendPacket(helloToHost);
}
else if(newPacket.getContentType().equals("FLWMD"))
{
if(newPacket.getPacketContent() != null)
{
routerFlowTable.updateFlowChart(newPacket.getPacketContent());
terminal.println(routerName + ": Controller has updated our flow table!");
}
}
}
public void processRouterInstruction(SNDContent newPacket)
{
// Routers will receive only "PACIN" packet content types from other routers
if(newPacket.getContentType().equals("PACIN"))
{
String destinationHost = newPacket.getDestinationHostName();
// If we know about the destination host (judging by our flow table), then
// we can send the packet on. Otherwise, we must request a new flow table
// from the Controller.
if(routerFlowTable.isDestinationHostKnown(destinationHost))
forwardPacket(newPacket, destinationHost);
else
{
terminal.println(routerName + ": Requesting flow modification from Controller...");
SNDContent requestFlowMod = new SNDContent("SND00PACIN0000" + '\u0003');
connectedController.sendPacket(requestFlowMod);
waitingToSend.add(newPacket);
}
}
}
public void processHostInstruction(SNDContent newPacket)
{
// Routers will also received only "PACIN" packets from hosts
if(newPacket.getContentType().equals("PACIN"))
{
terminal.println(routerName + ": Packet received from " + connectedHost.getNodeName() + ", attempting to send to " + newPacket.getDestinationHostName());
if(routerFlowTable.isDestinationHostKnown(newPacket.getDestinationHostName()))
forwardPacket(newPacket, newPacket.getDestinationHostName());
else
{
SNDContent requestFlowMod = new SNDContent("SND00PACIN0000" + '\u0003');
connectedController.sendPacket(requestFlowMod);
waitingToSend.add(newPacket);
}
}
else if(newPacket.getContentType().equals("HELLO"))
hostReturnedHello = true;
}
public void forwardToHost(SNDContent content)
{
if(hostReturnedHello) // if we know that the host is available
{
connectedHost.sendPacket(content);
terminal.println(routerName + ": Packet DELIVERED to " + connectedHost.getNodeName()
+ " (Src: " + content.getSourceHostName() +
", Dst: " + content.getDestinationHostName()
+ ", Content: " + content.getPacketContent() +")");
}
else
{
terminal.println(routerName + ": Packet received for " + connectedHost.getNodeName() + ", but "
+ "this host is not connected. Packet discarded");
}
}
public synchronized void start() throws Exception {
SNDContent helloToController = new SNDContent("SND00HELLO0000" + routerName + '\u0003');
terminal.println(routerName + ": Saying hello to Controller...");
connectedController.sendPacket(helloToController);
ArrayList<SNDContent> removable = new ArrayList<SNDContent>();
while(true)
{
if(setupComplete)
{
// Will iterate over the packets that we previously were not able to send,
// and see if our flow table has been updated to include the destination host
for(SNDContent recheckingPacket : waitingToSend)
{
if(routerFlowTable.isDestinationHostKnown(recheckingPacket.getDestinationHostName()))
{
forwardPacket(recheckingPacket, recheckingPacket.getDestinationHostName());
removable.add(recheckingPacket);
}
}
for(SNDContent removePacket : removable)
waitingToSend.remove(removePacket);
removable.clear();
}
this.wait();
}
}
public static void main(String[] args) {
try
{
String [] routerArguments = args;
Terminal terminal1 = new Terminal("Router");
(new Router(terminal1, routerArguments, 50000)).start();
} catch (java.lang.Exception e) { e.printStackTrace(); }
}
}