Webring².
Published 11 August 2024 at Yours, Kewbish. 4,014 words. Subscribe via RSS.
Introduction
Earlier in April, I was preparing for my first conference talk. Not quite knowing what to expect, I thought there’d be plenty of networking and job opportunities and swag. I was right on only one of these counts (spoiler: it was the merch)1.
But when I’d just been accepted, I thought I’d be hitting up recruiters left and right on the showcase floor. I printed ten paper copies of my resume, and if that wasn’t enough, I also ordered a NFC ring to flash my personal website onto. This way, when people wanted to learn more, they’d be able to tap their phone against the ring to bring up my site, and I wouldn’t have to fumble around typing my LinkedIn URL or pulling crumpled paper resumes out. I thought it’d be a memorable way to leave a first impression: look, this candidate not only knows about web and distributed systems, but is making their resume available to me in a way I’ve never seen before! I chose a slick double-sided titanium ring off nfcring.com and giddily wrote my site URL onto both chips.
Suffice it to say that no one scanned my ring during the conference. However, I still had the ring, and I wasn’t about to wear it every day in the deranged hope I’d be able to organically sneak it into conversation one day. I wanted to make a little project out of the ring, and after some very Emilie-core wordplay brainstorming, I happened upon the idea of a webring ring. You’d be able to scan it, just like when I flashed my site URL onto it, but it’d lead to a webring of me and my friends’ blogs, and you’d be able to navigate between sites on the ring by scanning it multiple times.
It didn’t take long at all to build — a weekend and a Cloudflare Pages deep dive later, my webring ring was ready. I’m calling it Webring², because it’s a web(ring)(ring) = web(ring)²
. Here’s a demo video:
The source is available here. In this blog post, I’ll go through the three main components of the project: the webring site itself, the mechanism to open different pages each time you scanned it, and the physical NFC ring. Along the way, I’ll cover the history of early web search engines, some niche behaviour of WebRTC, and odd body modifications.
If you searched it, then you should’ve put a ring on it
Webrings are a collection of sites that link to each other in a cycle. Each site has ’next’ and ‘previous’ links, which are wired together so by the time you’ve hit the last site, its ’next’ link takes you back to the first site, and vice versa. The links were usually included in a commonly styled footer that everyone in the ring included on their site, and sometimes there’d be a central directory listing to make sure that if someone’s website went down, the ring didn’t turn into an abbreviated line instead. There was typically a “ringmaster” who’d manage the ring structure, moderating new applications to join or detangling awry linking loops.
Before search engines like Google or DDG, the early web subsisted on web directories. You couldn’t just search across the whole web: you needed to have an explicit entrypoint to some site. (Before DNS, people even had to share IP addresses, not domain names!) Web directories bridged this gap by providing a central catalog of sites, neatly categorized for your perusal. DMOZ was one such web directory, owned by AOL but managed by volunteers. Yahoo! also started as a hand-curated directory before pivoting to a more modern crawler approach.
Webrings were sort of like a branching-off point and alternative to directories, like a human-curated ‘Recommended videos’ at the bottom of each site. Once you’d found a page via a directory, it’d be very convenient to be able to find other similar sites without going through the directory again. It’s natural, then, that webrings were also used to boost SEO rankings, especially for early search engines in the era of PageRank, since they provide a guaranteed few links to/from relevant content. Backlinks were crucial then, because more backlinks from quality sites would boost your search result rankings. In some ways, webrings were a less-commercialized version of the linkfarms and SEO drivel that’s pervasive on the front page of the web today.
Nowadays, it seems that webrings are seen as a cute nod to the web of the past. The major webring providers have shut down, and search engines have replaced much of the discovery functionality of webrings. The primary webring site, WebRing.com, was summarily end-of-lifed by Yahoo! fairly early on, in 2001, but other providers stuck around til through the 2010s. There are still modern webrings running, like this one by Devine Lu Linvega, and every so often, a post comes up on Hacker News lamenting their relative demise. While poking through Hacker News, I also found this site webri.ng that lets you manage webrings by generating you the footer HTML to insert (bonus for a cool domain!). I’m sure there are plenty of webrings still running, particularly personal ones like the one I started, hosted by a group of friends or colleagues. In my head, they’re in the same semantic space as the cozy web, as little relics of delight that you might stumble upon from a quirky site.
Speaking of my webring, let’s look into its HTML structure. The NFC ring has a URL record written on it, so when you scan it, all it does is open a website. I’ll discuss why we have to use a single website in the next section, but for now, let’s take a look at the site itself. The ring links to webring.kewbi.sh, which is a static Cloudflare pages site. It displays a single <iframe>
with the current webring page, and the footer links navigate to the previous and next sites in the ring. The source is here — the markup is very simple, simple enough to have been mostly generated by ChatGPT.
One of the more unique bits about this webring is its architecture. Webring² is a centralized display, linking to sites that are separately controlled by each of my friends. Using an <iframe>
to dynamically link to their content and keeping a central navigation header retains the core experience of exploring a webring, but guarantees no broken links or downtime. Even if one of my friends’ pages goes down, their site won’t load, but the navigation will still allow you to go to the next and previous sites. This lessens maintenance burden, since I won’t have to run around asking the owners of the pages that link to the broken site to change their links. This also makes it easier to join and leave webrings: all the links are managed centrally on my link service, so I can localize changes just to that configuration file instead of requiring multiple people to update their links. Because this site is just a static HTML file, it’s easy to rehost on another provider, providing additional future-proofing.
I claim no originality, since I was neither alive nor on the Internet for the webring heyday, but I don’t think I’ve seen this structure in any of the other (modern) webrings I’ve found. I don’t see any reason why this architecture couldn’t have been supported by the early Web — Netscape added support for <frame>
s, a predecessor to <iframe>
s, in 1996, and sites were being hosted by servers already anyways. To be honest, I haven’t dug into the history of WebRing.com or any of the other webring providers, so maybe that’s indeed how they worked.
Web Whorls
The static Webring² website has links to navigate between sites in the ring, but I also wanted to build out an interaction where scanning the ring multiple times would automatically advance the user’s ‘position’ in the webring. The obvious way to do this is to somehow change the URL stored on the ring each time it was scanned, but there’s no way to make a self-modifying record as far as I know. You could try making some standalone app to force the user to both read/write to the ring, but I wanted my site to work on the web, as was originally intended! This is also why I had to use the one-centralized-site architecture, since otherwise I wouldn’t be able to change the view to the selected website.
If you go through the HTML and the Pages Function, you’ll see that the app is quite simple.
- In the HTML, we fetch from the
api.ipify.org
API to get the user’s public IP, then pass this as an identifieripkey
to the Pages Function. - The Pages Function maps the IP address to the current position in the webring and returns the current URL alongside the current index and the list of URLs.
- The frontend then displays the current URL in the
<iframe>
. - When the user clicks the ‘previous’ or ’next’ links, the frontend makes a request to the Pages Function to set the current user’s index.
- This way, the next time the user taps the ring, the index will increment again in the Pages Function, and they’ll be sent to the next site.
This is well and good if you have a dedicated IP address, but nowadays your public IP will be shared by multiple devices on your network, or even at a higher level, by other households on your ISP. At this point, the site only maps an IP to a position, so if I scanned my ring and clicked ’next’ a few times, my parents on the same network would see my state instead of starting from the beginning. This isn’t the end of the world — it’s the whole reason that webrings are a cycle in the first place. However, I wanted to drill down more, so ideally each device would be able to have its own position in the webring. I came up with three potential approaches and prototyped one of them out. The other two were very interesting reading, but I decided they were a bit too invasive to implement, especially for a page of this scale (read: no real users).
The first idea I had was to find a way to figure out the user’s local IP. This is the address that’s used intra-network, and would uniquely identify the device among the others sharing the public IP. The WebRTC API seemed to promise a solution and path forward. The WebRTC API is used for real-time communication: video and voice in particular. It enables screensharing, streaming, and sending messages between peers. WebRTC needs to know the local IPs of devices in order to negotiate connection information between them. You can see this in action on net.ipcalf.com, which will display a .local
address, or by running JS similar to this Gist:
The address we get back is a .local
address, not in typical IP octet format. That in itself is fine, since I only need some unique identifier for the device, and I don’t care about what that ID looks like. You’ll notice that if you refresh your tab, though, the .local
address will change. This .local
address is a mDNS protocol address — mDNS is like DNS but for small networks where you don’t need a hierarchy of nameservers and can just address peers directly. It’s a bit like ARP:
- The requesting device will multicast a request for the local IP address linked to the
.local
address. - The device matching the
.local
address will multicast back its local IP address as its response. - All devices except the one that matches the address don’t respond to the request, but can read the response and cache it for future reference.
At this point, you might realize that being able to access local IP addresses from anywhere on the Internet, just via a simple API call, feels a little iffy privacy-wise. That’s why the WebRTC API uses mDNS instead. The browser will dynamically generate a .local
address for you each time you create a connection and resolve it for you behind the scenes. This way, your local IP is never leaked into the Internet, but unfortunately that means your local mDNS address can never be used for identifying your device for webring purposes either. Notwithstanding the fact that the .local
address kept changing on refresh, I think the API also threw errors on mobile Safari, though I can’t be sure since I can’t see the console logs. This WebRTC connection code is still left in the HTML source, but it’s commented out.
At this point, I realized I should probably stop fighting the privacy protections that people smarter than me had come up with, but I couldn’t help myself from looking into a couple of other ways to identify users. After trying out the WebRTC API, I realized I could quite trivially generate a UUID and store it into the browser’s localStorage
or as a cookie, and reference that as an identifier. (I’ll get around to adding this one day, but it’s not up on the webring site now quite yet.) While looking into cookies, I came across the concept of Evercookies, which were self-reconstructing cookies that couldn’t be deleted. Instead of just storing data into a cookie, localStorage
, or the IndexedDB, Evercookies hide themselves into weird, niche storage mechanisms like reading cookies from the RGB values of force-cached images. Usually, the typical ‘clear cookies’ option on major browsers just wipes the cookies themselves. If any other pieces of the cookies are left, however, like a piece left in a Flash Shared Object, the JS is smart enough to reconstruct and restore all the cookies back where they came from. Here’s the source of the Evercookie API — it’s really something, and the list of places where you can sneakily store data is worth a read through. Though it doesn’t have anything to do with this project per se, CSS history knocking stood out to me in particular as an interesting way to exfiltrate visited status. It’s a little scary being aware of all the ways that you can get around the basic browser controls to remove your history or web footprint, and that it seems so clean and simple to do.
Perhaps even scarier are the possibilities laid out by browser fingerprinting. JS libraries can combine your user agent, screen resolution, timezone information, plugins, fonts, and more to create a mostly unique identifier for you. FingerprintJS claims 40-60% accuracy, which is already impressive, but their closed-source version claims 99.5%. See a demo here for the open-source version of FingerprintJS to get a sense of just how much identifying information you’re transmitting with every request. Or, check out the closed-source version’s site — I opened the site in a regular Chrome window, then in an incognito window, then after restarting Chrome, and all three times it got my identifier right.
I was super surprised that even with the user agent, screen resolution, and other parameters that the more limited open-source version tracks, it’s able to pinpoint users so precisely. I’d have expected that there’d be billions of users on the internet, millions within my country and using my same ISP, and probably in the thousands using the same browser at the same standard laptop resolution. Even taking browsing time into account, I’d have expected maybe at least ten other folks matching my browser version and parameters to be on the Web at any given time, but it’s been so far quite impressive. Again, sorting out proper cookies or browser fingerprinting felt like too big of a lift for my weekend project, so I didn’t really consider adding the library. Certainly fun (and spooky) to play around with, though.
The Right-Hand Rule
We’ve gone through the webring site itself and how I navigate between pages on the webring — all the software components that make the site fully usable in a browser. This brings us, finally, to the physical ring itself. My ring is the cheapest model off nfcring.com2. I was worried the company wasn’t even in business, since their forum was inactive and their stock fairly low, but a quick email assuaged my concerns. I wasn’t too fussy about the datasheet, since I didn’t have particular requirements on the NFC chip, as long as it could hold a tiny URL.
NFC tags are pretty easy to read and write — just download a writer/reader app on your phone and you’re set. To write Webring²’s URL to the ring, I used the NFC Tools app. For most record types, you don’t even have to download a reader if you have a recent smartphone, so you can just wave your phone near a tag to automatically open the contact or whatever data’s stored on it. You can see in the demo video above that I can just tap my phone on the ring to scan it without opening any particular app. I was at a cybersecurity competition recently, hosted in an official government building, and several participants had a lot of fun surreptitiously writing their own data to the NFC-enabled visitor lanyards we were required to wear (This is your warning to not do this.) Some folks also brought their Flipper Zeros, which is a standalone device that can, among many other things, scan and emulate NFC cards, so there might have been some shenanigans with cloning hotel room cards. (Again, don’t do this.)
To understand how NFC tags work, you’ll need to take a throwback to high school physics, and the electromagnetism unit in particular. The high-level overview goes like this:
- NFC tags themselves, like the one in my ring, doesn’t have a power source of its own. These are called passive NFC devices.
- Your active NFC device, like your reader, has some power source. This power source can generate an electromagnetic field when you pass the current (electricity) through a coil.
- Remember the right-hand rule from physics class? It turns out that if you put a coil into a magnetic field, you also get current back out. If you look closely at the NFC ring, you’ll see a little coil of wire, which now can get some current running through it.
- A capacitor is like a mini-battery: it charges up and can release all the power in a short burst.
- This provides enough power for the NFC tag’s microprocessor to create its own magnetic fields, which the phone can then read. It’s like the NFC tag is wirelessly tapping power from your phone in order to transmit its own information. The official term for this is ‘magnetic induction’. Transferring power this way isn’t very efficient, however, so it has to be done at very close intervals.
- Data is stored on NFC tags in raw bytes, which can be formed into one of several record types defined by the NFC forum. These include records like URLs, contact information, WiFi passwords, or application-specific NDEF records.
NFC tags typically differ in terms of their capacity and speed, and are categorized into one of five types. Type 1 tags are older and slower, and the tags generally go up in performance as their type number increases. The tag on my ring is the NTAG203
type, which falls under Type 2. It has 144 bytes of space, which is limited but more than enough for my single URL. I don’t notice any speed issues, but I’ll note that it can sometimes be difficult to scan the ring. I think the problems are due to the curved surface of the ring, but in the spirit of PEBKAC, are just as likely to be because I don’t quite know where the NFC antenna is on my phone. I’ve tried scanning the ring with a thinner plastic case, which works, but it wouldn’t scan through some of my friends’ thicker cases (e.g. Otter) or wallet cases. Again, not sure if the problem is on me or the ring.
I was surprised by how long ago NFC tags existed, even on smartphones. By 2010, Nokia released the first NFC-enabled smartphone, and it’d already been used in public transportation networks in 2009. Today, I think the most prevalent use-case for NFC that most people have interacted with is wireless payments and digital wallets like Apple Pay, as well as transit cards and other tap-based ecosystems. NFC stickers are fairly cheap now too: I found some to bulk order on AliExpress from $0.03 a piece.
Here’s an interesting aside: NFC chip hand implants have been around since at least 2015. It’s the same underlying technology as a ring, but you don’t have to remember to bring it with you anywhere anymore. This paper estimates that 50K to 100K people have some sort of chip in their hand, so if you maybe cut that number in half for NFC implants in particular, that’s still a sizeable 25K. A 2018 NPR post says it’ll run you about $180, but if you’re a DIY hacker (which I’d expect most people who’d think about injecting a chip into their hand would be), you can buy a kit to insert it yourself for as little as $69. When I was younger, I briefly considered getting an implant, just because it was weird and it’d have been a hell of a fun fact, but I also don’t have much of a use for it. Giving someone a high five to pass on your LinkedIn is cool, but a little too extreme for a one-day Linux conference.
Conclusion
Every time I build a small side project over a weekend, I find myself learning much more than I’d have imagined. I try to prototype out unique ideas that I haven’t seen done before, or at least ones that combine fields that I know of, but not fully understand. Building Webring² and my HTML Day 2024 have inspired me to humour myself more and keep occasionally hacking on ideas that catch my fancy. The self-contained, timeboxed nature of these projects makes them more approachable than returning to my long-running projects, so maybe this is all just productive procrastination.
This has been the story of how I built one Webring², but I’ve been toying around with the idea of buying up a bunch of NFC rings for my friends so we can have a Webring² ring — a Webring³, if you will. We could use a system slightly different than the current one, where each ring statically links to its own ‘starting position’ (the URL of the friend that has the ring) but can navigate to any of the other sites. On the other hand, we could use the current implementation, where we dynamically fetch the starting URL on scan and have all the rings use the same position in the webring, but I think this’d be a bit less personalized for each person with a ring.
There’s probably more untapped potential in doing fun (or cursed) things with the NFC ring — I haven’t looked too far into the technical details, but I’d reckon there’s some hacky stuff I could do. Even though the ring is a passive tag, there are some very unique ideas for automations I could rip off. If you’ve been thinking of getting into home automation, or just playing around with NFC in particular, go take a look at the repo and set up your own. I’m looking forward to seeing more webrings (or Webring²s) in the future!
-
To be fair, the other two were certainly available, but with my limited time there I didn’t have much luck. Everyone simply redirected me to their online job board and I was busy during the preplanned mixers. ↩︎
-
It was one of the only manufacturers at this price point that had ring sizes small enough for my finger. I found several cheaper alternatives on Etsy and Amazon, but their ring sizes started at 10. For reference, that’d probably be several millimeters too loose, even on my thumb. Small detail, but made me think about the target demographic and demand served a bit. ↩︎