This discrete-event network simulator is based on simpy, which is a general-purpose discrete event simulation framework for Python. ns.py is designed to be flexible and reusable, and can be used to connect multiple networking components together easily, including packet generators, network links, switch elements, schedulers, traffic shapers, traffic monitors, and demultiplexing elements.
Use it to study packet timing, scheduling, queue occupancy, and congestion
control by connecting small components through out and put(packet). The
models deliberately omit some production protocol mechanisms; read the
model notes before interpreting results as TCP, WFQ, RED,
or BBR behavior. The audit inventory
links each module to its tests and reference evidence.
pip install ns.pyThe development target is Python 3.14. Python 3.14.1 is excluded to match NetworkX's supported interpreter versions.
-
Install uv if it's not already on your machine.
-
Sync the project and provision a Python 3.14 virtual environment:
uv sync --locked
-
Run commands through
uv runso they pick up the synced environment. For example:uv run --locked python examples/basic.py
uv sync --locked installs ns.py in editable mode along with its runtime
dependencies, pytest, and ty. Check the package, examples, scripts, and tests
with uv run --locked ty check; CI runs the same command. Every simulator
function has typed parameters and a return type. The out.put(packet) links
and a few caller-supplied adapters remain dynamic so components can still be
composed without a shared class hierarchy. Use uv run --locked for tests and
examples to keep the environment aligned with the checked-in lockfile. Use
uv lock --upgrade when refreshing packages, then review the lockfile and
re-run the checks below.
uv build builds the wheel and source distribution in an isolated build
environment.
The network components that have already been implemented include:
-
Packet: a simple representation of a network packet, carrying its creation time, size, packet id, flow id, source and destination. -
DistPacketGenerator: generates packets according to provided distributions of inter-arrival times and packet sizes. -
TracePacketGenerator: generates packets according to a trace file, with each row in the trace file representing a packet. -
TCPPacketGenerator: models a cumulative-ACK TCP byte stream with configurable congestion control. Seedocs/tcp_timing.mdfor the sender/receiver segmentation, retransmission, application-deadline, and timing contract. -
BBRPacketGenerator: models a paced TCP sender using the educational BBR controller. -
ProxyPacketGenerator: forwards real TCP receive chunks or UDP datagrams into simulation packets whose sizes equal the received payload lengths. -
PacketSink: receives packets and records delay statistics. -
TCPSink: receives packets, records delay statistics, and produces acknowledgements back to a TCP sender. -
ProxySink: forwards simulated payloads to a real TCP or UDP server and returns responses through the simulation; see proxy timing and lifecycle. -
Port: an output port on a switch with a given rate and buffer size (in either bytes or the number of packets), using the simple tail-drop mechanism to drop packets. -
REDPort: an output port on a switch with a given rate and buffer size (in either bytes or the number of packets), using the Random Early Detection (RED) mechanism to drop packets. -
Wire: a network wire (cable) with its propagation delay following a given distribution. There is no need to model the bandwidth of the wire, as that can be modeled by its upstreamPortor scheduling server. -
Splitter: a splitter that simply sends the original packet out of port 1 and sends a copy of the packet out of port 2. -
NWaySplitter: an n-way splitter that sends copies of the packet to n downstream elements. -
TrTCM: a two rate three color marker that marks packets as green, yellow, or red (refer to RFC 2698 for more details). -
RandomDemux: a demultiplexing element that chooses the output port at random. -
FlowDemux: a demultiplexing element that splits packet streams by flow ID. -
FIBDemux: a demultiplexing element that uses a Forwarding Information Base (FIB) to make packet forwarding decisions based on flow IDs. -
TokenBucketShaper: a token bucket shaper. -
TwoRateTokenBucketShaper: a two-rate three-color token bucket shaper with both committed and peak rates/burst sizes. -
SPServer: a Static Priority (SP) scheduler. -
WFQServer: a Weighted Fair Queueing (WFQ) scheduler. -
DRRServer: a Deficit Round Robin (DRR) scheduler. -
VirtualClockServer: a Virtual Clock scheduler. -
SimplePacketSwitch: a packet switch with a FIFO bounded buffer on each of the outgoing ports. -
FairPacketSwitch: a fair packet switch with a choice of a WFQ, DRR, Static Priority or Virtual Clock scheduler, as well as bounded buffers, on each of the outgoing ports. It also shows an example how a simple hash function can be used to map tuples of (flow_id, node_id, and port_id) to class IDs, and then use the parameterflow_classesto activate class-based scheduling rather than flow-based scheduling. -
PortMonitor: records the number of packets in aPort. The monitoring interval follows a given distribution. -
ServerMonitor: records performance statistics in a scheduling server, such asWFQServer,VirtualClockServer,SPServer, orDRRServer.
-
TaggedStore: a sortedsimpy.Storebased on tags, useful in the implementation of WFQ and Virtual Clock. -
Config: a global singleton instance that reads parameter settings from a configuration file. UseConfig()to access the instance globally.
-
basic.py: A basic example that connects two packet generators to a network wire with a propagation delay distribution, and then to a packet sink. It showcasesDistPacketGenerator,PacketSink, andWire. -
overloaded_switch.py: an example that contains a packet generator connected to a downstream switch port, which is then connected to a packet sink. It showcasesDistPacketGenerator,PacketSink, andPort. -
mm1.py: this example shows how to simulate a port with exponential packet inter-arrival times and exponentially distributed packet sizes. It showcasesDistPacketGenerator,PacketSink,Port, andPortMonitor. -
tcp.py: this example shows how a two-hop simple network from a sender to a receiver, via a simple packet forwarding switch, can be configured, and how acknowledgment packets can be sent from the receiver back to the sender via the same switch. The sender uses a TCP as its transport protocol, and the congestion control algorithm is configurable (such as TCP Reno or TCP CUBIC). It showcasesTCPPacketGenerator,CongestionControl,TCPSink,Wire, andSimplePacketSwitch. -
token_bucket.py: this example creates a traffic shaper whose bucket size is the same as the packet size, and whose bucket rate is one half the input packet rate. It showcasesDistPacketGenerator,PacketSink, andTokenBucketShaper. -
two_rate_token_bucket.py: this example creates a two-rate three-color traffic shaper. It showcasesDistPacketGenerator,PacketSink, andTwoRateTokenBucketShaper. -
static_priority.py: this example shows how to use two Static Priority (SP) schedulers to construct a more complex two-layer scheduler, turning onzero_downstream_bufferfor the upstream scheduler andzero_bufferfor the downstream one. It showcasesDistPacketGenerator,PacketSink, andSPServer. -
wfq.py: this example shows how to use the Weighted Fair Queueing (WFQ) scheduler, and how to use a server monitor to record performance statistics with a finer granularity using a sampling distribution. It showcasesDistPacketGenerator,PacketSink,Splitter,WFQServer, andServerMonitor. -
virtual_clock.py: this example shows how to use the Virtual Clock scheduler, and how to use a server monitor to record performance statistics with a finer granularity using a sampling distribution. It showcasesDistPacketGenerator,PacketSink,Splitter,VirtualClockServer, andServerMonitor. -
drr.py: this example shows how to use the Deficit Round Robin (DRR) scheduler. It showcasesDistPacketGenerator,PacketSink,SplitterandDRRServer. -
two_level_drr.py,two_level_wfq.py,two_level_sp.py: these examples have shown how to construct a two-level topology consisting of Deficit Round Robin (DRR), Weighted Fair Queueing (WFQ) and Static Priority (SP) servers. They also show how to use strings for flow IDs and to use dictionaries to provide per-flow weights to the DRR, WFQ, or SP servers, so that group IDs and per-group flow IDs can be easily used to construct globally unique flow IDs. -
red_wfq.py: this example shows how to combine a Random Early Detection (RED) buffer (or a tail-drop buffer) and a WFQ server. The RED or tail-drop buffer serves as an upstream input buffer, configured to recognize that its downstream element has a zero-buffer configuration. The WFQ server is initialized with zero buffering as the downstream element after the RED or tail-drop buffer. Packets will be dropped when the downstream WFQ server is the bottleneck. It showcasesDistPacketGenerator,PacketSink,Port,REDPort,WFQServer, andSplitter, as well as howzero_bufferandzero_downstream_buffercan be used to construct more complex network elements using elementary elements. -
fattree.py: an example that shows how to construct and use a FatTree topology for network flow simulation. It showcasesDistPacketGenerator,PacketSink,SimplePacketSwitch, andFairPacketSwitch. If per-flow fairness is desired,FairPacketSwitchwould be used, along with Weighted Fair Queueing, Deficit Round Robin, or Virtual Clock as the scheduling discipline at each outgoing port of the switch.
Similar to the emulation mode in the ns-3 simulator, ns.py supports an emulation mode that serves as a proxy between a real-world client (such as a modern web browser) and a real-world server (such as a node.js webserver). All incoming traffic from a real-world client are handled by the ProxyPacketGenerator, sent via a simulated network topology, and forwarded by the ProxySink to a real-world server. Here is a high-level overview of the design of ns.py's emulation mode:
examples/real_traffic/proxy.py has been provided as an example that shows how a real-world client and server can communicate using a simulated network environment as the proxy, and how ProxyPacketGenerator and ProxySink are to be used to achieve this objective.
The adapters use a plain SimPy environment with cooperative socket polling.
Call both proxies' close() methods in finally after a run; stopping the
environment alone does not close sockets. TCP receives are stream chunks, not
application messages, and simulated loss is not repaired by a proxy-internal TCP
model. See Local socket proxies for the supported behavior.
A simple echo client and echo server have been provided for an example demonstration how the proxy works. To run this example with the provided echo client and echo server, start the server first:
python examples/real_traffic/tcp_echo_server.py 10000The TCP echo server will listen on port 10000 on localhost.
Now run the provided simple example for a TCP ns.py proxy:
python examples/real_traffic/proxy.py 5000 localhost 10000 tcpThis TCP proxy will now listen on port 5000, and redirects all traffic to localhost:10000, which is where the TCP echo server is.
Finally, run the TCP echo client:
python examples/real_traffic/tcp_echo_client.py localhost 5000It will send one simple message to port 5000, where the TCP proxy is.
To use an UDP proxy instead, first run the UDP echo server, which listens on port 10000 on localhost:
python examples/real_traffic/udp_echo_server.py 10000Then run the UDP ns.py proxy on port 5000, asking it to redirect all traffic to localhost:10000, where the UDP echo server is.
python examples/real_traffic/proxy.py 5000 localhost 10000 udpFinally, run the UDP echo client:
python examples/real_traffic/udp_echo_client.py localhost 5000 Hello WorldA simple HTTPS server has been provided in examples/real_traffic. To use it to test the emulation mode, you will need to generate a self-signed server certificate first:
openssl req -new -x509 -keyout server_cert.pem -out server_cert.pem -days 365 -nodesThen run the HTTPS server:
python examples/real_traffic/https_server.py 4443 server_cert.pemNow you can run the ns.py proxy with the HTTPS server as its destination:
python examples/real_traffic/proxy.py 5000 localhost 4443Finally, run a curl HTTPS client to connect to the HTTP server:
curl -v https://localhost:5000 --insecureStart with the SimPy tutorial. Components that wait for packets or model elapsed time use generator functions as SimPy processes. A constructor typically registers its process:
self.action = env.process(self.run())A process yields an event, such as store.get() or env.timeout(delay), and
resumes when that event completes. Other processes can then run at the same
simulation time or advance the simulated clock. This concurrency uses simulated
time; ordinary simulations need not wait for wall-clock time to pass.
A FIFO serializer can express its timing directly:
packet = yield self.store.get()
yield self.env.timeout(packet.size * 8.0 / self.rate)
self.out.put(packet)Packet sizes are bytes and link rates are bits/second, so the factor of eight
converts bytes to bits. Most clocks use seconds. BBR pacing and StackDelayer
use bytes/second instead; check each component's documented units.
Each repeating process path must yield an event. An idle server should wait for
work; repeatedly yielding zero-time events without eventual time progress can
still stall a simulation. Schedulers may use a zero-time selection wait to admit
already scheduled same-time arrivals, then perform nonpreemptive service. WFQ
and Virtual Clock wake separately from selection so an idle heap get() cannot
reserve a packet before other same-time arrivals are considered.
Demultiplexers, splitters, sinks, and markers normally act synchronously inside
put() and need no process of their own. A downstream put() may call back
immediately, so register send/accounting state before forwarding. Connect an
output before running the environment, for example:
generator.out = port
port.out = sink
env.run(until=100)Numeric env.run(until=100) observes events before time 100; ordinary events
exactly at that boundary remain pending. Use a finite source and env.run() to
drain a network with no forever-running monitor. See
examples/composed_network.py for a finite
composition and model notes for shared-buffer ownership.
Flow IDs identify routes and statistics. Schedulers use flow_classes(packet)
to map flows to scheduling classes without rewriting packet.flow_id. Lists
index configured integer class IDs; dictionaries support named or sparse class
IDs. Flows mapped to one class share its FIFO, priority, or finish-tag history,
according to the chosen discipline.
Run the regression suite with:
uv run pytest -qCI also runs the finite basic, TCP, FatTree, and composed-network scenarios with Matplotlib's
headless Agg backend and a 90-second timeout per process:
uv run --locked python scripts/run_examples.py
uv buildReal-traffic proxy and server examples need their local client/server setup described above and are run separately.