Skip to content

Commit 4cc44c0

Browse files
authored
Merge pull request #261 from stratosphereips/fix-global-defender
Fix-global-defender
2 parents c6869ab + adbb6a0 commit 4cc44c0

File tree

5 files changed

+94
-25
lines changed

5 files changed

+94
-25
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,5 +275,25 @@ This will load and run the unit tests in the `tests` folder.
275275
## Code adaptation for new configurations
276276
The code can be adapted to new configurations of games and for new agents. See [Agent repository](https://github.com/stratosphereips/NetSecGameAgents/tree/main) for more details.
277277

278+
## Function calling diagram
279+
280+
```
281+
_handle_world_responses
282+
├── _world_response_queue.get() # Called continuously to get a response from the World Response Queue.
283+
├── _process_world_response # Called to process the response from the world.
284+
│ ├── _process_world_response_created # Called if agent status is JoinRequested. Processes agent creation.
285+
│ ├── _process_world_response_reset_done # Called if agent status is ResetRequested. Processes agent reset.
286+
│ ├── _remove_player # Called if agent status is Quitting. Removes the agent from the world.
287+
│ └── _process_world_response_step # Called if agent status is Ready, Playing, or PlayingActive. Processes a game step.
288+
├── _answers_queues[agent_id].put() # Called to place the processed response in the agent's answer queue.
289+
└── asyncio.sleep() # Called to yield control back to the event loop.
290+
291+
_process_world_response
292+
├── _process_world_response_created # Called if agent status is JoinRequested. Processes agent creation.
293+
├── _process_world_response_reset_done # Called if agent status is ResetRequested. Processes agent reset.
294+
├── _remove_player # Called if agent status is Quitting. Removes the agent from the world.
295+
└── _process_world_response_step # Called if agent status is Ready, Playing, or PlayingActive. Processes a game step.
296+
```
297+
278298
## About us
279299
This code was developed at the [Stratosphere Laboratory at the Czech Technical University in Prague](https://www.stratosphereips.org/).

coordinator.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,9 @@ def _check_detection(self, agent_addr:tuple, last_action:Action)->bool:
617617
if last_action:
618618
if self._use_global_defender:
619619
self.logger.warning("Global defender - ONLY use for backward compatibility!")
620-
episode_actions = self._agent_trajectories[agent_addr]["actions"] if "actions" in self._agent_trajectories[agent_addr] else []
620+
episode_actions = None
621+
if (agent_addr in self._agent_trajectories and "trajectory" in self._agent_trajectories[agent_addr] and "actions" in self._agent_trajectories[agent_addr]["trajectory"]):
622+
episode_actions = self._agent_trajectories[agent_addr]["trajectory"]["actions"]
621623
detection = stochastic_with_threshold(last_action, episode_actions)
622624
if detection:
623625
self.logger.info("\tDetected!")
@@ -723,7 +725,7 @@ def _process_world_response(self, agent_addr:tuple, response:tuple)-> str:
723725
self._remove_player(agent_addr)
724726
elif agent_status in [AgentStatus.Ready, AgentStatus.Playing, AgentStatus.PlayingActive]:
725727
output_message_dict = self._process_world_response_step(agent_addr, game_status, agent_new_state)
726-
elif agent_status in [AgentStatus.FinishedBlocked, AgentStatus.FinishedGameLost, AgentStatus.FinishedGoalReached, AgentStatus.FinishedMaxSteps]:
728+
elif agent_status in [AgentStatus.FinishedBlocked, AgentStatus.FinishedGameLost, AgentStatus.FinishedGoalReached, AgentStatus.FinishedMaxSteps]: # This if does not make sense. Put together with the previous (sebas)
727729
output_message_dict = self._process_world_response_step(agent_addr, game_status, agent_new_state)
728730
else:
729731
self.logger.error(f"Unsupported value '{agent_status}'!")
@@ -804,7 +806,7 @@ def _process_world_response_step(self, agent_addr:tuple, game_status:GameStatus,
804806
if not self.episode_end:
805807
# increase the action counter
806808
self._agent_steps[agent_addr] += 1
807-
self.logger.info(f"{agent_addr} steps: {self._agent_steps[agent_addr]}")
809+
self.logger.info(f"Agent {agent_addr} did #steps: {self._agent_steps[agent_addr]}")
808810
# register the new state
809811
self._agent_states[agent_addr] = agent_new_state
810812
# load the action which lead to the new state

env/global_defender.py

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,35 @@
44
from random import random
55

66

7+
# The probability of detecting an action is defined by the following dictionary
78
DEFAULT_DETECTION_PROBS = {
89
ActionType.ScanNetwork: 0.05,
910
ActionType.FindServices: 0.075,
1011
ActionType.ExploitService: 0.1,
1112
ActionType.FindData: 0.025,
1213
ActionType.ExfiltrateData: 0.025,
13-
ActionType.BlockIP:0
14+
ActionType.BlockIP: 0.01
1415
}
1516

16-
TW_RATIOS = {
17+
# Ratios of action types in the time window (TW) for each action type. The ratio should be higher than the defined value to trigger a detection check
18+
TW_TYPE_RATIOS_THRESHOLD = {
1719
ActionType.ScanNetwork: 0.25,
1820
ActionType.FindServices: 0.3,
1921
ActionType.ExploitService: 0.25,
2022
ActionType.FindData: 0.5,
2123
ActionType.ExfiltrateData: 0.25,
22-
ActionType.BlockIP:1
24+
ActionType.BlockIP: 1
2325
}
2426

25-
CONSECUTIVE_THRESHOLD = {
27+
# Thresholds for consecutive actions of the same type in the TW. Only if the threshold is crossed, the detection check is triggered
28+
TW_CONSECUTIVE_TYPE_THRESHOLD = {
2629
ActionType.ScanNetwork: 2,
2730
ActionType.FindServices: 3,
28-
ActionType.ExfiltrateData: 2,
31+
ActionType.ExfiltrateData: 2
2932
}
30-
REPEATED_THRESHOLD = {
33+
34+
# Thresholds for repeated actions in the episode. Only if the threshold is crossed, the detection check is triggered
35+
EPISODE_REPEATED_ACTION_THRESHOLD = {
3136
ActionType.ExploitService: 2,
3237
ActionType.FindData: 2,
3338
}
@@ -37,36 +42,39 @@ def stochastic(action_type:ActionType)->bool:
3742
Simple random detection based on predefied probability and ActionType
3843
"""
3944
roll = random()
40-
return roll < DEFAULT_DETECTION_PROBS[action_type]
45+
if roll < DEFAULT_DETECTION_PROBS[action_type]:
46+
return True
47+
else:
48+
return False
4149

42-
def stochastic_with_threshold(action:Action, episode_actions:list, tw_size:int=5)-> bool:
50+
def stochastic_with_threshold(action: Action, episode_actions:list, tw_size:int=5)-> bool:
4351
"""
4452
Only detect based on set probabilities if pre-defined thresholds are crossed.
4553
"""
4654
# extend the episode with the latest action
47-
episode_actions.append(action)
48-
if len(episode_actions) >= tw_size:
49-
last_n_actions = episode_actions[-tw_size:]
50-
last_n_action_types = [action.type for action in last_n_actions]
55+
# We need to copy the list before the copying, so we avoid modifying it when it is returned. Modifycation of passed list is the default behavior in Python
56+
temp_episode_actions = episode_actions.copy()
57+
temp_episode_actions.append(action.as_dict)
58+
if len(temp_episode_actions) >= tw_size:
59+
last_n_actions = temp_episode_actions[-tw_size:]
60+
last_n_action_types = [action['type'] for action in last_n_actions]
5161
# compute ratio of action type in the TW
52-
tw_ratio = last_n_action_types.count(action.type)/tw_size
62+
tw_ratio = last_n_action_types.count(str(action.type))/tw_size
5363
# Count how many times this exact (parametrized) action was played in episode
54-
repeats_in_episode = episode_actions.count(action)
55-
# Compute Action type ration in the TW
56-
tw_ratio = last_n_action_types.count(action.type)/tw_size
64+
repeats_in_episode = temp_episode_actions.count(action.as_dict)
5765
# compute the highest consecutive number of action type in TW
58-
max_consecutive_action_type= max(sum(1 for item in grouped if item == action.type)
66+
max_consecutive_action_type = max(sum(1 for item in grouped if item == str(action.type))
5967
for _, grouped in groupby(last_n_action_types))
6068

61-
if action.type in CONSECUTIVE_THRESHOLD.keys():
69+
if action.type in TW_CONSECUTIVE_TYPE_THRESHOLD.keys():
6270
# ScanNetwork, FindServices, ExfiltrateData
63-
if tw_ratio < TW_RATIOS[action.type] and max_consecutive_action_type < CONSECUTIVE_THRESHOLD[action.type]:
71+
if tw_ratio < TW_TYPE_RATIOS_THRESHOLD[action.type] and max_consecutive_action_type < TW_CONSECUTIVE_TYPE_THRESHOLD[action.type]:
6472
return False
6573
else:
6674
return stochastic(action.type)
67-
elif action.type in REPEATED_THRESHOLD.keys():
75+
elif action.type in EPISODE_REPEATED_ACTION_THRESHOLD.keys():
6876
# FindData, Exploit service
69-
if tw_ratio < TW_RATIOS[action.type] and repeats_in_episode < REPEATED_THRESHOLD[action.type]:
77+
if tw_ratio < TW_TYPE_RATIOS_THRESHOLD[action.type] and repeats_in_episode < EPISODE_REPEATED_ACTION_THRESHOLD[action.type]:
7078
return False
7179
else:
7280
return stochastic(action.type)

env/worlds/aidojo_world.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,29 @@ def update_goal_dict(self, goal_dict:dict)->dict:
5757
raise NotImplementedError
5858

5959
async def handle_incoming_action(self)->None:
60+
"""
61+
Asynchronously handles incoming actions from agents and processes them accordingly.
62+
63+
This method continuously listens for actions from the `_action_queue`, processes them based on their type,
64+
and sends the appropriate response to the `_response_queue`. It handles different types of actions such as
65+
joining a game, quitting a game, and resetting the game. For other actions, it updates the game state by
66+
calling the `step` method.
67+
68+
Raises:
69+
asyncio.CancelledError: If the task is cancelled, it logs the termination message.
70+
71+
Action Types:
72+
- ActionType.JoinGame: Creates a new game state and sends a CREATED status.
73+
- ActionType.QuitGame: Sends an OK status with an empty game state.
74+
- ActionType.ResetGame: Resets the world if the agent is "world", otherwise resets the game state and sends a RESET_DONE status.
75+
- Other: Updates the game state using the `step` method and sends an OK status.
76+
77+
Logging:
78+
- Logs the start of the task.
79+
- Logs received actions and game states from agents.
80+
- Logs the messages being sent to agents.
81+
- Logs termination due to `asyncio.CancelledError`.
82+
"""
6083
try:
6184
self.logger.info(f"\tStaring {self.world_name} task.")
6285
while True:
@@ -77,7 +100,7 @@ async def handle_incoming_action(self)->None:
77100
new_state = self.step(game_state, action,agent_id)
78101
msg = (agent_id, (new_state, GameStatus.OK))
79102
# new_state = self.step(state, action, agent_id)
80-
self.logger.debug(f"Sending to{agent_id}: {msg}")
103+
self.logger.debug(f"Sending to {agent_id}: {msg}")
81104
await self._response_queue.put(msg)
82105
await asyncio.sleep(0)
83106
except asyncio.CancelledError:

env/worlds/network_security_game.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,22 @@ def _create_new_network_mapping(self)->tuple:
326326
new_self_networks[mapping_nets[net]].add(mapping_ips[ip])
327327
self._networks = new_self_networks
328328

329+
# Harpo says that here there is a problem that firewall.items() do not return an ip that can be used in the mapping
330+
# His solution is: (check)
331+
"""
332+
new_self_firewall = {}
333+
for ip, dst_ips in self._firewall.items():
334+
if ip not in mapping_ips:
335+
self.logger.debug(f"IP {ip} not found in mapping_ips")
336+
continue # Skip this IP if it's not found in the mapping
337+
338+
new_self_firewall[mapping_ips[ip]] = set()
339+
340+
for dst_ip in dst_ips:
341+
new_self_firewall[mapping_ips[ip]].add(mapping_ips[dst_ip])
342+
self._firewall = new_self_firewall
343+
"""
344+
329345
#self._firewall
330346
new_self_firewall = {}
331347
for ip, dst_ips in self._firewall.items():

0 commit comments

Comments
 (0)