meraki-builder
meraki-builder copied to clipboard
Web UI for switch management
There is no UI in the firmware to configure the switch. Users are required to use SSH if they want to make changes to the switch configuration. This is not a great user experience, as even simple layer 2 switches include web management (though often not SSH).
Please see #2 for my thoughts on how switch configuration could work on the backend, and how it might make development of a management UI simpler.
I am not good at front-end development, nor do I have time/desire to write a Web UI for the firmware. This issue is a placeholder for anyone else who is interested in writing a management UI.
I'm no good at anything but I can write a developer's version of the UI, can you add a tag of "needing-help" to those issues you think you can be helped with? I'd like to do my part to keep these switches out of a landfill and get myself cheaper switches.
Edit: is there some specific software that could be supported or would running docker on device be a possibility.
is there some specific software that could be supported or would running docker on device be a possibility.
There is no support for Docker, nor will there be as the switch runs a fixed kernel version (3.18.123) and has very limited resources (~5MB flash available, 128MB of RAM).
There is uhttpd present in the firmware. Looking at the source code, uhttpd appears to have support for HTTP POST/PUT, but perhaps that requires Lua or a more complex configuration than I have currently.
(~5MB flash available, 128MB of RAM).
Yeah, that's gonna be a tight fit even with PHP... I wonder if we could just expose a basic serial interface or something.
I'm gonna go hunt down for one of these switches.
Edit: Is it possible to extract an image from one of these devices (just an vmdk equivalent?). I'd love to take a crack at it using a VM (5mb is enough for a static site but you need something to execute your commands)
Edit: Is it possible to extract an image from one of these devices (just an vmdk equivalent?). I'd love to take a crack at it using a VM (5mb is enough for a static site but you need something to execute your commands)
There is an early image that Hal built and linked in the build instructions Google Doc. Just checked and lighthttpd is on there under /usr/sbin, and starts up with a basic config happily enough.
Buildroot offers several options for web servers, including lighthttpd instead of uhttpd is possible.
(5mb is enough for a static site but you need something to execute your commands)
5MB is currently the space allocated to JFFS2, which is the user-storage portion of the firmware. That is to say, you can use all 5MB, but then you won't have space for any persistent storage.
My thinking was as follows:
- JSON configuration file (retrieved with GET, edited in the static site with JS, POSTed back to the switch)
- Very small daemon running on the switch that uses inotify or polling to notice configuration changes and apply them
This is very simple and won't require anything advanced running on the switch. Using inotify in C or bash is simple and will not consume much space. Likewise, using GET/POST in a web server is simple and probably supported by the HTTP options available in buildroot, we simply need to find the right choice and build it.
IMHO, the best way forward is an HTTP server and a static website that runs on the client with JS to manipulate settings. Anything else requires too much space and/or increases complexity.
Is it possible to extract an image from one of these devices (just an vmdk equivalent?).
Yes and no. If you want similar user space, you can use buildroot to build an x86 VM with the same commands and storage size.
You will not have the click
kernel module, since that is tied to the 3.18.123 kernel from Cisco, and requires the Vitesse ASIC to be present. So you will be able to test a web server and size constraints, but it will not be sufficient to test applying the actual configuration.
JSON configuration file (retrieved with GET, edited in the static site with JS, POSTed back to the switch)
Hal sent me an email with a cheap testing switch I can use.
Based on the existing from the configuration ticket, I'm going with this. The UI will boot by default with a credentials of "admin"/"admin". By default it will assume you have 1 port (at least so that you can get it going)
The UI will allow you to change the credentials (logging in assumes you have proper rights) You will be able to add ports from the UI (default: 1 port) You will be able to attach the ports to a VLAN (default: tag, vlan 1) You will be able to enable stp per port (default: on) You will be able to enable lacp per port (default: off) You will be able to add PoE per port (default, off, with option to go to 802.3af or 802.3at)
I'm thinking of just a basic static site that just reads in the config. Pressing 'Save' will push the config and re-read it.
I'm going to get lighthttpd running locally to see what this looks like.
@halmartin @randlor I've pushed up a basic version of what I have to the freeraki-ui repo here (https://github.com/WriteCodeEveryday/freeraki-ui). It retrieves a version of the config and allows you to post it back to the switch. Weighs in at around 200kb. If you'd like to try it, you can use any static file hosting setup (python3 -m http.server).
Not super secure since there's no server side component but it can do the job for now.
I'm gonna mess around the lighttpd and see what it takes to get a basic server to put those files available and process the POST request for the user saving it.
@WriteCodeEveryday, off to a great start. I've got a basic lighttpd config with your UI up and running on the switch, just messing around with WebDAV now to see how we can get the POST processed.
Regarding authentication, could we perhaps leverage mod_auth to handle this rather than doing it in script?
@randlor If we do mod_auth, we can use the sha256 that's being sent by the app.
Here's what I'm thinking
- On the base image, we add a basic htpasswd file with admin:admin(sha256) and lock the switch config behind that.
- When WebDAV grabs that POST it updates the file from the "credentials" node (assuming it can read JSON)
- Modify the app to make HTTP Digest Auth for that config request when getting the config (https://redmine.lighttpd.net/projects/lighttpd/wiki/docs_modauth) and send the password when doing POST.
I'm not real good on embedded development (work on massive React apps that need at least 1Gb of RAM) so I'm not caught up on WebDAV but everything I see so far points to POST not being super viable here (https://stackoverflow.com/a/22606899).
Got the MS42 in the mail (thanks for the right direction on cheap @halmartin), I'm gonna try building it using Leo's Notes but I'm a little short on knowledge for sure (also lacking a proper build environment as I am running on a PopOS Debian Live CD due to a Windows incident)
I'm gonna try building it using Leo's Notes
Leo's approach is an entirely different firmware which utilizes Meraki's binaries for switch management (notably, switch_brain
and libpoecore
).
I have written instructions on how to install the firmware built from this repository, which in my opinion is actually easier than Leo's method as you only need to flash the SPI flash. Using the buildroot firmware, there is no need to transfer files over serial or modify the contents of NAND.
If you wish, I can provide a recent firmware build that includes lighthttpd
for your development. I can also update the installation instructions for the MS42, as I have one myself.
Note that you will need an SPI programmer and a SOIC-16 chip clip, or a soldering iron and "dupont" prototype wires.
@WriteCodeEveryday I used a Raspberry Pi instead of an SPI programmer, with a SOIC clip and DuPont wires. Saves all the pain of soldering, much much easier.
@halmartin is there a more recent build than 0722 available?
@randlor it's a bit more complex on the MS42, as the SOIC flash is underneath the PCB, requiring you to remove the PCB from the chassis to access the chip. There is a header you can solder to on the top of the board, that's what I've done.
I will update the installation instructions to include the MS42.
Edit: the installation docs have been updated with instructions for the MS42.
If you wish, I can provide a recent firmware build that includes lighthttpd for your development. I can also update the installation instructions for the MS42, as I have one myself.
@halmartin your helpfulness shall be rewarded with an epic UI that will be full of blinklies and hot pink (or just a working build) :+1:
I have written a small, terrible shell script to print the current status of the switch ports as JSON: switch_status
Here is what the output looks like on my MS220-8P with port 1 connected and SFPs installed in ports 9 and 10:
{"ports":{"1":{"link": {"state": "up", "speed": 1000},"lacp": "disabled", "enabled": true, "vlan": {"pvid": 1, "allowed": "1-4094"},"poe": {"standard": "802.3at", "enabled": true, "power": 0.0}},"2":{"link": {"state": "down", "speed": 0},"lacp": "disabled", "enabled": true, "vlan": {"pvid": 1, "allowed": "1-4094"},"poe": {"standard": "802.3at", "enabled": true, "power": 0.0}},"3":{"link": {"state": "down", "speed": 0},"lacp": "disabled", "enabled": true, "vlan": {"pvid": 1, "allowed": "1-4094"},"poe": {"standard": "802.3at", "enabled": true, "power": 0.0}},"4":{"link": {"state": "down", "speed": 0},"lacp": "disabled", "enabled": true, "vlan": {"pvid": 1, "allowed": "1-4094"},"poe": {"standard": "802.3at", "enabled": true, "power": 0.0}},"5":{"link": {"state": "down", "speed": 0},"lacp": "disabled", "enabled": true, "vlan": {"pvid": 1, "allowed": "1-4094"},"poe": {"standard": "802.3at", "enabled": true, "power": 0.0}},"6":{"link": {"state": "down", "speed": 0},"lacp": "disabled", "enabled": true, "vlan": {"pvid": 1, "allowed": "1-4094"},"poe": {"standard": "802.3at", "enabled": true, "power": 0.0}},"7":{"link": {"state": "down", "speed": 0},"lacp": "disabled", "enabled": true, "vlan": {"pvid": 1, "allowed": "1-4094"},"poe": {"standard": "802.3at", "enabled": true, "power": 0.0}},"8":{"link": {"state": "down", "speed": 0},"lacp": "disabled", "enabled": true, "vlan": {"pvid": 1, "allowed": "1-4094"},"poe": {"standard": "802.3at", "enabled": true, "power": 0.0}},"9":{"link": {"state": "up", "speed": 1000},"lacp": "disabled", "enabled": true, "vlan": {"pvid": 1, "allowed": "1-4094"},"poe": {"standard": "802.3at", "enabled": true, "power": 0.0}},"10":{"link": {"state": "up", "speed": 1000},"lacp": "disabled", "enabled": true, "vlan": {"pvid": 1, "allowed": "1-4094"},"poe": {"standard": "802.3at", "enabled": true, "power": 0.0}}}}
This might be helpful for displaying the status of the switch in the web UI.
A shell CGI script and a bit of lighttpd config (mod-auth, mod-cgi) should get you going with 0 dependencies.
As for the json, why not make ports an array? Seems the natural way to model a switch, maybe something like:
{
"device": "Meraki MS220-8P",
"version": "1.2.3-beta",
"date": "2020-04-23T18:25:43.511Z",
"ports": [
{
"port_number": 1,
"pvid": 1,
"vlans": [1, 10],
"tagged": true,
"stp": true,
"lacp": false,
"poe": {
"enabled": true,
"mode": "802.3af"
}
},
{
"port_number": 2,
"pvid": 1,
"vlans": [10],
"tagged": true,
"stp": true,
"lacp": false,
"poe": {
"enabled": true,
"mode": "802.3af"
}
}
]
}
Good suggestions, I've implemented the following changes:
{
"device": "meraki_MS220-8P",
"date": "2020-12-05T13:24:05Z",
"temperature": "43.9 C",
"ports": [
{
"port": 1,
"link": {
"established": true,
"speed": 1000
},
"lacp": false,
"enabled": true,
"vlan": {
"pvid": 1,
"allowed": "1-4094"
},
"poe": {
"standard": "802.3at",
"enabled": true,
"power": 0
}
},
{
"port": 2,
"link": {
"established": false,
"speed": 0
},
"lacp": false,
"enabled": true,
"vlan": {
"pvid": 1,
"allowed": "1-4094"
},
"poe": {
"standard": "802.3at",
"enabled": true,
"power": 0
}
}
]
}
That looks better however looks like you are mixing status with configuration maybe. If you are going to build a web config interface you want those 2 things to be separate documents I think.
I can think of a getStatus
function that tells me if something is connected, power draw, speed, etc and a getConfig
(& setConfig
) functions to configure things like VLANs, PoE type, enable/disable, etc.
I think it's fine to have them in the same document, the biggest issue is removing that username/password out of the configuration.
I'm almost done moving and will begin flashing my little meraki and getting back into this.
In case anyone is still interested, I decided to play with this over the weekend (https://github.com/hall/freeraki-ui). Made a few changes but happy to be contested on the right approach. Here's a screenshot, using the last file posted in the comments here (extrapolated out to 10 ports).
Some of the changes include
- removed the port add/delete functionality since I think the initial json should be generated by whatever daemon is running on the switch
- moved the json preview to a modal to help de-clutter the page (though might be overkill to pull in a dependency for a single instance)
- added a grid of ports with a layout representative of the switch model (properly groups ports for 8, 24, and 48 count switches; which I believe covers all devices the firmware runs on)
- removed any login/credential logic; my initial hope was to use the PAM account(s) but not yet sure if that's a good idea (though I imagine it's what openwrt is doing)
- added a lighttpd proof of concept (see the
README.md
) which uses plain auth and webdav to modiy a file in the repo
Need to add a legend but I think most, if not all, of the currently-discussed config is represented.
Alright -- not very familiar with buildroot and didn't want to re-flash my device if I didn't have to so I got things working with uhttpd. The install docs should be able to get the UI setup (you'll need the incoming config daemon to actually have the settings applied but you can at least see the /etc/switch.json
file change when you edit the UI).
The API is currently a super simple CGI script that just reads and writes /etc/switch.json
.
You might also have to add /:root:$p$root
to /etc/httpd.conf
to put PAM auth in front of uhttpd (I don't recall whether it was already there).
Let us know if you try it out and, if you do, what breaks. @halmartin, not sure what you had in mind for where this code should eventually exist but I'll start a PR to this repo if you feel it belongs here.
I tried to build the UI following the instructions in the repo, but I got the following error:
Step 1/7 : FROM node:17-alpine
---> d0ea4d2aa898
Step 2/7 : RUN apk add --no-cache git
---> Using cache
---> 718f410d8c6f
Step 3/7 : WORKDIR /opt
---> Using cache
---> 1f036e332614
Step 4/7 : RUN git clone https://github.com/hall/freeraki-ui.git
---> Using cache
---> 57752b08fc6b
Step 5/7 : WORKDIR /opt/freeraki-ui
---> Using cache
---> e9044f6bea9f
Step 6/7 : RUN npm i
---> Running in 484401de65a2
npm WARN old lockfile
npm WARN old lockfile The package-lock.json file was created with an old version of npm,
npm WARN old lockfile so supplemental metadata must be fetched from the registry.
npm WARN old lockfile
npm WARN old lockfile This is a one-time fix-up, please be patient...
npm WARN old lockfile
npm WARN deprecated [email protected]: Please see https://github.com/lydell/urix#deprecated
npm WARN deprecated [email protected]: See https://github.com/lydell/source-map-url#deprecated
npm WARN deprecated [email protected]: See https://github.com/lydell/source-map-resolve#deprecated
npm WARN deprecated [email protected]: request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142
npm WARN deprecated [email protected]: https://github.com/lydell/resolve-url#deprecated
npm WARN deprecated [email protected]: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
npm WARN deprecated [email protected]: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
npm WARN deprecated [email protected]: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated [email protected]: this library is no longer supported
npm WARN deprecated [email protected]: 3.x is no longer supported
npm WARN deprecated [email protected]: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)
npm WARN deprecated [email protected]: babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.
npm WARN deprecated [email protected]: This SVGO version is no longer supported. Upgrade to v2.x.x.
npm WARN deprecated [email protected]: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
npm WARN deprecated [email protected]: Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.
npm WARN deprecated [email protected]: Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.
added 1786 packages, and audited 1787 packages in 56s
142 packages are looking for funding
run `npm fund` for details
59 vulnerabilities (2 low, 50 moderate, 7 high)
To address issues that do not require attention, run:
npm audit fix
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
Removing intermediate container 484401de65a2
---> b3e3c0b962b3
Step 7/7 : RUN npm run build
---> Running in b858aa6663fd
> [email protected] build
> preact build --prerender && cp -r ./assets/* ./build/ && zip -r latest.zip build
✖ ERROR Error: error:0308010C:digital envelope routines::unsupported
at new Hash (node:internal/crypto/hash:67:19)
at Object.createHash (node:crypto:130:10)
at module.exports (/opt/freeraki-ui/node_modules/webpack/lib/util/createHash.js:135:53)
at NormalModule._initBuildHash (/opt/freeraki-ui/node_modules/webpack/lib/NormalModule.js:417:16)
at handleParseError (/opt/freeraki-ui/node_modules/webpack/lib/NormalModule.js:471:10)
at /opt/freeraki-ui/node_modules/webpack/lib/NormalModule.js:503:5
at /opt/freeraki-ui/node_modules/webpack/lib/NormalModule.js:358:12
at /opt/freeraki-ui/node_modules/loader-runner/lib/LoaderRunner.js:373:3
at iterateNormalLoaders (/opt/freeraki-ui/node_modules/loader-runner/lib/LoaderRunner.js:214:10)
at iterateNormalLoaders (/opt/freeraki-ui/node_modules/loader-runner/lib/LoaderRunner.js:221:10)
at /opt/freeraki-ui/node_modules/loader-runner/lib/LoaderRunner.js:236:3
at context.callback (/opt/freeraki-ui/node_modules/loader-runner/lib/LoaderRunner.js:111:13)
at /opt/freeraki-ui/node_modules/babel-loader/lib/index.js:59:71
The command '/bin/sh -c npm run build' returned a non-zero code: 1
I am not a Node person, so I don't know what to do to solve this.
I believe it just doesn't like your version of node. This Dockerfile
worked for me.
FROM node:14-alpine
RUN apk add --no-cache zip
COPY . /opt/freeraki-ui
WORKDIR /opt/freeraki-ui
RUN npm i
RUN npm run build
Just pushed a change to build it in docker with one command:
docker run -it -v $PWD:/app -w /app --entrypoint sh node:14-alpine -c "npm i && npm run build"
I finally had a chance to test this on my MS42.
Some thoughts:
- I think the port index should start at 1, not 0. This corresponds to the port numbering present on the switch itself
- The port grouping on larger switches like the MS42 with 52 ports is a bit chaotic (see screenshot)
- The UI shows PoE configuration even on hardware which as no PoE capability (like the MS42). This can be confirmed by viewing the JSON
I think 1 & 2 are because the latest version expects ports
to be an object instead of an array so I need to update either the UI to switch back to arrays or switch_status
to print out the object syntax (which is part of the change in #19). I put example files for 8, 24, and 48 ports in the repo and the 48 port file renders correctly:
3 is valid. I'll remove PoE options if the config isn't present.
Unrelated, but I'm also considering making the bottom section a table so all ports can be viewed together instead of needing to click through each one.
Pushed a commit to only show PoE enable and mode options if the poe
object exists on the port: https://github.com/hall/freeraki-ui/commit/d158d09c9dcbf6741974a1cbeec3b108b7877193
Preview of converting the port config into a table:
Makes it easier to compare and edit without clicking between ports but also might be information overload :shrug: let me know if anyone has a preference (or a different idea altogether).
I like the table view! What about also applying a light shading of a port based on the status?
e.g. white for disconnected, orange for 100M, green for 1000M, and something (plaid?) for 10000M
Alright -- went ahead and implemented the table layout in the latest commit. I also added a collapsible legend and set the port background color based on link speed (I can make some of them background patterns as well but the solid light blue seemed like a good fit).
A few more updates:
- added CI to publish releases so no more need to build (also updated docs to reflect that installation process)
- added feedback on in-flight changes (highlighting table cells) as well as a progress spinner on the upload button
Another feature I think would be really useful is if we could determine the hostname of the device attached to a port. I did a little digging on how to do that but have, so far, come up empty handed. Anyone happen to know a way to do that?
Another feature I think would be really useful is if we could determine the hostname of the device attached to a port. I did a little digging on how to do that but have, so far, come up empty handed. Anyone happen to know a way to do that?
LLDP? But the host would have to run it as well.
Hm, I'm hoping for an approach with fewer to no (extra) requirements of the devices on the network.
I found /click/arp_table/table
but it seems to have surprisingly few entries (4, in my case, on a network of ~20 devices). But, knowing the DNS server address, I'm able to find hostnames for those devices!
# nslookup $(cat arp_table/table | tail -1 | awk '{print $1}') 192.168.1.1 | tail -2 | head -1 | awk '{print $4}'
router.lan
I found
/click/arp_table/table
but it seems to have surprisingly few entries (4, in my case, on a network of ~20 devices). But, knowing the DNS server address, I'm able to find hostnames for those devices!
Maybe you know something I don't, but how do you associate a MAC address to a port? What about a port which leads to another switch?
What about a host with a static IP address with no reverse DNS record on the port?
Maybe you know something I don't, but how do you associate a MAC address to a port?
Oh, I have no idea :grin: I'm just poking around to see what I can find out. This SO answer seems to suggest that it's possible (for OpenWRT, at least) with some effort but I'm not certain how much of that applies here.
What about a port which leads to another switch?
Yeah, I don't think the arp table is entirely helpful as a source of truth since we (for display on the UI) only care about directly connected devices.
What about a host with a static IP address with no reverse DNS record on the port?
I'm just looking for some way to ID the device connected on a particular port so a static IP address is just a good if there's no DNS record available.
Looking at screenshots of other switch interfaces online, it doesn't seem like this is a standard feature (and, after a bit more searching, may not even be possible).