Today I wrote a small ethernet switch using my Lisp networking code. A ways back I broke it into separate "low-level networking library" and "partial TCP/IP stack" pieces, which makes it easier to write other applications like this switch. It's surprising how many useful throw-away applications have popped up at work recently when testing our latest network appliance.
I also added support for PF_PACKET sockets in addition to TAP interfaces. PF_PACKET sockets are better for switchey things because they directly access existing interfaces.
The switch is a toy, but I like it because it works and it fits on one screen. (Your screen mileage may vary.)
Here's the code:
;;; switch.lisp -- a toy ethernet switch built on `netlib'.
;;; (netlib source is at http://www.sourceforge.net/projects/slitch/)
(defpackage :switch
(:use :common-lisp :netlib)
(:export :start))
(in-package :switch)
(defvar *ports* nil
"Array of switch ports (network devices).")
(defvar *fdb* (make-hash-table :test #'equalp)
"Forwarding database: maps MAC address onto port number.")
(defun start (&rest devices)
"Start switching packets between DEVICES."
(setq *ports* (concatenate 'vector devices))
(loop for device across *ports*
for port from 0
do (init-port device port)))
(defun init-port (device port)
"Initialize DEVICE as an input port (number PORT)."
(netdev-enable device
;; This function is called when a frame arrives.
;; FRAME is an ethernet frame as an array of bytes.
(lambda (frame) (input frame port))))
(defun input (frame input-port)
"Process a FRAME arriving on INPUT-PORT."
(multiple-value-bind (source destination) (header-addresses frame)
(update-fdb source input-port)
(let ((output-port (where-is destination)))
(cond ((null output-port)
(flood frame input-port))
((/= output-port input-port)
(send frame output-port))))))
(defun header-addresses (frame)
"Return the source and destination addresses from FRAME's ethernet header."
(with-input-from-frame (stream frame)
(let ((header (read-ethh stream)))
(values (ethh-src header) (ethh-dest header)))))
(defun update-fdb (address port)
"Update the forwarding database: ADDRESS is on PORT."
(unless (ethernet-multicast-p address)
(setf (gethash address *fdb*) port)))
(defun where-is (address)
"Return the port that ADDRESS is on, or NIL if unknown."
(gethash address *fdb*))
(defun send (frame output-port)
"Send FRAME to OUTPUT-PORT."
(netdev-tx (aref *ports* output-port) frame))
(defun flood (frame input-port)
"Send FRAME to all ports except INPUT-PORT."
(dotimes (output-port (length *ports*))
(unless (= output-port input-port)
(send frame output-port))))
