Nama : Satria Winekas Herlambang

NIM : 201012410046

Dokumentasi ini merupakan tugas mata kuliah Advanced Network Security. Model machine learning ini mendeteksi serangan DDoS pada dataset CICIDS 2017

<aside> ⛓️ Link Github : https://github.com/ChernobylWaste/P2PDocker Link Dataset : https://drive.google.com/file/d/1yIByIKTYc1iCXxm9Naxeh2PaBrZH3CaS/view?usp=sharing

</aside>

Konfigurasi Jaringan P2P menggunakan Docker

File yang diperlukan untuk membuat jaringan P2P menggunakan docker yaitu :

  1. docker-compose.yml
  2. main.py
  3. Dockerfile

Berikut penjelasan detail terkait codingan pada file konfigurasi.

# Menentukan versi Docker Compose yang digunakan
version: '3'

# Mendefinisikan layanan-layanan yang akan dijalankan
services:
  # Inisialisasi service pertama dengan nama node1
  node1:
    # Menggunakan Dockerfile di direktori saat ini untuk membangun image
    build: .
    # Perintah yang akan dijalankan saat container node1 dimulai
    command: python ./main.py node1
    # Port yang akan diekspos oleh container
    expose:
      - 65434
    # Menghubungkan direktori lokal dengan direktori files di dalam container
    volumes:
      - ./node1:/files
    # Jaringan p2p_network
    networks:
      - p2p_network

  # Inisialisasi service kedua dengan nama node2
  node2:
    build: .
    command: python ./main.py node2
    expose:
      - 65434
    volumes:
      - ./node2:/files
    networks:
      - p2p_network

  # Inisialisasi service ketiga dengan nama node3
  node3:
    build: .
    command: python ./main.py node3
    expose:
      - 65434
    volumes:
      - ./node3:/files
    networks:
      - p2p_network

  # Inisialisasi service keempat dengan nama node4
  node4:
    build: .
    command: python ./main.py node4
    expose:
      - 65434
    volumes:
      - ./node4:/files
    networks:
      - p2p_network

# Inisialisasi jaringan yang digunakan oleh services di atas
networks:
  # Jaringan dengan nama p2p_network
  p2p_network:
    # Konfigurasi IP Address Management (IPAM) untuk jaringan di container
    ipam:
      config:
        # Subnet yang digunakan oleh jaringan
        - subnet: 192.168.0.0/24
          # Gateway yang digunakan oleh jaringan
          gateway: 192.168.0.1

# Impor modul zmq untuk komunikasi jaringan menggunakan ZeroMQ
import zmq
# Impor modul os untuk berinteraksi dengan sistem operasi
import os
# Impor modul sys untuk mengakses argumen baris perintah
import sys
# Impor modul time untuk menangani operasi waktu
import time
# Impor modul hashlib untuk menghitung hash file
import hashlib
# Impor modul Thread dari threading untuk menjalankan beberapa proses secara bersamaan
from threading import Thread

# Konfigurasi node dan port
NODES = ["node1", "node2", "node3", "node4"]  # Daftar node yang terlibat dalam jaringan
PORT = 65434  # Port yang digunakan untuk komunikasi antar node

# Direktori untuk menyimpan file
FILE_DIR = "/files"  # Direktori tempat file-file akan disimpan

# Node saat ini
me = str(sys.argv[1])  # Nama node saat ini yang diberikan sebagai argumen baris perintah

# Fungsi untuk menghitung hash file
def calculate_file_hash(filepath):
    # Buat objek hasher MD5
    hasher = hashlib.md5()
    # Buka file dalam mode baca biner
    with open(filepath, "rb") as f:
        buf = f.read()  # Baca isi file
        hasher.update(buf)  # Update hasher dengan isi file
    return hasher.hexdigest()  # Kembalikan hash dalam bentuk heksadesimal

# Fungsi untuk mengirim file
def send_file(filename, data, socket):
    # Kirim nama file dan data file sebagai multipart message
    socket.send_multipart([filename.encode(), data])

# Fungsi untuk menyimpan file
def save_file(filename, data):
    # Tentukan path lengkap file
    filepath = os.path.join(FILE_DIR, filename)
    # Buat direktori jika belum ada
    if not os.path.exists(FILE_DIR):
        os.makedirs(FILE_DIR)

    # Periksa apakah file sudah ada
    if os.path.exists(filepath):
        # Jika file dengan nama sama ada, periksa hash
        existing_hash = calculate_file_hash(filepath)
        new_hash = hashlib.md5(data).hexdigest()
        if existing_hash == new_hash:
            print(f"File {filename} sudah ada, tidak di-overwrite.")
            return
    # Simpan file baru
    with open(filepath, "wb") as f:
        f.write(data)
    print(f"File {filename} disimpan.")

# Fungsi server untuk mengirim file
def server():
    # Buat konteks ZeroMQ
    context = zmq.Context()
    # Buat socket PUSH untuk mengirim pesan
    server_socket = context.socket(zmq.PUSH)
    # Bind socket ke port yang ditentukan
    server_socket.bind(f"tcp://*:{PORT}")
    print(f"Server di {me} berjalan...")
    
    known_files = {}  # Dictionary untuk melacak file yang sudah dikirim

    while True:
        # Daftar file dalam direktori
        files_in_dir = os.listdir(FILE_DIR)
        for filename in files_in_dir:
            # Abaikan file sementara seperti .swp atau file tersembunyi
            if filename.endswith('.swp') or filename.startswith('.'):
                print(f"Skipping temporary file: {filename}")
                continue

            # Tentukan path lengkap file
            filepath = os.path.join(FILE_DIR, filename)
            # Hitung hash file
            file_hash = calculate_file_hash(filepath)
            # Jika file baru atau file berubah
            if filename not in known_files or known_files[filename] != file_hash:
                # Buka file dalam mode baca biner
                with open(filepath, "rb") as f:
                    data = f.read()  # Baca isi file
                # Kirim file ke setiap node lainnya
                for node in NODES:
                    if node != me:
                        send_file(filename, data, server_socket)
                # Update dictionary dengan hash file terbaru
                known_files[filename] = file_hash
                print(f"File {filename} dikirim ke node lainnya.")
        # Tunggu 5 detik sebelum memeriksa kembali
        time.sleep(5)

# Fungsi klien untuk menerima file
def client():
    # Buat konteks ZeroMQ
    context = zmq.Context()
    # Buat socket PULL untuk menerima pesan
    client_socket = context.socket(zmq.PULL)
    # Hubungkan ke setiap node lainnya
    for node in NODES:
        if node != me:
            client_socket.connect(f"tcp://{node}:{PORT}")
    print(f"Klien di {me} berjalan dan menunggu file...")

    while True:
        # Terima pesan multipart (nama file dan data file)
        message = client_socket.recv_multipart()
        filename, data = message
        # Simpan file yang diterima
        save_file(filename.decode(), data)

# Jalankan server dan klien secara paralel
if __name__ == "__main__":
    # Buat thread untuk server
    Thread(target=server).start()
    # Buat thread untuk klien
    Thread(target=client).start()

# Menggunakan image Python terbaru sebagai basis image
FROM python:latest

# Install-an Opsional
# Memperbarui daftar paket dan menginstal nano
RUN apt update && apt install -y nano
# Memperbarui daftar paket dan menginstal net-tools
RUN apt update && apt install -y net-tools
# Memperbarui daftar paket dan menginstal iputils-ping
RUN apt update && apt install -y iputils-ping

# Menyalin file utama (main.py) ke dalam image Docker
ADD main.py .

# Mendefinisikan volume untuk menyimpan file yang akan disinkronisasi
VOLUME ["/files"]

Cara Menjalankan Codingannya

Pastikan sudah menginstall docker di komputer. Berikut cara menginstall docker desktop di komputer windows.

Windows

Setelah docker desktop terinstall dan semua file codingan telah di buat, berikut cara menjalankan codingan tersebut.

  docker-compose up --build

image.png

image.png

Mencoba Sinkronisasi File Antar Node

docker container ls

image.png

docker exec -it <container-name> bash 

contoh ingin masuk ke bash node 1:

docker exec -it p2pdocker-node1-1 bash 

image.png

cd files
nano node1.txt

image.png

Berikut contoh isi file node1.txt

Hallo, ini file dari node 1

Mengecek File Sudah Tersinkron

ada dua cara melihat file yang sudah dibuat sudah tersinkron atau belum.

Cara Pertama:

image.png

Folder tersebut merupakan folder /files dari tiap node.

image.png

Gambar tersebut menunjukkan bahwasannya file telah tersinkron di tiap node nya.

Cara kedua:

docker exec -it p2pdocker-node2-1 bash 
cd files
ls

image.png

cat node1.txt

Konfigurasi Federated Learning

Jika P2P sudah berhasil dilakukan, langkah selanjutnya bisa melakukan federated learning menggunakan model yang akan dipakai. Pada dokumentasi ini, model machine learning yang dipakai yaitu XGBoost. Untuk agregasi modelnya menggunakan FedAVG.

File machine learning yang akan digunakan yaitu :

  1. lokalml.py
  2. fedavg.py
  3. requirements.txt

Berikut codingan dan penjelasannya :

# Memanggil library yang akan digunakan.
import warnings
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
import xgboost as xgb
import matplotlib.pyplot as plt
import seaborn as sns
import sklearn

# Mengabaikan peringatan FutureWarning dan UserWarning untuk membersihkan output
warnings.filterwarnings('ignore', category=FutureWarning)
warnings.filterwarnings('ignore', category=UserWarning)

data_path = './Friday-WorkingHours-Afternoon-DDos.pcap_ISCX.csv'
df = pd.read_csv(data_path)

# Menghapus spasi di awal kalimat
df.columns = df.columns.str.lstrip()

# Menampilkan informasi dataset, nama kolom, dan distribusi label
print("Jumlah Label dalam Dataset:")
print(df['Label'].value_counts())

# Memisahkan fitur (X) dan label (y) dari dataset
X = df.drop('Label', axis=1)
y = df['Label']

# Mengganti nilai tak terhingga dengan NaN dan mengisi NaN dengan median fitur
X.replace([np.inf, -np.inf], np.nan, inplace=True)
X.fillna(X.median(), inplace=True)

# Membuat objek LabelEncoder yang akan digunakan untuk melakukan encoding pada label kategorikal.
label_encoder = LabelEncoder()
# Mengidentifikasi kategori unik dalam label y dan menetapkan nilai numerik untuk setiap kategori.
y_encoded = label_encoder.fit_transform(y)

# Menormalisasi fitur menggunakan StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# Membuat objek XGBClassifier dengan beberapa parameter yang ditentukan.
# Parameter use_label_encoder=False untuk menentukan apakah XGBClassifier akan menggunakan LabelEncoder internal untuk mengubah label kategorikal menjadi nilai numerik.
# Parameter eval_metric='logloss' untuk menentukan metrik evaluasi yang digunakan selama pelatihan model.
# Parameter random_state=42 untuk menentukan seed untuk generator random,
model = xgb.XGBClassifier(use_label_encoder=False, eval_metric='logloss', random_state=42)

# Training model XGBoost
try:
    model.fit(X_scaled, y_encoded)
# Jika training ada error, maka akan mengoutput kannya. Digunakannya exception karena ada bug dari XGBoost dengan skilearn nya.
except Exception as e:
    print(f"Error pada saat memuat model fitting: {e}")

# Menghitung dan menampilkan Importance Score pada fitur teratas
feature_importances = model.feature_importances_
feature_scores = pd.DataFrame({'Fitur': df.columns[:-1], 'Importance': feature_importances})
feature_scores = feature_scores.sort_values(by='Importance', ascending=False)
print("\\nFeature Importance Score:")
print(feature_scores.head(20))

# Menyimpan 20 fitur dengan importance score teratas sebagai rekomendasi fitur
recommended_features = feature_scores['Fitur'].head(20).values
print("\\nRekomendasi Fitur untuk Deteksi/Klasifikasi Serangan DDoS:")
print(recommended_features)

# Memilih fitur teratas
X_selected = df[recommended_features]

# Mengganti nilai tak terhingga dengan NaN
X_selected.replace([np.inf, -np.inf], np.nan, inplace=True)

# Mengisi NaN dengan median
X_selected.fillna(X_selected.median(), inplace=True)

# Menormalisasi fitur
X_selected_scaled = scaler.fit_transform(X_selected)

# Memisahkan data menjadi set pelatihan 80% dan pengujian 20%
X_train, X_test, y_train, y_test = train_test_split(X_selected_scaled, y_encoded, test_size=0.2, random_state=42)

# Mendefinisikan parameter untuk model XGBoost
params = {
    # Menentukan tujuan dari model, yaitu klasifikasi multi-kelas.
    # 'multi:softprob' menghasilkan probabilitas untuk setiap kelas.
    'objective': 'multi:softprob',

    # Menentukan jumlah kelas dalam klasifikasi.
    # Ini diambil dari jumlah kelas yang dihasilkan oleh label encoder.
    'num_class': len(label_encoder.classes_),

    # Metode evaluasi untuk mengukur performa model.
    # 'mlogloss' (multiclass logarithmic loss) digunakan untuk klasifikasi multi-kelas.
    'eval_metric': 'mlogloss',

    # Nilai ini mengontrol seberapa cepat model akan beradaptasi dengan data.
    'learning_rate': 0.1,

    # Kedalaman maksimum untuk setiap tree dalam model. Parameter ini membantu mengontrol overfitting.
    'max_depth': 6,

    # Proporsi fitur yang akan dipilih secara acak untuk digunakan dalam setiap pohon. 0.8 berarti 80% fitur dipilih.
    'colsample_bytree': 0.8,

    # Proporsi sampel yang digunakan untuk membuat setiap pohon. 0.8 berarti 80% data digunakan.
    'subsample': 0.8,

    # Seed atau nilai acak untuk menjaga konsistensi hasil model.
    'random_state': 42
}

# Membuat DMatrix untuk XGBoost dan menyiapkan watchlist untuk evaluasi
dtrain = xgb.DMatrix(X_train, label=y_train)
dtest = xgb.DMatrix(X_test, label=y_test)
watchlist = [(dtrain, 'train'), (dtest, 'eval')]
evals_result = {}

# Melatih model XGBoost dengan parameter dan watchlist
model_final = xgb.train(
    params,
    dtrain,
    num_boost_round=35, # Mengatur untuk seberapa banyak data ingin di training
    evals=watchlist,
    evals_result=evals_result,
    verbose_eval=1
)

# Menyimpan model yang dilatih ke file JSON
model_file_path = "model.json"
model_final.save_model(model_file_path)
print(f"Model saved to {model_file_path}")

# Melakukan prediksi
y_pred_prob = model_final.predict(dtest)
y_pred_encoded = np.argmax(y_pred_prob, axis=1)
y_pred = label_encoder.inverse_transform(y_pred_encoded)

# Menghitung akurasi model
accuracy = accuracy_score(y_test, y_pred_encoded)
print(f"Test Accuracy: {accuracy * 100:.2f}%")
print("Classification Report:")
print(classification_report(y_test, y_pred_encoded, target_names=label_encoder.classes_, digits=4))

# Membuat untuk evaluasi model
conf_matrix = confusion_matrix(y_test, y_pred_encoded)
reordered_indices = [list(label_encoder.classes_).index(label) for label in ['DDoS', 'BENIGN']]
conf_matrix = conf_matrix[reordered_indices, :][:, reordered_indices]

plt.figure(figsize=(10, 7))
sns.heatmap(conf_matrix, annot=True, fmt='d', cmap='Blues',
            xticklabels=['DDoS', 'BENIGN'],
            yticklabels=['DDoS', 'BENIGN'])
plt.title('Confusion Matrix')
plt.xlabel('Predicted')
plt.ylabel('True')

# Menyimpan confusion matrix menjadi png
conf_matrix_file_path = "files/Confusion_matrix.png"
plt.savefig(conf_matrix_file_path)
print(f"Reordered confusion matrix saved to {conf_matrix_file_path}")

plt.show()
import warnings
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
import xgboost as xgb
import matplotlib.pyplot as plt
import seaborn as sns
import sklearn

# Mengabaikan peringatan FutureWarning dan UserWarning untuk membersihkan output
warnings.filterwarnings('ignore', category=FutureWarning)
warnings.filterwarnings('ignore', category=UserWarning)

data_path = './Friday-WorkingHours-Afternoon-DDos.pcap_ISCX.csv'
df = pd.read_csv(data_path)

# Menghapus spasi di awal kalimat
df.columns = df.columns.str.lstrip()

# Menampilkan informasi dataset, nama kolom, dan distribusi label
print("Jumlah Label dalam Dataset:")
df['Label'].value_counts()
# Menampilkan informasi dataset, nama kolom, dan distribusi label
print("Jumlah Label dalam Dataset:")
print(df['Label'].value_counts())

# Memisahkan fitur (X) dan label (y) dari dataset
X = df.drop('Label', axis=1)
y = df['Label']

# Mengganti nilai tak terhingga dengan NaN dan mengisi NaN dengan median fitur
X.replace([np.inf, -np.inf], np.nan, inplace=True)
X.fillna(X.median(), inplace=True)

# Membuat objek LabelEncoder yang akan digunakan untuk melakukan encoding pada label kategorikal.
label_encoder = LabelEncoder()
# Mengidentifikasi kategori unik dalam label y dan menetapkan nilai numerik untuk setiap kategori.
y_encoded = label_encoder.fit_transform(y)

# Menormalisasi fitur menggunakan StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# Membuat objek XGBClassifier dengan beberapa parameter yang ditentukan.
# Parameter use_label_encoder=False untuk menentukan apakah XGBClassifier akan menggunakan LabelEncoder internal untuk mengubah label kategorikal menjadi nilai numerik.
# Parameter eval_metric='logloss' untuk menentukan metrik evaluasi yang digunakan selama pelatihan model.
# Parameter random_state=42 untuk menentukan seed untuk generator random,
model = xgb.XGBClassifier(use_label_encoder=False, eval_metric='logloss', random_state=42)

# Training model XGBoost
try:
    model.fit(X_scaled, y_encoded)
# Jika training ada error, maka akan mengoutput kannya. Digunakannya exception karena ada bug dari XGBoost dengan skilearn nya.
except Exception as e:
    print(f"Error pada saat memuat model fitting: {e}")

# Menghitung dan menampilkan Importance Score pada fitur teratas
feature_importances = model.feature_importances_
feature_scores = pd.DataFrame({'Fitur': df.columns[:-1], 'Importance': feature_importances})
feature_scores = feature_scores.sort_values(by='Importance', ascending=False)
print("\\nFeature Importance Score:")
print(feature_scores.head(20))

# Menyimpan 20 fitur dengan importance score teratas sebagai rekomendasi fitur
recommended_features = feature_scores['Fitur'].head(20).values
print("\\nRekomendasi Fitur untuk Deteksi/Klasifikasi Serangan DDoS:")
print(recommended_features)

# Memilih fitur teratas
X_selected = df[recommended_features]

# Mengganti nilai tak terhingga dengan NaN
X_selected.replace([np.inf, -np.inf], np.nan, inplace=True)

# Mengisi NaN dengan median
X_selected.fillna(X_selected.median(), inplace=True)

# Menormalisasi fitur
X_selected_scaled = scaler.fit_transform(X_selected)

# Memisahkan data menjadi set pelatihan 80% dan pengujian 20%
X_train, X_test, y_train, y_test = train_test_split(X_selected_scaled, y_encoded, test_size=0.2, random_state=42)

# Memuat model dari masing-masing node
print("\\nSedang memuat model 1, 2, dan 3")

model1 = xgb.Booster()
model1.load_model('model1.json')

model2 = xgb.Booster()
model2.load_model('model2.json')

model3 = xgb.Booster()
model3.load_model('model3.json')

print("Model berhasil dimuat!\\n")

# Membuat DMatrix untuk prediksi
dtest = xgb.DMatrix(X_test, label=y_test)

# Prediksi dengan setiap model
y_pred1 = model1.predict(dtest)
y_pred2 = model2.predict(dtest)
y_pred3 = model3.predict(dtest)

# Agregasi prediksi menggunakan rata-rata (FedAvg)
y_pred_avg = (y_pred1 + y_pred2 + y_pred3) / 3

# Mengonversi prediksi rata-rata ke kelas
y_pred_class = np.argmax(y_pred_avg, axis=1)

# Evaluasi model teragregasi
accuracy = accuracy_score(y_test, y_pred_class)
print(f"Test Accuracy (FedAvg Aggregation): {accuracy * 100:.2f}%")

# Evaluasi dengan classification report
class_report = classification_report(y_test, y_pred_class, target_names=label_encoder.classes_, digits=4)
print("Classification Report:")
print(class_report)

# Confusion matrix
conf_matrix = confusion_matrix(y_test, y_pred_class)

# Ubah urutan label untuk memindahkan DDoS ke baris dan kolom pertama
reordered_indices = [list(label_encoder.classes_).index('DDoS'), list(label_encoder.classes_).index('BENIGN')]
reordered_conf_matrix = conf_matrix[reordered_indices, :][:, reordered_indices]

# Visualisasi confusion matrix yang telah diatur ulang
plt.figure(figsize=(10, 7))
sns.heatmap(reordered_conf_matrix, annot=True, fmt='d', cmap='Blues',
            xticklabels=['DDoS', 'BENIGN'], 
            yticklabels=['DDoS', 'BENIGN'])
plt.title('Confusion Matrix Untuk Aggregated Model')
plt.xlabel('Predicted')
plt.ylabel('True')

# Simpan confusion matrix ke file
reordered_conf_matrix_file = "Confusion Matrix.png"
plt.savefig(reordered_conf_matrix_file, dpi=300, bbox_inches='tight')
print(f"File Gambar Confusion Matrix Tersimpan dengan Nama: {reordered_conf_matrix_file}")

plt.show()
pandas
numpy
scikit-learn
xgboost
matplotlib
seaborn
pyzmq

Memasukkan File Python ke Container

Untuk memasukkan semua file Python ke container, cara nya menambahkan command di dockerfile nya sebagai berikut :

# Menggunakan image Python terbaru sebagai basis image
FROM python:latest

# Install-an Opsional
# Memperbarui daftar paket dan menginstal nano
RUN apt update && apt install -y nano
# Memperbarui daftar paket dan menginstal net-tools
RUN apt update && apt install -y net-tools
# Memperbarui daftar paket dan menginstal iputils-ping
RUN apt update && apt install -y iputils-ping

# Menyalin file utama (main.py) ke dalam image Docker
ADD main.py .

# Codingan untuk Federated learning
# Menyalin file requirements.txt ke dalam image Docker
COPY requirements.txt .
# Menginstal dependensi yang terdaftar di requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
# Menyalin file fedavg.py ke dalam image Docker
COPY fedavg.py .
# Menyalin file tes.py ke dalam image Docker
COPY lokal.py .
# Menyalin file Friday-WorkingHours-Afternoon-DDos.pcap_ISCX.csv ke dalam image Docker
COPY Friday-WorkingHours-Afternoon-DDos.pcap_ISCX.csv .

# Mendefinisikan volume untuk menyimpan file yang akan disinkronisasi
VOLUME ["/files"]

Setelah Dockerfile di update, bisa di deploy dockernya dengan cara yang serupa seperti mendeploy arsitektur jaringan P2P.

Menjalankan ML di Lokal Node

Sebelum menjalankan ML di lokal node, harus diperhatikan apakah file ML nya sudah tersedia di tiap node atau tidak. Dengan cara sebagai berikut :

docker exec -it <container-name> bash 
nano lokal.py
# Menyimpan model yang dilatih ke file JSON
model_file_path = "<namamodel>.json"
model_final.save_model(model_file_path)
print(f"Model saved to {model_file_path}")
python lokalml.py
ls
mv <namafile>.json files