- Posted on
- • Artificial Intelligence
Artificial Intelligence SDN on Linux
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Artificial Intelligence SDN on Linux: Build an Adaptive Network Brain with Bash and Open vSwitch
If your network still runs on static ACLs and manual change windows, you’re playing whack‑a‑mole in a world of shape‑shifting traffic. Software‑Defined Networking (SDN) gives you centralized control; Artificial Intelligence (AI) gives you adaptive insight. On Linux, you can combine both—today—to create a small but powerful “self-driving” network lab that observes, learns, and reacts in real time.
This article explains why AI + SDN on Linux is worth your time, and walks you through a hands-on mini-lab using Open vSwitch (OVS) and a Python-based SDN controller (Ryu) that uses a simple ML model to detect bursts and auto-block suspicious flows. All commands are Linux-friendly and shown for apt, dnf, and zypper where relevant.
Why AI + SDN on Linux is a good idea
Centralized control meets probabilistic intelligence. SDN lets you change the network with API calls. AI helps you decide what to change, and when.
Linux gives you the toolbox. Open vSwitch, Python, and controllers like Ryu run natively. You can prototype on a laptop and scale up to servers.
Real value, right now:
- DDoS/DoH anomaly detection from flow/port telemetry
- Auto‑QoS and path selection based on traffic patterns
- Microsegmentation that adapts to workload behavior
- Closed-loop automation (observe → decide → act)
What follows is a minimal “closed loop” you can build in under an hour.
Architecture overview (what you’ll build)
Data plane: Open vSwitch bridge on Linux
Test hosts: Two Linux network namespaces (h1, h2)
Controller: Ryu (OpenFlow 1.3) running a Python app
AI model: A small scikit-learn IsolationForest for anomaly detection
Loop: OVS flows → Ryu stats → AI decision → OVS actions (allow/drop)
1) Install prerequisites
You’ll need root privileges for networking commands (sudo).
Packages:
Open vSwitch
Python 3 + pip
iproute2 (for network namespaces and veth)
Optional: git, tmux
Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y openvswitch-switch python3 python3-pip python3-venv iproute2 git
sudo systemctl enable --now openvswitch-switch
Fedora/RHEL/CentOS/Rocky (dnf):
sudo dnf install -y openvswitch python3 python3-pip iproute git
sudo systemctl enable --now openvswitch
openSUSE/SLE (zypper):
sudo zypper refresh
sudo zypper install -y openvswitch-switch python3 python3-pip iproute2 git
sudo systemctl enable --now openvswitch
Create a Python virtual environment (recommended) and install dependencies:
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install ryu scikit-learn joblib numpy
Note: If you prefer not to use venv, you can install user-wide with:
python3 -m pip install --user ryu scikit-learn joblib numpy
Make sure your PATH includes ~/.local/bin if you go the user route.
2) Build a tiny SDN playground with Linux namespaces + OVS
Create two hosts (h1, h2), connect them to an OVS bridge (br0), and point OVS at our controller.
# Create namespaces
sudo ip netns add h1
sudo ip netns add h2
# Create an OVS bridge
sudo ovs-vsctl --may-exist add-br br0
# Create veth pairs: (h1 <-> br0) and (h2 <-> br0)
sudo ip link add veth-h1 type veth peer name veth-br1
sudo ip link add veth-h2 type veth peer name veth-br2
# Move one end into each namespace
sudo ip link set veth-h1 netns h1
sudo ip link set veth-h2 netns h2
# Attach the other ends to OVS
sudo ovs-vsctl --may-exist add-port br0 veth-br1
sudo ovs-vsctl --may-exist add-port br0 veth-br2
# Bring up the OVS bridge and OVS-facing veth ports
sudo ip link set br0 up
sudo ip link set veth-br1 up
sudo ip link set veth-br2 up
# Assign IPs and bring up interfaces and loopbacks in namespaces
sudo ip netns exec h1 ip addr add 10.0.0.1/24 dev veth-h1
sudo ip netns exec h1 ip link set veth-h1 up
sudo ip netns exec h1 ip link set lo up
sudo ip netns exec h2 ip addr add 10.0.0.2/24 dev veth-h2
sudo ip netns exec h2 ip link set veth-h2 up
sudo ip netns exec h2 ip link set lo up
# Point OVS to a local controller on TCP/6653 and use 'secure' fail mode
sudo ovs-vsctl set-controller br0 tcp:127.0.0.1:6653
sudo ovs-vsctl set-fail-mode br0 secure
Sanity check (no controller yet, so this may hang—run again after the controller starts):
sudo ip netns exec h1 ping -c 1 10.0.0.2
3) Train a tiny anomaly detector
We’ll train a simple IsolationForest on synthetic “normal” flow rates (packets/sec and bytes/sec). In production, train on your own baseline telemetry.
Create train_model.py:
cat > train_model.py << 'EOF'
import numpy as np
from sklearn.ensemble import IsolationForest
from joblib import dump
rng = np.random.default_rng(42)
# Synthetic "normal" traffic: pkts/sec ~ N(60, 15), bytes/sec ~ N(8000, 2000)
pkts = rng.normal(loc=60, scale=15, size=2000).clip(min=1)
bytes_ = rng.normal(loc=8000, scale=2000, size=2000).clip(min=200)
X = np.column_stack([pkts, bytes_])
model = IsolationForest(
n_estimators=200,
contamination=0.05,
random_state=42
).fit(X)
dump(model, "model.joblib")
print("Wrote model.joblib")
EOF
Run it:
python3 train_model.py
This produces model.joblib that classifies “normal-like” rates as 1 and anomalies as -1.
4) Run a Ryu controller that learns and reacts
The app below:
Installs a table-miss rule to forward unknown packets to the controller.
On first packet of a new flow, installs a short-lived flow that uses NORMAL forwarding so traffic flows at line rate.
Periodically queries flow stats; computes simple (pkts/sec, bytes/sec) deltas; classifies with the ML model.
If a flow looks anomalous, it installs a high-priority drop rule for that flow’s source/destination.
Create ai_guard.py:
cat > ai_guard.py << 'EOF'
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER, DEAD_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
from ryu.lib.packet import packet, ethernet, ipv4, tcp, udp
from ryu.lib import hub
from joblib import load
import time
def flow_key_from_pkt(msg):
pkt = packet.Packet(msg.data)
eth = pkt.get_protocol(ethernet.ethernet)
ip4 = pkt.get_protocol(ipv4.ipv4)
if not ip4:
return None
l4 = pkt.get_protocol(tcp.tcp) or pkt.get_protocol(udp.udp)
proto = ip4.proto
sport = getattr(l4, 'src_port', 0)
dport = getattr(l4, 'dst_port', 0)
return (ip4.src, ip4.dst, proto, sport, dport)
class AIGuard(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def __init__(self, *args, **kwargs):
super(AIGuard, self).__init__(*args, **kwargs)
self.datapath = None
self.model = load("model.joblib")
self.prev_counts = {} # flow_key -> (prev_pkts, prev_bytes, prev_time)
self.monitor_thread = hub.spawn(self._monitor)
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
dp = ev.msg.datapath
self.datapath = dp
ofp = dp.ofproto
parser = dp.ofproto_parser
# Table-miss: send to controller
match = parser.OFPMatch()
actions = [parser.OFPActionOutput(ofp.OFPP_CONTROLLER, ofp.OFPCML_NO_BUFFER)]
self.add_flow(dp, priority=0, match=match, actions=actions)
self.logger.info("Installed table-miss rule")
def add_flow(self, dp, priority, match, actions=None, buffer_id=None, idle_timeout=0, hard_timeout=0):
ofp = dp.ofproto
parser = dp.ofproto_parser
inst = []
if actions is not None:
inst = [parser.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions)]
mod = parser.OFPFlowMod(datapath=dp, priority=priority,
match=match, instructions=inst,
buffer_id=buffer_id if buffer_id is not None else ofp.OFP_NO_BUFFER,
idle_timeout=idle_timeout, hard_timeout=hard_timeout)
dp.send_msg(mod)
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def packet_in_handler(self, ev):
msg = ev.msg
dp = msg.datapath
ofp = dp.ofproto
parser = dp.ofproto_parser
key = flow_key_from_pkt(msg)
if not key:
# Non-IPv4: just let NORMAL handle it
actions = [parser.OFPActionOutput(ofp.OFPP_NORMAL)]
out = parser.OFPPacketOut(datapath=dp, buffer_id=ofp.OFP_NO_BUFFER,
in_port=msg.match['in_port'], actions=actions, data=msg.data)
dp.send_msg(out)
return
src, dst, proto, sport, dport = key
# Install a short-lived NORMAL forwarding rule for this 5-tuple
match_fields = dict(eth_type=0x0800, ipv4_src=src, ipv4_dst=dst, ip_proto=proto)
if proto == 6: # TCP
match_fields.update(tcp_src=sport, tcp_dst=dport)
elif proto == 17: # UDP
match_fields.update(udp_src=sport, udp_dst=dport)
match = parser.OFPMatch(**match_fields)
actions = [parser.OFPActionOutput(ofp.OFPP_NORMAL)]
self.add_flow(dp, priority=10, match=match, actions=actions, idle_timeout=30)
# Also forward this initial packet
out = parser.OFPPacketOut(datapath=dp, buffer_id=ofp.OFP_NO_BUFFER,
in_port=msg.match['in_port'], actions=actions, data=msg.data)
dp.send_msg(out)
def _monitor(self):
while True:
if self.datapath and self.datapath.state != DEAD_DISPATCHER:
self._request_flow_stats(self.datapath)
hub.sleep(2)
def _request_flow_stats(self, dp):
parser = dp.ofproto_parser
req = parser.OFPFlowStatsRequest(dp)
dp.send_msg(req)
@set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER)
def flow_stats_reply_handler(self, ev):
now = time.time()
for stat in ev.msg.body:
# Only look at our priority-10 flows with eth_type=IPv4
if stat.priority != 10:
continue
match = stat.match
if match.get('eth_type') != 0x0800:
continue
key = (match.get('ipv4_src'),
match.get('ipv4_dst'),
match.get('ip_proto'),
match.get('tcp_src', match.get('udp_src', 0)),
match.get('tcp_dst', match.get('udp_dst', 0)))
prev = self.prev_counts.get(key)
pkts = stat.packet_count
bytes_ = stat.byte_count
if prev:
prev_pkts, prev_bytes, prev_t = prev
dt = max(0.001, now - prev_t)
pkts_per_s = (pkts - prev_pkts) / dt
bytes_per_s = (bytes_ - prev_bytes) / dt
# Classify using the ML model
X = [[pkts_per_s, bytes_per_s]]
pred = self.model.predict(X)[0] # 1 = normal, -1 = anomaly
if pred == -1:
self.logger.warning("Anomalous flow detected %s: %.1f pps, %.1f Bps -> dropping", key, pkts_per_s, bytes_per_s)
self._drop_flow(ev.msg.datapath, match)
# Update counters
self.prev_counts[key] = (pkts, bytes_, now)
def _drop_flow(self, dp, match):
# Install a higher-priority drop rule for this specific match
parser = dp.ofproto_parser
ofp = dp.ofproto
self.add_flow(dp, priority=200, match=match, actions=[], idle_timeout=60)
# Optionally remove the lower-priority NORMAL rule to hasten effect
mod = parser.OFPFlowMod(datapath=dp,
command=ofp.OFPFC_DELETE_STRICT,
out_port=ofp.OFPP_ANY, out_group=ofp.OFPG_ANY,
priority=10, match=match)
dp.send_msg(mod)
EOF
Run the controller:
source .venv/bin/activate
ryu-manager ai_guard.py
You should see logs about the table-miss rule and stats cycles.
5) Test it: normal vs. “bursty” traffic
Open a second terminal. First, verify connectivity:
sudo ip netns exec h1 ping -c 3 10.0.0.2
Generate steady “normal-ish” traffic (a slow loop of pings):
sudo ip netns exec h1 bash -c 'for i in {1..50}; do ping -c1 -W1 10.0.0.2 >/dev/null; sleep 0.1; done'
Now simulate a burst (many small packets quickly). This crude loop should trip the anomaly detector:
sudo ip netns exec h1 bash -c 'for i in {1..500}; do ping -c1 -W1 10.0.0.2 >/dev/null; done'
Watch the Ryu terminal. You should see log lines like:
WARNING AIGuard: Anomalous flow detected ('10.0.0.1', '10.0.0.2', 1, 0, 0): 250.0 pps, 30000.0 Bps -> dropping
Try ping again and notice it gets blocked (for up to the drop rule idle_timeout):
sudo ip netns exec h1 ping -c 3 10.0.0.2
You can inspect flows and counters at any time:
sudo ovs-ofctl -O OpenFlow13 dump-flows br0
Cleanup when you’re done:
sudo ip netns del h1
sudo ip netns del h2
sudo ip link del br0
sudo ovs-vsctl del-br br0
Real-world tips and patterns
Telemetry at scale:
- OpenFlow counters are fine for a lab. In production, add sFlow or IPFIX/NetFlow exporters and feed an AI pipeline (e.g., Kafka → Python/Scikit/PyTorch).
- OVS sFlow example:
sudo ovs-vsctl -- --id=@sflow create sflow agent=eth0 target=\"udp:127.0.0.1:6343\" sampling=200 polling=10 -- set bridge br0 sflow=@sflowSafer actuation:
- Start with “mark + log” instead of “drop.” For example, match anomalies and place them in a lower-priority QoS queue rather than outright blocking.
Fallbacks and guardrails:
- Keep fail-mode secure and maintain an allowlist. Always provide a manual override path and time-bound rules (idle/hard timeouts).
Performance:
- For ultra-low-latency classification, explore eBPF/XDP for feature extraction and in-kernel counters, then make decisions in user space.
Scale up:
- Swap Ryu for ONOS/OVN-Kubernetes/OVSDB for multi-switch setups. Your “brain” can still be a Python microservice that feeds decisions to the controller northbound.
Conclusion and next steps (CTA)
You just stood up an AI-assisted SDN loop on a single Linux box: OVS switches packets, Ryu controls the plane, and a small ML model decides when to intervene. From here:
Replace synthetic training with your real baseline data.
Add richer features (flow duration, variance, burstiness) and retrain.
Try a different action policy (rate-limit instead of drop).
Containerize the controller and deploy on a lab fabric with multiple OVS bridges.
If you’d like a follow-up post with sFlow/IPFIX ingestion and a Grafana dashboard, let me know. Until then, iterate on this loop: measure better, decide smarter, act safer.