-
Notifications
You must be signed in to change notification settings - Fork 16
/
ClientB.java
268 lines (222 loc) · 10.2 KB
/
ClientB.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
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.*;
/**
*
* @author user
*/
public class ClientB {
private static InetAddress mediatorIP;
private static int mediatorTcpDiscussionPort;
private static int mediatorTcpPunchPort;
private Socket socketDiscussion, socketClientPunch;
private ServerSocket socketServerPunch;
private final BufferedReader inDiscussion;
private final BufferedOutputStream outDiscussion;
private BufferedReader inPunch;
private BufferedOutputStream outPunch;
private String message = "";
private String[] tokens = null;
private boolean respRead;
private volatile boolean runningHole;
private Thread readOnHole, listenOnHole, writeOnHole;
/**
* Constructor of ClientB
* @param ip the ip addr of the mediator
* @param tcpDiscussionPort the tcp port to connect to the mediator for discussion
* @param tcpPunchPort the tcp port to connect to the mediator for punching holes
* @throws IOException if something goes wrong
*/
public ClientB(InetAddress ip, int tcpDiscussionPort, int tcpPunchPort) throws IOException{
//create a socket to connect to the server
try {
socketDiscussion = new Socket(ip, tcpDiscussionPort);
socketClientPunch = new Socket(ip, tcpPunchPort);
} catch (IOException ex) {
System.err.println("Exception creating a socket: " + ex);
}
this.runningHole = true;
//create input and output stream
inDiscussion = new BufferedReader(new InputStreamReader(socketDiscussion.getInputStream()));
outDiscussion = new BufferedOutputStream(socketDiscussion.getOutputStream());
inPunch = new BufferedReader(new InputStreamReader(socketClientPunch.getInputStream()));
outPunch = new BufferedOutputStream(socketClientPunch.getOutputStream());
System.out.println("Read on hole");
readOnHole();
System.out.println("sending initial tcp punch message");
//Send message, the server get all information about the message send (local port, distant port and ip)
byte[] sendData = "two".getBytes();
outPunch.write(sendData);
outPunch.write('\n');
outPunch.flush();
}
private void readOnHole() throws IOException{
this.readOnHole = new Thread(new Runnable() {
@Override
public void run() {
//create a loop to read the TCP response from the server
while (!respRead){
try{
//Wait for message
message = inDiscussion.readLine();
tokens = message.split("~~"); //split response into tokens for IP and Port
System.out.println("****************************************");
System.out.println("My PUBLIC IP seen by server: " + tokens[0]);
System.out.println("My PUBLIC TCP PORT seen by server: " + tokens[1]);
System.out.println("My LOCAL TCP PORT seen by server: " + tokens[2]);
System.out.println("****************************************\n");
System.out.println("****************************************");
System.out.println("CLIENT A PUBLIC IP seen by server: " + tokens[3]);
System.out.println("CLIENT A PUBLIC TCP PORT seen by server: " + tokens[4]);
System.out.println("CLIENT A LOCAL TCP PORT seen by server: " + tokens[5]);
System.out.println("****************************************");
respRead = true;
//ACK SERVER
outDiscussion.write("ackTwo".getBytes());
outDiscussion.write('\n');
outDiscussion.flush();
//Received all infos needed -> proceed hole punching
proceedHolePunching(InetAddress.getByName(tokens[3].trim()), Integer.parseInt(tokens[5].trim()), Integer.valueOf(tokens[4]));
}catch (IOException ioe){
ioe.printStackTrace();
}
}
}
});
this.readOnHole.start();
}
private void listenConnectionHole(int localPort){
new Thread(new Runnable() {
@Override
public void run() {
try{
System.out.println("Listen hole on port: " + localPort);
socketServerPunch = new ServerSocket(localPort);
socketClientPunch = socketServerPunch.accept();
inPunch = new BufferedReader(new InputStreamReader(socketClientPunch.getInputStream()));
outPunch = new BufferedOutputStream(socketClientPunch.getOutputStream());
}catch (Exception e){
inPunch = null;
outPunch = null;
}
}
}).start();
}
private void listenDataOnHole(String addr, int port){
this.listenOnHole = new Thread(new Runnable() {
@Override
public void run() {
while(runningHole) {
try {
message = inPunch.readLine();
System.out.println("Received: " + message.trim() + ", From: IP " + addr + " Port " + port);
} catch (IOException ex) {
System.err.println("Error " + ex);
}
}
}
});
this.listenOnHole.start();
}
private void redirectPorts(int from, int to){
try {
Process proc1 = Runtime.getRuntime().exec("iptables -t nat -A PREROUTING -i eth0 -p tcp --dport " + from + " -j REDIRECT --to-port " + to);
} catch (IOException e) {
e.printStackTrace();
}
}
private void writeDataOnHole(){
this.writeOnHole = new Thread(new Runnable() {
@Override
public void run() {
int j = 0;
String msg;
//create Loop to send udp packets
while(runningHole){
try{
msg = "I AM CLIENT B " + j;
outPunch.write(msg.getBytes());
outPunch.write('\n');
outPunch.flush();
j++;
Thread.sleep(2000);
}catch(IOException e){
System.err.println("IOException");
}catch(Exception e){
System.err.println("SleepException");
}
}
}
});
this.writeOnHole.start();
}
private void proceedHolePunching(InetAddress addrToConnect, int portToConnect, int localPort) throws IOException{
if(this.socketClientPunch != null){
outPunch = null;
inPunch = null;
String addr = addrToConnect.getHostAddress().trim();
System.out.println("Start listen on port : " + localPort);
listenConnectionHole(localPort);
System.out.println("Attempt to connect to : " + addr + ":" + portToConnect);
try{
//Close this socket actually connected to the mediator
socketClientPunch.setReuseAddress(true);
socketClientPunch.close();
//Create a new one
socketClientPunch = new Socket();
socketClientPunch.setReuseAddress(true);
//Bind it to the same addr
socketClientPunch.bind(new InetSocketAddress(localPort));
//Connect to the distant client
socketClientPunch.connect(new InetSocketAddress(addrToConnect, portToConnect));
//Init in and out
inPunch = new BufferedReader(new InputStreamReader(socketClientPunch.getInputStream()));
outPunch = new BufferedOutputStream(socketClientPunch.getOutputStream());
}catch (ConnectException ce){
System.out.println("Punch: Connection refused");
}
if(outPunch != null && inPunch != null){
System.out.println("Punch: Connected to : " + addr + ":" + portToConnect);
redirectPorts(localPort, 8080);
listenDataOnHole(addr, portToConnect);
writeDataOnHole();
}else{
System.err.println("Error when attempting to connect");
}
}
}
//Entry point
public static void main(String[] args) throws IOException{
if (args.length > 0) {//Give args
try {
//Get first param server mediator ip address
mediatorIP = InetAddress.getByName(args[0].trim());
//Get second params udp port
mediatorTcpDiscussionPort = Integer.parseInt(args[1].trim());
//Get third params tcp port
mediatorTcpPunchPort = Integer.parseInt(args[2].trim());
} catch (Exception ex) {
System.err.println("Error in input");
System.out.println("USAGE: java ClientB mediatorIP mediatorTcpDiscussionPort mediatorTcpPunchPort");
System.out.println("Example: java ClientB 127.0.0.1 9000 9001");
System.exit(0);
}
} else {//Give no args
System.out.println("ClientB running with default ports 9000 and 9001");
//by default use localhost
mediatorIP = InetAddress.getByName("127.0.0.1");
//default port for tcp
mediatorTcpDiscussionPort = 9000;
//default port for udp
mediatorTcpPunchPort = 9001;
}
new ClientB(mediatorIP, mediatorTcpDiscussionPort, mediatorTcpPunchPort);
}
}