Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 54 additions & 42 deletions src/main/java/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,20 @@
import java.util.LinkedList;

public class Main {
private static final int DEFAULT_TIMEOUT = 1000; // milliseconds
private enum Mode {
UDP,
TCP;

public static Mode fromString(String s) {
return switch (s) {
case "u" -> UDP;
case "t" -> TCP;
default -> throw new IllegalArgumentException("Unknown mode: " + s);
};
}
}

private static final int TCP_TIMEOUT = 1000; // milliseconds

private static void printUsageAndExit() {
System.out.println(
Expand All @@ -20,58 +33,59 @@ public static void main(String[] args) {
if (args.length == 0) {
printUsageAndExit();
}
// Collect parameters...

// Default parameters...
int delay = 100; // default delay
String host = "localhost"; // default host
LinkedList<AbstractMap.SimpleEntry<String, Integer>> knockSequence = new LinkedList<>();

// Parse arguments...
LinkedList<AbstractMap.SimpleEntry<Mode, Integer>> knockSequence = new LinkedList<>();
for (String arg : args) {
String[] parts = arg.split("=", 2);
if (parts.length == 2) {
String key = parts[0];
String value = parts[1];
switch (key) {
case "delay" -> {
try {
int d = Integer.parseInt(value);
if (d < 0 || d > 60_000) {
printUsageAndExit();
}
delay = d;
} catch (NumberFormatException e) {
if (parts.length != 2) {
printUsageAndExit();
}
String key = parts[0];
String value = parts[1];
switch (key) {
case "host" -> host = value;
case "delay" -> {
try {
int d = Integer.parseInt(value);
if (d < 0 || d > 60_000) {
printUsageAndExit();
}
delay = d;
} catch (NumberFormatException e) {
printUsageAndExit();
}
case "host" -> host = value;
case "u", "t" -> {
try {
int port = Integer.parseInt(value);
if (port < 1 || port > 65535) {
printUsageAndExit();
}
knockSequence.add(new AbstractMap.SimpleEntry<>(key, port));
} catch (NumberFormatException e) {
}
case "u", "t" -> {
try {
int port = Integer.parseInt(value);
if (port < 1 || port > 65535) {
printUsageAndExit();
}
knockSequence.add(new AbstractMap.SimpleEntry<>(Mode.fromString(key), port));
} catch (NumberFormatException e) {
printUsageAndExit();
}
default -> printUsageAndExit();
}
} else {
printUsageAndExit();
default -> printUsageAndExit();
}
}
if (knockSequence.isEmpty()) {
printUsageAndExit();
}

// Perform knocking sequence...
for (AbstractMap.SimpleEntry<String, Integer> entry : knockSequence) {
String protocol = entry.getKey();
for (AbstractMap.SimpleEntry<Mode, Integer> entry : knockSequence) {
Mode mode = entry.getKey();
int port = entry.getValue();
if (protocol.equals("u")) {
System.out.printf("Knocking UDP %s:%d ... ", host, port);
System.out.println(knockUDP(host, port) ? "Success" : "Failed");
} else if (protocol.equals("t")) {
System.out.printf("Knocking TCP %s:%d ... ", host, port);
System.out.println(knockTCP(host, port, DEFAULT_TIMEOUT) ? "Success" : "Failed");
System.out.printf("Knocking %s %s:%d ... ", mode.name(), host, port);
switch (mode) {
case UDP -> System.out.println(knockUDP(host, port) ? "Success" : "Failed");
case TCP -> System.out.println(knockTCP(host, port, TCP_TIMEOUT) ? "Success" : "Failed");
}
try {
Thread.sleep(delay);
Expand All @@ -83,15 +97,12 @@ public static void main(String[] args) {
}

/**
* Knocks on a UDP port by attempting to create a UDP socket connection. Note: UDP is
* connectionless, so this method only validates that a DatagramSocket can be created and
* connected to the address. It will succeed even if nothing is listening on the target port, as
* UDP does not establish a true connection like TCP.
* Knocks on a UDP port by attempting to create a UDP socket, and sending a datagram. The datagram
* content is not important, it has a fixed size of 2048 bytes.
*
* @param host the hostname or IP address to connect to
* @param port the UDP port to knock on
* @return true if the socket was successfully created and connected to the address, false
* otherwise
* @return true if the datagram was sent successfully, false otherwise
*/
public static boolean knockUDP(String host, int port) {
try (DatagramSocket socket = new DatagramSocket()) {
Expand All @@ -104,7 +115,8 @@ public static boolean knockUDP(String host, int port) {
}

/**
* Knocks on a TCP port by attempting to establish a TCP connection.
* Knocks on a TCP port by attempting to establish a TCP connection. Returning <code>false</code>
* does not necessarily mean that the port is closed; it may also be filtered by a firewall.
*
* @param host the hostname or IP address to connect to
* @param port the TCP port to knock on
Expand Down