Skip to content

Commit 2d3bf06

Browse files
Merge remote-tracking branch 'origin/main' into events
2 parents 7ba30b7 + 754ca88 commit 2d3bf06

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+1492
-2175
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
<h1 align="center">NEBULA: A Platform for Decentralized Federated Learning</h1>
77

88
<p align="center">
9-
<a href="https://federeratedlearning.inf.um.es">federatedlearning.inf.um.es</a>
9+
<a href="https://nebula-dfl.com">nebula-dfl.com</a> |
10+
<a href="https://nebula-dfl.eu">nebula-dfl.eu</a> |
11+
<a href="https://federeratedlearning.inf.um.es">federatedlearning.inf.um.es</a>
1012
</p>
1113
</p>
1214

analysis/README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,23 @@
22
<br>
33
<p align="center">
44
<a href="https://github.com/CyberDataLab/nebula">
5-
<img src="../docs/_prebuilt/static/nebula-logo.jpg" alt="nebula">
5+
<img src="https://raw.githubusercontent.com/CyberDataLab/nebula/5b44d54eec9186f7c9f6351f26cd92b33bd37fdf/docs/_prebuilt/static/nebula-logo.jpg" alt="nebula">
66
</a>
77
<h1 align="center">NEBULA: A Platform for Decentralized Federated Learning</h1>
88

99
<p align="center">
10-
<a href="https://federeratedlearning.inf.um.es">federatedlearning.inf.um.es</a>
10+
<a href="https://nebula-dfl.com">nebula-dfl.com</a> |
11+
<a href="https://nebula-dfl.eu">nebula-dfl.eu</a> |
12+
<a href="https://federeratedlearning.inf.um.es">federatedlearning.inf.um.es</a>
1113
</p>
1214
</p>
1315

1416
## NEBULA Analysis
1517

16-
This folder contains different analysis of the NEBULA platform. The analysis is divided in two parts:
18+
This folder contains different analyses of the NEBULA platform. The analysis is divided into two parts:
1719

1820
- **Data analysis**: This folder contains the analysis of the data included in the platform. The analysis is done using Jupyter Notebooks and Python.
1921

2022
- **Network analysis**: This folder contains the analysis of the network of the platform. The analysis is done using WireShark and Python.
2123

22-
_*NOTE:* Some data is not included in this repository due to privacy reasons. If you want to access to the data, please contact us._
24+
_*NOTE:* Some data is not included in this repository due to privacy reasons. If you want to access the data, please contact us._

docs/_prebuilt/developerguide.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,6 @@ First, you must add the Dataset option in the frontend. Adding the Dataset optio
259259
"EMNIST": ["MLP", "CNN"],
260260
"CIFAR10": ["CNN", "CNNv2", "CNNv3", "ResNet9", "fastermobilenet", "simplemobilenet"],
261261
"CIFAR100": ["CNN"],
262-
"KITSUN": ["MLP"],
263262
}
264263
var datasetSelect = document.getElementById("datasetSelect");
265264
var modelSelect = document.getElementById("modelSelect");

nebula/addons/attacks/attacks.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ def create_attack(engine) -> Attack:
111111
AttackException: If the specified attack name is not found in the `ATTACK_MAP`.
112112
"""
113113
from nebula.addons.attacks.communications.delayerattack import DelayerAttack
114+
from nebula.addons.attacks.communications.floodingattack import FloodingAttack
114115
from nebula.addons.attacks.dataset.datapoison import SamplePoisoningAttack
115116
from nebula.addons.attacks.dataset.labelflipping import LabelFlippingAttack
116117
from nebula.addons.attacks.model.gllneuroninversion import GLLNeuronInversionAttack
@@ -123,6 +124,7 @@ def create_attack(engine) -> Attack:
123124
"Noise Injection": NoiseInjectionAttack,
124125
"Swapping Weights": SwappingWeightsAttack,
125126
"Delayer": DelayerAttack,
127+
"Flooding": FloodingAttack,
126128
"Label Flipping": LabelFlippingAttack,
127129
"Sample Poisoning": SamplePoisoningAttack,
128130
"Model Poisoning": ModelPoisonAttack,
Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,32 @@
11
import logging
2+
import random
23
import types
34
from abc import abstractmethod
4-
import random
55

66
from nebula.addons.attacks.attacks import Attack
77

88

99
class CommunicationAttack(Attack):
10-
def __init__(self, engine,
11-
target_class,
12-
target_method,
13-
round_start_attack,
14-
round_stop_attack,
15-
decorator_args=None,
16-
selectivity_percentage: int = 100,
17-
selection_interval: int = None
18-
):
10+
def __init__(
11+
self,
12+
engine,
13+
target_class,
14+
target_method,
15+
round_start_attack,
16+
round_stop_attack,
17+
attack_interval,
18+
decorator_args=None,
19+
selectivity_percentage: int = 100,
20+
selection_interval: int = None,
21+
):
1922
super().__init__()
2023
self.engine = engine
2124
self.target_class = target_class
2225
self.target_method = target_method
2326
self.decorator_args = decorator_args
2427
self.round_start_attack = round_start_attack
2528
self.round_stop_attack = round_stop_attack
29+
self.attack_interval = attack_interval
2630
self.original_method = getattr(target_class, target_method, None)
2731
self.selectivity_percentage = selectivity_percentage
2832
self.selection_interval = selection_interval
@@ -36,10 +40,10 @@ def __init__(self, engine,
3640
def decorator(self, *args):
3741
"""Decorator that adds malicious behavior to the execution of the original method."""
3842
pass
39-
43+
4044
async def select_targets(self):
4145
if self.selectivity_percentage != 100:
42-
if self.selection_interval:
46+
if self.selection_interval:
4347
if self.last_selection_round % self.selection_interval == 0:
4448
logging.info("Recalculating targets...")
4549
all_nodes = await self.engine.cm.get_addrs_current_connections(only_direct=True)
@@ -55,12 +59,10 @@ async def select_targets(self):
5559
self.targets = await self.engine.cm.get_addrs_current_connections(only_direct=True)
5660

5761
logging.info(f"Selected {self.selectivity_percentage}% targets from neighbors: {self.targets}")
58-
self.last_selection_round+=1
59-
62+
self.last_selection_round += 1
63+
6064
async def _inject_malicious_behaviour(self):
6165
"""Inject malicious behavior into the target method."""
62-
logging.info("Injecting malicious behavior")
63-
6466
decorated_method = self.decorator(self.decorator_args)(self.original_method)
6567

6668
setattr(
@@ -71,15 +73,20 @@ async def _inject_malicious_behaviour(self):
7173

7274
async def _restore_original_behaviour(self):
7375
"""Restore the original behavior of the target method."""
74-
logging.info(f"Restoring original behavior of {self.target_class}.{self.target_method}")
7576
setattr(self.target_class, self.target_method, self.original_method)
7677

7778
async def attack(self):
7879
"""Perform the attack logic based on the current round."""
79-
if self.engine.round == self.round_stop_attack:
80-
logging.info(f"[{self.__class__.__name__}] Restoring original behavior")
80+
if self.engine.round not in range(self.round_start_attack, self.round_stop_attack + 1):
81+
pass
82+
elif self.engine.round == self.round_stop_attack:
83+
logging.info(f"[{self.__class__.__name__}] Stoping attack")
8184
await self._restore_original_behaviour()
82-
elif self.engine.round == self.round_start_attack:
85+
elif (self.engine.round == self.round_start_attack) or (
86+
(self.engine.round - self.round_start_attack) % self.attack_interval == 0
87+
):
8388
await self.select_targets()
84-
logging.info(f"[{self.__class__.__name__}] Injecting malicious behavior")
89+
logging.info(f"[{self.__class__.__name__}] Performing attack")
8590
await self._inject_malicious_behaviour()
91+
else:
92+
await self._restore_original_behaviour()

nebula/addons/attacks/communications/delayerattack.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,21 @@ def __init__(self, engine, attack_params: dict):
2222
self.delay = int(attack_params["delay"])
2323
round_start = int(attack_params["round_start_attack"])
2424
round_stop = int(attack_params["round_stop_attack"])
25-
self.target_percentage = 50#int(attack_params["target_percentage"])
26-
self.selection_interval = 1#int(attack_params["selection_interval"])
25+
attack_interval = int(attack_params["attack_interval"])
26+
self.target_percentage = int(attack_params["target_percentage"])
27+
self.selection_interval = int(attack_params["selection_interval"])
2728
except KeyError as e:
2829
raise ValueError(f"Missing required attack parameter: {e}")
2930
except ValueError:
3031
raise ValueError("Invalid value in attack_params. Ensure all values are integers.")
3132

3233
super().__init__(
3334
engine,
34-
engine._cm,
35+
engine._cm,
3536
"send_model",
3637
round_start,
3738
round_stop,
39+
attack_interval,
3840
self.delay,
3941
self.target_percentage,
4042
self.selection_interval,
@@ -54,13 +56,11 @@ def decorator(self, delay: int):
5456
def decorator(func):
5557
@wraps(func)
5658
async def wrapper(*args, **kwargs):
57-
if len(args) > 1:
59+
if len(args) > 1:
5860
dest_addr = args[1]
59-
if dest_addr in self.targets:
61+
if dest_addr in self.targets:
6062
logging.info(f"[DelayerAttack] Delaying model propagation to {dest_addr} by {delay} seconds")
6163
await asyncio.sleep(delay)
62-
#logging.info(f"[DelayerAttack] Adding delay of {delay} seconds to {func.__name__}")
63-
#await asyncio.sleep(delay)
6464
_, *new_args = args # Exclude self argument
6565
return await func(*new_args)
6666

nebula/addons/attacks/communications/floodingattack.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import asyncio
21
import logging
32
from functools import wraps
43

@@ -19,23 +18,26 @@ def __init__(self, engine, attack_params: dict):
1918
attack_params (dict): Parameters for the attack, including the delay duration.
2019
"""
2120
try:
22-
2321
round_start = int(attack_params["round_start_attack"])
2422
round_stop = int(attack_params["round_stop_attack"])
25-
self.flooding_factor = 100 #int(attack_params["flooding_factor"])
26-
self.target_percentage = 50#int(attack_params["target_percentage"])
27-
self.selection_interval = 1#int(attack_params["selection_interval"])
23+
attack_interval = int(attack_params["attack_interval"])
24+
self.flooding_factor = int(attack_params["flooding_factor"])
25+
self.target_percentage = int(attack_params["target_percentage"])
26+
self.selection_interval = int(attack_params["selection_interval"])
2827
except KeyError as e:
2928
raise ValueError(f"Missing required attack parameter: {e}")
3029
except ValueError:
3130
raise ValueError("Invalid value in attack_params. Ensure all values are integers.")
3231

32+
self.verbose = False
33+
3334
super().__init__(
3435
engine,
35-
engine._cm,
36+
engine._cm,
3637
"send_message",
3738
round_start,
3839
round_stop,
40+
attack_interval,
3941
self.flooding_factor,
4042
self.target_percentage,
4143
self.selection_interval,
@@ -55,16 +57,20 @@ def decorator(self, flooding_factor: int):
5557
def decorator(func):
5658
@wraps(func)
5759
async def wrapper(*args, **kwargs):
58-
if len(args) > 1:
60+
if len(args) > 1:
5961
dest_addr = args[1]
60-
if dest_addr in self.targets:
62+
if dest_addr in self.targets:
6163
logging.info(f"[FloodingAttack] Flooding message to {dest_addr} by {flooding_factor} times")
6264
for i in range(flooding_factor):
63-
logging.info(f"[FloodingAttack] Sending duplicate {i+1}/{flooding_factor} to {dest_addr}")
64-
await func(*args, **kwargs)
65+
if self.verbose:
66+
logging.info(
67+
f"[FloodingAttack] Sending duplicate {i + 1}/{flooding_factor} to {dest_addr}"
68+
)
69+
_, *new_args = args # Exclude self argument
70+
await func(*new_args, **kwargs)
6571
_, *new_args = args # Exclude self argument
6672
return await func(*new_args)
6773

6874
return wrapper
6975

70-
return decorator
76+
return decorator

nebula/addons/attacks/dataset/datapoison.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,22 @@ def __init__(self, engine, attack_params):
4646
engine (object): The training engine object.
4747
attack_params (dict): Dictionary of attack parameters.
4848
"""
49-
super().__init__(engine)
49+
try:
50+
round_start = int(attack_params["round_start_attack"])
51+
round_stop = int(attack_params["round_stop_attack"])
52+
attack_interval = int(attack_params["attack_interval"])
53+
except KeyError as e:
54+
raise ValueError(f"Missing required attack parameter: {e}")
55+
except ValueError:
56+
raise ValueError("Invalid value in attack_params. Ensure all values are integers.")
57+
58+
super().__init__(engine, round_start, round_stop, attack_interval)
5059
self.datamodule = engine._trainer.datamodule
5160
self.poisoned_percent = float(attack_params["poisoned_percent"])
5261
self.poisoned_ratio = float(attack_params["poisoned_ratio"])
5362
self.targeted = attack_params["targeted"]
5463
self.target_label = int(attack_params["target_label"])
5564
self.noise_type = attack_params["noise_type"]
56-
self.round_start_attack = int(attack_params["round_start_attack"])
57-
self.round_stop_attack = int(attack_params["round_stop_attack"])
5865

5966
def apply_noise(self, t, noise_type, poisoned_ratio):
6067
"""

nebula/addons/attacks/dataset/datasetattack.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,17 @@ class DatasetAttack(Attack):
1313
data, potentially impacting the model's training process.
1414
"""
1515

16-
def __init__(self, engine):
16+
def __init__(self, engine, round_start_attack, round_stop_attack, attack_interval):
1717
"""
1818
Initializes the DatasetAttack with the given engine.
1919
2020
Args:
2121
engine: The engine managing the attack context.
2222
"""
2323
self.engine = engine
24-
self.round_start_attack = 0
25-
self.round_stop_attack = 10
24+
self.round_start_attack = round_start_attack
25+
self.round_stop_attack = round_stop_attack
26+
self.attack_interval = attack_interval
2627

2728
async def attack(self):
2829
"""
@@ -32,11 +33,13 @@ async def attack(self):
3233
with a malicious dataset. The attack is stopped when the engine reaches the
3334
designated stop round.
3435
"""
35-
if self.engine.round in range(self.round_start_attack, self.round_stop_attack):
36-
logging.info("[DatasetAttack] Performing attack")
36+
if self.engine.round not in range(self.round_start_attack, self.round_stop_attack + 1):
37+
pass
38+
elif self.engine.round == self.round_stop_attack:
39+
logging.info(f"[{self.__class__.__name__}] Stopping attack")
40+
elif self.engine.round >= self.round_start_attack and ((self.engine.round - self.round_start_attack) % self.attack_interval == 0):
41+
logging.info(f"[{self.__class__.__name__}] Performing attack")
3742
self.engine.trainer.datamodule.train_set = self.get_malicious_dataset()
38-
elif self.engine.round == self.round_stop_attack + 1:
39-
logging.info("[DatasetAttack] Stopping attack")
4043

4144
async def _inject_malicious_behaviour(self, target_function, *args, **kwargs):
4245
"""

nebula/addons/attacks/dataset/labelflipping.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,21 @@ def __init__(self, engine, attack_params):
3232
attack_params (dict): Parameters for the attack, including the percentage of
3333
poisoned data, targeting options, and label specifications.
3434
"""
35-
super().__init__(engine)
35+
try:
36+
round_start = int(attack_params["round_start_attack"])
37+
round_stop = int(attack_params["round_stop_attack"])
38+
attack_interval = int(attack_params["attack_interval"])
39+
except KeyError as e:
40+
raise ValueError(f"Missing required attack parameter: {e}")
41+
except ValueError:
42+
raise ValueError("Invalid value in attack_params. Ensure all values are integers.")
43+
44+
super().__init__(engine, round_start, round_stop, attack_interval)
3645
self.datamodule = engine._trainer.datamodule
3746
self.poisoned_percent = float(attack_params["poisoned_percent"])
3847
self.targeted = attack_params["targeted"]
3948
self.target_label = int(attack_params["target_label"])
4049
self.target_changed_label = int(attack_params["target_changed_label"])
41-
self.round_start_attack = int(attack_params["round_start_attack"])
42-
self.round_stop_attack = int(attack_params["round_stop_attack"])
4350

4451
def labelFlipping(
4552
self,

0 commit comments

Comments
 (0)