This is a student project of mine. I got 4/5 points but I suspect there's a lot of weird stuff in there. I never had a chance to hear my teacher's feedback about it and it bothers me so I figured somebody could take a glance at it and provide some feedback.
Quick documentation
First we run "Server" program that opens ports provided by runtime args[] parameters listening in parallel in separate threads.
Next we run "Client" passing to the constructor hostname and list of ports to knock. We can run many clients or set numberofclients variable in code.Server UDP sockets have access to synchronized data structure that stores information about incoming connections with sockets.
After each connection program checks if order of connections is same as key. If yes then server sends message with TCP port to the socket of unauthorized yet client that sent correct combination of ports and another thread is run with TCP socket listening for authorized client. Authorized client connects to TCP socket and exchanges short information with the server. After the exchange TCP socket closes.Enter key closes program and every thread and socket is freed.
Server
package com.company;
import java.io.IOException;
import java.net.*;
import java.util.*;
import java.util.stream.Collectors;
public class Server implements Runnable{
byte[] buffer = new byte[2048];
byte[] sendBuffer = new byte[2048];
List<DatagramSocket> socketList;
Map<InetSocketAddress, List<Integer>> internalMap;
Map<InetSocketAddress, List<Integer>> guestMap;
List<Integer> key;
Server(Integer... ports) throws SocketException {
this.key = new ArrayList<>();
//key.addAll(Arrays.asList(ports));
for(Integer portnumber : ports)
key.add(portnumber);
this.socketList = new ArrayList<>();
this.internalMap = new HashMap<>();
this.guestMap = Collections.synchronizedMap(internalMap);
List<Integer> uniquePorts = key.stream().distinct().collect(Collectors.toList());
for(Integer port: uniquePorts){
DatagramSocket socket = new DatagramSocket(port);
socketList.add(socket);
}
System.out.println("Zajete porty: ");
socketList.forEach(n -> System.out.print(n.getLocalPort() + ", "));
System.out.println();
this.key = socketList.stream().map(DatagramSocket::getLocalPort).collect(Collectors.toList());
}
@Override
public void run() {
for(DatagramSocket socket : socketList){
Thread t = new Thread(){
boolean running;
@Override
public void run() {
while(true){
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
try {
socket.receive(packet);
ProtocolMessage msgRecvd = new ProtocolMessage(packet.getData());
System.out.println(msgRecvd); //dbgging
if(!msgRecvd.equals(Protocol.synMessage))
throw new ProtocolException("Request not recognized, possibly malformed");
InetAddress address = packet.getAddress();
int port = packet.getPort();
InetSocketAddress guest = new InetSocketAddress(address,port);
if(guestMap.containsKey(guest)){
guestMap.get(guest).add(socket.getLocalPort()); //refactor it later
if(guestMap.get(guest).equals(key)){
ServerClientService serverClientService = new ServerClientService();
int establishedPort = serverClientService.serverSocket.getLocalPort();
System.out.println("Guest: " + address + ":" + port+ " authenticated sending tcp port: " + establishedPort);
ProtocolMessage auth = Protocol.authSuccess;
auth.setValue(establishedPort);
sendBuffer = auth.toByte();
socket.send(new DatagramPacket(sendBuffer,sendBuffer.length, address, port));
serverClientService.run();
}
else if(key.size() < guestMap.get(guest).size()){
guestMap.remove(guest);
}
}
else{
List<Integer> list = new ArrayList<>();
List<Integer> synchlist = Collections.synchronizedList(list);
synchlist.add(socket.getLocalPort());
guestMap.put(guest, synchlist);
}
sendBuffer = Protocol.ackMessage.toByte();
DatagramPacket confirmation = new DatagramPacket(sendBuffer, sendBuffer.length, address, port);
socket.send(confirmation);
}catch (SocketTimeoutException e){
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
t.setDaemon(true); //inne wątki nie moga istniec bez procesu main
t.start();
}
Scanner s = new Scanner(System.in);
System.out.println("Naciśnij \"Enter\" aby wyjść.....");
s.nextLine();
}
}
Server-Client TCP service
package com.company;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerClientService implements Runnable {
ServerSocket serverSocket;
ServerClientService(){
try {
serverSocket = new ServerSocket(0);
} catch (IOException e) {
e.printStackTrace();
System.err.println("Port niedostepny");
}
}
@Override
public void run() {
try( Socket clientSocket = serverSocket.accept();
PrintWriter out = new PrintWriter(clientSocket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()))
)
{
String query = in.readLine();
System.out.println(query);
String response = "hello there client " + getPort();
out.println(response);
out.flush();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public int getPort() {
return serverSocket.getLocalPort();
}
}
Client
package com.company;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.*;
import java.util.ArrayList;
import java.util.List;
public class Client {
byte[] sendBuffer;
byte[] recvBuffer;
DatagramSocket socket;
String hostName;
List<Integer> key;
Client(String address, int... ports) throws SocketException {
this.key = new ArrayList<>();
this.sendBuffer = new byte[2048];
this.recvBuffer = new byte[2048];
this.socket = new DatagramSocket();
this.hostName = address;
for(int i : ports)
this.key.add(i);
}
public void run() {
try {
InetAddress inetAddress = InetAddress.getByName(hostName);
for(Integer port : key){
sendBuffer = Protocol.synMessage.toByte();
DatagramPacket packet = new DatagramPacket(sendBuffer, sendBuffer.length, inetAddress,port);
socket.send(packet);
//System.out.println("sent: " + new ProtocolMessage(sendBuffer));
DatagramPacket returnPacket = new DatagramPacket(recvBuffer, recvBuffer.length);
returnPacket.setLength(returnPacket.getLength());
socket.setSoTimeout(5000);
try {
socket.receive(returnPacket);
} catch (SocketTimeoutException e){
System.err.println("No response, probably invalid authentication");
}
ProtocolMessage response = new ProtocolMessage(returnPacket.getData());
System.out.println(response); //debugging
if (response.equals(Protocol.ackMessage)) {
//proceed
continue;
}
else if(response.equals(Protocol.authSuccess)){
//connect to sent tcp port
int portNumber = response.value;
startTcpConnection(portNumber);
break;
}
else{
throw new ProtocolException("Response not recognized, possibly malformed");
//should like catch it and try again perhaps
}
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
socket.close();
}
public void startTcpConnection(int port ){
try(Socket tcpSocket = new Socket(hostName, port);
PrintWriter out = new PrintWriter(tcpSocket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(tcpSocket.getInputStream()));)
{
out.println("Client " + port + ": hello");
out.flush();
String response = in.readLine();
System.out.println(response);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Common protocol
package com.company;
public class Protocol {
final static ProtocolMessage synMessage = new ProtocolMessage(1);
final static ProtocolMessage ackMessage = new ProtocolMessage(2);
final static ProtocolMessage authSuccess = new ProtocolMessage(3);
}
package com.company;
import java.nio.ByteBuffer;
public class ProtocolMessage {
int type;
int value;
ProtocolMessage(int type, int value){
this.type = type;
this.value = value;
}
ProtocolMessage(int type){
this.type = type;
this.value = 0;
}
ProtocolMessage(byte[] bytes){
ByteBuffer wrapped = ByteBuffer.wrap(bytes);
this.type = wrapped.getInt(0);
this.value = wrapped.getInt(50);
}
public byte[] toByte(){
ByteBuffer byteBuffer = ByteBuffer.allocate(2048);
byteBuffer.putInt(0,this.type);
byteBuffer.putInt(50,this.value);
return byteBuffer.array();
}
@Override
public String toString(){
return type + ":" + value;
}
@Override
public boolean equals(Object o){
ProtocolMessage another = (ProtocolMessage) o;
if(another.type == this.type)
return true;
else
return false;
}
void setValue(int value){
this.value = value;
}
}