uplink.fnl (3895B)
1 (local nl (require :netlink)) 2 (local view (. (require :fennel) :view)) 3 4 ;; $ grep DEVTYPE /sys/class/net/*/uevent 5 ;; /sys/class/net/docker0/uevent:DEVTYPE=bridge 6 ;; /sys/class/net/wlp4s0/uevent:DEVTYPE=wlan 7 ;; /sys/class/net/wwp0s20f0u2i12/uevent:DEVTYPE=wwan 8 ;; (ethernet and loopback devices don't have DEVTYPE) 9 10 (fn devtype [ifname] 11 (with-open [f (io.open (.. "/sys/class/net/" ifname "/uevent") :r)] 12 (accumulate [dtype nil 13 line #(f:read "*l") 14 :until dtype] 15 (let [(name value) (line:match "([^=]+)=(.+)")] 16 (if (= name "DEVTYPE") value dtype))))) 17 18 ;; if the type is wlan, we can get a signal strength indicator 19 ;; from the "quality - link" column of /proc/net/wireless 20 21 (fn wlan-link-quality [ifname] 22 (with-open [f (io.open "/proc/net/wireless" :r)] 23 (accumulate [strength nil 24 line #(f:read "*l") 25 :until strength] 26 ;; "%6s: %04x %3d%c %3d%c %3d%c %6d %6d %6d " 27 ;; "%6d %6d %6d\n", 28 ;; https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/wireless/wext-proc.c#n49 29 30 (let [(name status link level) 31 (line:match "(.-): +(%x+) +(%S-)[ .] +(%S-)[ .]")] 32 (if (= name ifname) 33 (tonumber level) 34 strength))))) 35 36 (fn wlan-link-ssid [ifname] 37 ;; could do this directly using an ioctl 38 ;; http://papermint-designs.com/dmo-blog/2016-08-how-to-get-the-essid-of-the-wifi-network-you-are-connected-to-# 39 (with-open [f (io.popen (.. "iwgetid " ifname " --raw") :r)] 40 (f:read "*l"))) 41 42 (fn get-network-info [event] 43 ;; augments a newlink event with relevant information, if it's 44 ;; sufficiently well-configured to have any 45 46 ;; "up" => administratively up 47 ;; "running" => can actually exchange packets 48 (when (= event.running "yes") 49 (let [dtype (devtype event.name)] 50 (tset event :devtype dtype) 51 (when (= dtype "wlan") 52 (if (not event.ssid) 53 (tset event :ssid (wlan-link-ssid event.name))) 54 (tset event :quality (wlan-link-quality event.name))) 55 (when (= dtype "wwan") 56 ;; for wwan, need to determine how to get strength and carrier name 57 true) 58 )) 59 event) 60 61 (fn uplink [] 62 (let [links {} 63 routes {} 64 sock (nl.socket)] 65 (fn handle-event [event] 66 (match event 67 {:event :newlink} 68 (tset links event.index 69 (match event.up 70 "yes" (get-network-info event) 71 "no" event)) 72 73 {:event :newroute} 74 ;; there may be >1 route to any given destination, 75 ;; (e.g wwan and wlan both have default route) 76 ;; so we only update the routes table if the 77 ;; metric is lower 78 (let [dst (or event.dst "default") 79 existing (. routes dst)] 80 (if (or (not existing) 81 (< event.metric existing.metric)) 82 (tset routes dst event))) 83 )) 84 (each [_ event (ipairs (sock:query ))] 85 (handle-event event)) 86 87 { 88 :refresh #(each [_ event (ipairs (sock:event))] 89 (handle-event event)) 90 :fd (sock:fd) 91 :read (fn [self] 92 (self:refresh) 93 (let [defaultroute routes.default 94 interface (and defaultroute 95 (. links defaultroute.index))] 96 (and interface (= interface.running "yes") 97 (get-network-info interface)))) 98 :wait #(sock:poll 1000) 99 :interface (fn [self ifnum] 100 (. links ifnum)) 101 } 102 )) 103 104 (comment 105 (let [nl (netlunk)] 106 (while (or (nl:wait) true) 107 (nl:refresh) 108 (match (nl:uplink) 109 interface 110 (print "default route through " (view interface)) 111 nil 112 (print "no default route") 113 )))) 114 115 { 116 :new uplink 117 }