-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDDoSDetector.java
More file actions
178 lines (157 loc) · 8.63 KB
/
Copy pathDDoSDetector.java
File metadata and controls
178 lines (157 loc) · 8.63 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
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.HashMap;
public class DDoSDetector {
/**
* Parses log lines and returns IP addresses participating in a DDoS attack.
*
* A DDoS attacker is defined as any IP that made 3 or more requests within
* the same second that all resulted in a 4xx (400-level) HTTP status code.
*
* Log format:
* <ip> - <userId> [<date>:<hh>:<mm>:<ss> <tz>] "<method> <path> <protocol>" <statusCode> <bytes>
*
* Example line:
* 163.24.94.132 - 7734 [05/Jan/2015:16:53:25 -0800] "PUT /account/transfer HTTP/1.0" 401 7953
*
* @param logs list of raw log lines
* @return list of IP addresses flagged as DDoS attackers (no duplicates)
*/
public static List<String> detectDDoS(List<String> logs) {
// define a hash map that uses a string as the key and an int as the value
// iterate of the strings, parsing out the timestamp + IP + status
// for any 4XX status increment the value for key IP:timestamp
// return an arrayList of any IPs with a count of 3 or more in one second
HashMap<String, Integer> requests = new HashMap<String, Integer>();
for(String log : logs) {
String[] parts = log.split(" ");
String status = parts[parts.length-2];
if (!status.matches("^4[0-9]{2}$")) {
continue; //if it's not a 4xx error we don't count it
}
String ip = parts[0];
String time = parts[3];
String key = ip + ":" + time;
Integer count = 0;
if (requests.get(key) != null) {
count = requests.get(key);
}
requests.put(key, count + 1);
}
List<String> flagged = new ArrayList<>();
for (Map.Entry<String, Integer> entry : requests.entrySet()) {
if (entry.getValue() >= 3) {
flagged.add(entry.getKey().split(":")[0]);
}
}
return flagged.stream().distinct().collect(Collectors.toList());
}
// -------------------------------------------------------------------------
// Test runner
// -------------------------------------------------------------------------
public static void main(String[] args) {
int passed = 0, failed = 0;
// --- Test 1: basic case from the problem statement ---
// 163.24.94.132 makes 3 requests in the same second with 401 -> flagged
// 28.252.89.140 makes requests across two seconds; only 2 in second :26, 2 in :27 -> NOT flagged
{
List<String> logs = List.of(
"220.233.136.94 - 6079 [05/Jan/2015:16:53:25 -0800] \"PUT /account/withdraw HTTP/1.0\" 200 2895",
"23.108.181.138 - 7073 [05/Jan/2015:16:53:25 -0800] \"GET /login.html HTTP/1.0\" 200 3306",
"163.24.94.132 - 7734 [05/Jan/2015:16:53:25 -0800] \"PUT /account/transfer HTTP/1.0\" 401 7953",
"163.24.94.132 - 7734 [05/Jan/2015:16:53:25 -0800] \"PUT /account/transfer HTTP/1.0\" 401 7953",
"163.24.94.132 - 7734 [05/Jan/2015:16:53:25 -0800] \"PUT /account/transfer HTTP/1.0\" 401 7953",
"105.118.169.3 - 9277 [05/Jan/2015:16:53:26 -0800] \"POST /account/withdraw HTTP/1.0\" 200 1575",
"241.78.197.101 - 6734 [05/Jan/2015:16:53:26 -0800] \"PUT /account/transfer HTTP/1.0\" 200 427",
"28.252.89.140 - 7584 [05/Jan/2015:16:53:26 -0800] \"GET /account/transfer HTTP/1.0\" 401 543",
"28.252.89.140 - 7584 [05/Jan/2015:16:53:26 -0800] \"GET /account/transfer HTTP/1.0\" 403 543",
"28.252.89.140 - 7584 [05/Jan/2015:16:53:27 -0800] \"GET /account/transfer HTTP/1.0\" 403 543",
"5.61.113.127 - 8758 [05/Jan/2015:16:53:27 -0800] \"POST /account/information HTTP/1.0\" 200 9499",
"132.155.146.207 - 2400 [05/Jan/2015:16:53:27 -0800] \"GET /account/information HTTP/1.0\" 200 3137"
);
List<String> expected = List.of("163.24.94.132");
boolean ok1 = check("Test 1 (basic case)", detectDDoS(logs), expected);
if (ok1) passed++; else failed++;
}
// --- Test 2: empty log ---
{
List<String> result = detectDDoS(new ArrayList<>());
boolean ok = result.isEmpty();
System.out.printf(" %-40s -> %s%n", "Test 2 (empty log)", ok ? "PASS" : "FAIL (expected empty list)");
if (ok) passed++; else failed++;
}
// --- Test 3: no 4xx errors at all ---
{
List<String> logs = List.of(
"1.2.3.4 - 1 [05/Jan/2015:16:53:25 -0800] \"GET / HTTP/1.0\" 200 100",
"1.2.3.4 - 1 [05/Jan/2015:16:53:25 -0800] \"GET / HTTP/1.0\" 200 100",
"1.2.3.4 - 1 [05/Jan/2015:16:53:25 -0800] \"GET / HTTP/1.0\" 200 100"
);
List<String> expected = new ArrayList<>();
boolean ok3 = check("Test 3 (no 4xx)", detectDDoS(logs), expected);
if (ok3) passed++; else failed++;
}
// --- Test 4: exactly 3 4xx in same second -> flagged ---
{
List<String> logs = List.of(
"9.9.9.9 - 1 [05/Jan/2015:16:53:25 -0800] \"GET / HTTP/1.0\" 403 100",
"9.9.9.9 - 1 [05/Jan/2015:16:53:25 -0800] \"GET / HTTP/1.0\" 401 100",
"9.9.9.9 - 1 [05/Jan/2015:16:53:25 -0800] \"GET / HTTP/1.0\" 404 100"
);
List<String> expected = List.of("9.9.9.9");
boolean ok4 = check("Test 4 (exactly 3 mixed 4xx)", detectDDoS(logs), expected);
if (ok4) passed++; else failed++;
}
// --- Test 5: 2 in one second, 1 in next -> NOT flagged ---
{
List<String> logs = List.of(
"7.7.7.7 - 1 [05/Jan/2015:16:53:25 -0800] \"GET / HTTP/1.0\" 401 100",
"7.7.7.7 - 1 [05/Jan/2015:16:53:25 -0800] \"GET / HTTP/1.0\" 401 100",
"7.7.7.7 - 1 [05/Jan/2015:16:53:26 -0800] \"GET / HTTP/1.0\" 401 100"
);
List<String> expected = new ArrayList<>();
boolean ok5 = check("Test 5 (split across seconds)", detectDDoS(logs), expected);
if (ok5) passed++; else failed++;
}
// --- Test 6: multiple attackers ---
{
List<String> logs = List.of(
"1.1.1.1 - 1 [05/Jan/2015:16:53:25 -0800] \"GET / HTTP/1.0\" 401 100",
"1.1.1.1 - 1 [05/Jan/2015:16:53:25 -0800] \"GET / HTTP/1.0\" 401 100",
"1.1.1.1 - 1 [05/Jan/2015:16:53:25 -0800] \"GET / HTTP/1.0\" 401 100",
"2.2.2.2 - 2 [05/Jan/2015:16:53:25 -0800] \"GET / HTTP/1.0\" 403 100",
"2.2.2.2 - 2 [05/Jan/2015:16:53:25 -0800] \"GET / HTTP/1.0\" 403 100",
"2.2.2.2 - 2 [05/Jan/2015:16:53:25 -0800] \"GET / HTTP/1.0\" 403 100"
);
List<String> result = detectDDoS(logs);
boolean ok = result.size() == 2 && result.contains("1.1.1.1") && result.contains("2.2.2.2");
System.out.printf(" %-40s -> %s%n", "Test 6 (multiple attackers)", ok ? "PASS" : "FAIL (expected [1.1.1.1, 2.2.2.2])");
if (ok) passed++; else failed++;
}
// --- Test 7: same IP flagged only once even if attacks span multiple seconds ---
{
List<String> logs = List.of(
"3.3.3.3 - 1 [05/Jan/2015:16:53:25 -0800] \"GET / HTTP/1.0\" 401 100",
"3.3.3.3 - 1 [05/Jan/2015:16:53:25 -0800] \"GET / HTTP/1.0\" 401 100",
"3.3.3.3 - 1 [05/Jan/2015:16:53:25 -0800] \"GET / HTTP/1.0\" 401 100",
"3.3.3.3 - 1 [05/Jan/2015:16:53:26 -0800] \"GET / HTTP/1.0\" 401 100",
"3.3.3.3 - 1 [05/Jan/2015:16:53:26 -0800] \"GET / HTTP/1.0\" 401 100",
"3.3.3.3 - 1 [05/Jan/2015:16:53:26 -0800] \"GET / HTTP/1.0\" 401 100"
);
List<String> result = detectDDoS(logs);
boolean ok = result.size() == 1 && result.contains("3.3.3.3");
System.out.printf(" %-40s -> %s%n", "Test 7 (no duplicate IPs)", ok ? "PASS" : "FAIL (expected [3.3.3.3] once)");
if (ok) passed++; else failed++;
}
System.out.printf("%nResults: %d passed, %d failed%n", passed, failed);
}
private static boolean check(String label, List<String> actual, List<String> expected) {
boolean ok = actual.size() == expected.size()
&& actual.containsAll(expected)
&& expected.containsAll(actual);
System.out.printf(" %-40s -> %s%n", label, ok ? "PASS" : "FAIL (expected " + expected + ", got " + actual + ")");
return ok;
}
}