{"title":"Makefile.feld","link":[{"@attributes":{"href":"https:\/\/blog.feld.me\/","rel":"alternate"}},{"@attributes":{"href":"https:\/\/blog.feld.me\/feeds\/all.atom.xml","rel":"self"}}],"id":"https:\/\/blog.feld.me\/","updated":"2026-04-13T09:36:13-07:00","entry":[{"title":"I Just Want Simple S3","link":{"@attributes":{"href":"https:\/\/blog.feld.me\/posts\/2026\/04\/i-just-want-simple-s3\/","rel":"alternate"}},"published":"2026-04-10T17:14:34-07:00","updated":"2026-04-13T09:36:13-07:00","author":{"name":"feld"},"id":"tag:blog.feld.me,2026-04-10:\/posts\/2026\/04\/i-just-want-simple-s3\/","summary":"<p>I just want S3. My needs are pretty basic. I don't need to scale out. I don't need replication. I just need something that can do S3 and is reliable and not slow.<\/p>\n<p>Minio is dead, they pulled the plug after axing the interface. They <a class=\"reference external\" href=\"https:\/\/github.com\/minio\/minio\">archived<\/a> the repo so they \u2026<\/p>","content":"<p>I just want S3. My needs are pretty basic. I don't need to scale out. I don't need replication. I just need something that can do S3 and is reliable and not slow.<\/p>\n<p>Minio is dead, they pulled the plug after axing the interface. They <a class=\"reference external\" href=\"https:\/\/github.com\/minio\/minio\">archived<\/a> the repo so they can chase AI industry dollars because those folks have heavily utilized Minio. Good for them, but I always wrote them off after pointing out a bug their tests weren't catching because they were mocking responses, not actually testing the code, and they shrugged it off. (deletes were broken at one point)<\/p>\n<p><a class=\"reference external\" href=\"https:\/\/garagehq.deuxfleurs.fr\/\">Garage<\/a> is new and interesting being built in Rust, but last I tried (6 months ago?) it was also unnecessarily complex. It's very young, development was paused for a while (funding), and was missing a couple normal S3 features I wanted last I checked. Maybe it's better now. Still feels too heavy.<\/p>\n<p><a class=\"reference external\" href=\"https:\/\/github.com\/seaweedfs\/seaweedfs\">SeaweedFS<\/a> is honestly super cool. I like their approach and how they added layers on it so they can do things like support WebDAV. I don't know what's wrong, but I run it with a master and volume node, it's slow. I switch to the new <tt class=\"docutils literal\">weed mini<\/tt> approach -- still slow. I'm storing a couple GBs of normal files in here, it's nothing fancy, but even on my own LAN I try to download a file and it chugs and chugs -- starts downloading at maybe a few hundred KB\/s, eventually ramps up to ~10mbit\/s. I don't know why. It's on my <em>LAN<\/em>, why isn't it instant?<\/p>\n<p><a class=\"reference external\" href=\"https:\/\/ceph.io\/en\/\">CEPH<\/a> is a monster. It's huge. We have it at work. Way more complex than I need, but if you really wanted to build something that can compete with Amazon's S3 you probably want CEPH. SeaweedFS is close to being capable as well...<\/p>\n<p><a class=\"reference external\" href=\"https:\/\/github.com\/versity\/versitygw\">Versity GW<\/a> -- oh bless you, Reddit commenter, for bringing this one up in a thread about benchmarking S3 backend performance. Nobody seems to know it exists except... Sandia National Labs, Los Alamos National Lab, the military, bunch of universities...!<\/p>\n<blockquote>\nThe Versity S3 Gateway currently supports generic POSIX file backend storage and Versity\u2019s ScoutFS filesystem.<\/blockquote>\n<p>I don't know what the main use case is for Versity. It can proxy to other S3 backends so you don't have to expose auth or can provide a custom auth layer. Does it do read-through caching? Don't know, didn't check. Can you have some local buckets and some that are proxied? Don't know, didn't check.<\/p>\n<p>But what it can do is just use the local filesystem for S3 storage and that's good enough for me. And I get a web interface to manage it all, it can do anonymous\/public read buckets and handles policies. Interesting is that it uses xattrs for storing metadata of the objects.<\/p>\n<p>So I drop this in, rclone my data over, do some testing, and I get lightning fast performance on my LAN like I expect -- line rate downloads! Finally, sanity is restored.<\/p>\n<p>Now I just wait to replace this with a true ZFS native object storage that someone is working on...<\/p>\n<div class=\"section\" id=\"late-contenders\">\n<a class=\"toclink\" href=\"#late-contenders\" title=\"Permalink to this section\"><h2>Late Contenders<\/h2>\n<\/a><p>Didn't know about the following until after I deployed Versity, but here are some notes:<\/p>\n<p><a class=\"reference external\" href=\"https:\/\/github.com\/rustfs\/rustfs\">RustFS<\/a> is a newer contender also written in Rust. Also seems heavier than I need. Benchmarks show it's faster than Minio (also POSIX fs backed), they claim 100% S3 compatibility which is impressive if true. &quot;Development began in December 2023, and we officially launched as an open-source project on July 2, 2025.&quot; and &quot;users can now migrate from MinIO to RustFS through direct binary replacement.&quot; Have not evaluated it yet, not thrilled with adding more Rust to my environment as the compile times are so long and it's not in FreeBSD ports, so I'd have to port it myself. Its usage of the filesystem instead of packing the objects into a custom data format means it would be less performant for small objects, but I want regular fs backed anyway. Give it entire disks\/filesystems and it can distribute the data across them, repair\/heal when replacing broken disks. Neat!<\/p>\n<p><a class=\"reference external\" href=\"https:\/\/rclone.org\/\">rclone<\/a> has the ability to act as an S3 server, but that's not its main purpose and I'm hesitant to rely on it. I knew about this, but wrote it off. If I had to guess I'd say the server is mostly implemented to make it easier for them to test their client.<\/p>\n<p><a class=\"reference external\" href=\"https:\/\/github.com\/mickael-kerjean\/filestash\">filestash<\/a> seems like a nice all-in-one that I should look into. &quot;It started as a storage agnostic Dropbox-like file manager that works with every storage protocol: FTP, SFTP, S3, SMB, WebDAV, IPFS, and about 20 more.&quot;<\/p>\n<p><a class=\"reference external\" href=\"https:\/\/github.com\/scality\/cloudserver\">Zenko CloudServer<\/a> is another I've never heard of. Written in NodeJS, so enjoy your single threaded event loop...<\/p>\n<p><a class=\"reference external\" href=\"https:\/\/github.com\/supabase\/storage\">Supabase Storage<\/a> is also NodeJS, but stores metadata in Postgres. Authorization is handled with Postgres Row Level Security. Interesting concept.<\/p>\n<\/div>\n","category":[{"@attributes":{"term":"tech"}},{"@attributes":{"term":"s3"}},{"@attributes":{"term":"object"}},{"@attributes":{"term":"minio"}},{"@attributes":{"term":"seaweedfs"}},{"@attributes":{"term":"garage"}},{"@attributes":{"term":"versity"}}]},{"title":"Iran Would Already Have A Nuke","link":{"@attributes":{"href":"https:\/\/blog.feld.me\/posts\/2026\/04\/iran-would-already-have-a-nuke\/","rel":"alternate"}},"published":"2026-04-10T16:00:51-07:00","updated":"2026-04-10T17:10:07-07:00","author":{"name":"feld"},"id":"tag:blog.feld.me,2026-04-10:\/posts\/2026\/04\/iran-would-already-have-a-nuke\/","summary":"<img alt=\"Imaginary Timeline\" src=\"\/static\/iran\/iran_nuclear.png\" \/>\n<p>1984: West German intelligence sources claim that Iran\u2019s production of a bomb &quot;is entering its final stages.&quot; US Senator Alan Cranston claims Iran is seven years away from making a weapon. (Iranian nuclear specialists had no knowledge of how to enrich uranium and had no technology with which to \u2026<\/p>","content":"<img alt=\"Imaginary Timeline\" src=\"\/static\/iran\/iran_nuclear.png\" \/>\n<p>1984: West German intelligence sources claim that Iran\u2019s production of a bomb &quot;is entering its final stages.&quot; US Senator Alan Cranston claims Iran is seven years away from making a weapon. (Iranian nuclear specialists had no knowledge of how to enrich uranium and had no technology with which to do it.)<\/p>\n<p>1987: Iraq targeted Iranian civilians with chemical weapons. Iranian Prime Minister Mir Hossein Mousavi said in a late December 1987 speech that Iran &quot;is capable of manufacturing chemical weapons&quot; and added that a &quot;special section&quot; had been set up for &quot;offensive chemical weapons.&quot; Mousavi refrained from saying that Iran actually had chemical weapons, and he hinted that <strong>Iran was constrained by religious considerations.<\/strong> Rafighdoost recalls Khomeini asking rhetorically, &quot;If we produce chemical weapons, what is the difference between me and Saddam?&quot;. The supreme leader was unmoved by the new danger presented by the Iraqi gas attacks on civilians. &quot;It doesn\u2019t matter whether it is on the battlefield or in cities; we are against this,&quot; he told Rafighdoost. &quot;It is haram [forbidden] to produce such weapons. You are only allowed to produce protection.&quot; A few days after Mousavi\u2019s speech, a report in the London daily the Independent referred to a Khomeini fatwa against chemical weapons. Former Iranian nuclear negotiator Seyed Hossein Mousavian, now a research scholar at Princeton University, confirmed [for this article] that Khomeini\u2019s fatwa against chemical and nuclear weapons, which accounted for the prime minister\u2019s extraordinary statement, was indeed conveyed in the meeting with Rafighdoost.<\/p>\n<p>1992: Israeli parliamentarian Benjamin Netanyahu tells the Knesset that Iran is 3 to 5 years from being able to produce a nuclear weapon.<\/p>\n<p>1995: The New York Times reports that US and Israeli officials fear &quot;Iran is much closer to producing nuclear weapons than previously thought&quot; \u2013 less than five years away.  Netanyahu claims the time frame is three to five years.<\/p>\n<p>1996: Israeli Prime Minister Shimon Peres claims Iran will have nuclear weapons in four years.<\/p>\n<p>1998: Former Secretary of Defense Donald Rumsfeld claims Iran could build an ICBM capable of reaching the US within five years.<\/p>\n<p>1999: An Israeli military official claims that Iran will have a nuclear weapon within five years.<\/p>\n<p>2001: The Israeli Minister of Defence claims that Iran will be ready to launch a nuclear weapon in less than four years.<\/p>\n<p>2002: The CIA warns that the danger of nuclear weapons from Iran is higher than during the Cold War, because its missile capability has grown more quickly than expected since 2000 \u2013 putting it on par with North Korea.<\/p>\n<p>2003: A high-ranking Israeli military officer tells the Knesset that Iran will have the bomb by 2005 \u2014 17 months away.<\/p>\n<p>2004: Iran began publicizing Khamenei\u2019s fatwa against nuclear weapons.<\/p>\n<p>2006: A State Department official claims that Iran may be capable of building a nuclear weapon in 16 days.<\/p>\n<p>2007: U.S. National Intelligence Estimate assessed that Iran had halted its nuclear weapons program in 2003.<\/p>\n<p>2008: An Israeli general tells the Cabinet that Iran is &quot;half-way&quot; to enriching enough uranium to build a nuclear weapon and will have a working weapon no later than the end of 2010.<\/p>\n<p>2009: U.S. President Barack Obama revealed the existence of an underground enrichment facility in Fordow, near Qom. Israeli Defense Minister Ehud Barak estimates that Iran is 6-18 months away from building an operative nuclear weapon.<\/p>\n<p>2010: Israeli decision-makers believe that Iran is at most 1-3 years away from being able to assemble a nuclear weapon.<\/p>\n<p>2011: An IAEA report indicates that Iran could build a nuclear weapon within months.<\/p>\n<p>2012: Secretary of State John Kerry: &quot;The fatwa issued by a cleric is an extremely powerful statement about intent,&quot; but then added, &quot;It is our need to codify it.&quot;<\/p>\n<p>2013: Israeli intelligence officials claim that Iran could have the bomb by 2015 or 2016. Joint Comprehensive Plan of Action (JCPOA, &quot;Iran Nuclear Deal&quot;) talks begin.<\/p>\n<p>2016: Iran Nuclear Deal is agreed to.<\/p>\n<p>2018: President Trump withdraws from the Iran Nuclear Deal.<\/p>\n<p>2018-2022: The <a class=\"reference external\" href=\"https:\/\/www.iranwatch.org\/our-publications\/articles-reports\/irans-nuclear-timetable-weapon-potential\">Iran Watch<\/a> project documents &quot;violations&quot; of the 2015 accord which did not even exist anymore.<\/p>\n<p>March 2025: At a Senate hearing, Gabbard said the intelligence community <strong>&quot;continues to assess that Iran is not building a nuclear weapon and Supreme Leader Khamenei has not authorized the nuclear weapons program that he suspended in 2003.&quot;<\/strong><\/p>\n<p>June 2025: President Donald Trump said Iran was &quot;weeks away&quot; from having a nuclear bomb. The United States Air Force and Navy attacked three nuclear facilities in Iran as part of the Twelve-Day War, under the code name Operation Midnight Hammer.<\/p>\n<p>July 2025: A Pentagon assessment found that Iran's nuclear program was likely set back around 2 years.<\/p>\n<p>March 2026: Israel starts a war with Iran, drags the United States into it.<\/p>\n<hr class=\"docutils\" \/>\n<p>We're either gullible or Iranians are too stupid to build a nuclear weapon and it just keeps slipping through their fingers. Which one do you think it is?<\/p>\n","category":[{"@attributes":{"term":"politics"}},{"@attributes":{"term":"iran"}},{"@attributes":{"term":"nuclear"}}]},{"title":"Immich: Ending The Blood Pact With iCloud Photos","link":{"@attributes":{"href":"https:\/\/blog.feld.me\/posts\/2026\/04\/immich-ending-blood-pact-with-icloud-photos\/","rel":"alternate"}},"published":"2026-04-06T10:16:42-07:00","updated":"2026-04-06T11:29:27-07:00","author":{"name":"feld"},"id":"tag:blog.feld.me,2026-04-06:\/posts\/2026\/04\/immich-ending-blood-pact-with-icloud-photos\/","summary":"<p><a class=\"reference external\" href=\"https:\/\/pxlnv.com\/blog\/i-regret-the-blood-pact-i-have-made-with-icloud-photos\/\">This poor guy<\/a>\nwrote about his frustrations escaping iCloud Photos. I went down this\npath last year and it was annoying. You can't just trust the copies of\nphotos on your device because they might not be the original\/full\nversion, and forcing them to all download might not work \u2026<\/p>","content":"<p><a class=\"reference external\" href=\"https:\/\/pxlnv.com\/blog\/i-regret-the-blood-pact-i-have-made-with-icloud-photos\/\">This poor guy<\/a>\nwrote about his frustrations escaping iCloud Photos. I went down this\npath last year and it was annoying. You can't just trust the copies of\nphotos on your device because they might not be the original\/full\nversion, and forcing them to all download might not work as expected.\nI was running into duplicates, missing files (my local count didn't\nactually match the official Library count), etc.<\/p>\n<div class=\"section\" id=\"get-your-files\">\n<a class=\"toclink\" href=\"#get-your-files\" title=\"Permalink to this section\"><h2>Get Your Files<\/h2>\n<\/a><p>Go to <a class=\"reference external\" href=\"https:\/\/privacy.apple.com\/account\">privacy.apple.com<\/a>. Request\na copy of your iCloud Photos data. Be patient.<\/p>\n<p>You will get an email when it's ready, and then you'll have a webpage\nwhere they have links to download your entire iCloud Photos library as archive\nfiles of a few gigabytes each.<\/p>\n<p>Congrats, you escaped with your data.<\/p>\n<\/div>\n<div class=\"section\" id=\"immich\">\n<a class=\"toclink\" href=\"#immich\" title=\"Permalink to this section\"><h2>Immich<\/h2>\n<\/a><p>If you want your own hosted photos service, you probably want <a class=\"reference external\" href=\"https:\/\/immich.app\/\">Immich<\/a>. Install that, you'll get all the modern\nfeatures you expect from an image hosting service.<\/p>\n<\/div>\n<div class=\"section\" id=\"importing-data\">\n<a class=\"toclink\" href=\"#importing-data\" title=\"Permalink to this section\"><h2>Importing data<\/h2>\n<\/a><p>There's a tool for this, and it specifically supports these iCloud\nPhotos backup archives! The tool is called <a class=\"reference external\" href=\"https:\/\/github.com\/simulot\/immich-go\">immich-go<\/a>. There's a helpful <a class=\"reference external\" href=\"https:\/\/github.com\/simulot\/immich-go\/blob\/main\/docs\/examples.md#icloud-import\">iCloud\nimport guide<\/a>\nwhich you can follow. It's fast and worked great for me.<\/p>\n<\/div>\n","category":[{"@attributes":{"term":"tech"}},{"@attributes":{"term":"icloud"}},{"@attributes":{"term":"apple"}},{"@attributes":{"term":"immich"}}]},{"title":"Blocking Traffic to Docker Containers","link":{"@attributes":{"href":"https:\/\/blog.feld.me\/posts\/2026\/03\/blocking-traffic-docker\/","rel":"alternate"}},"published":"2026-03-27T13:07:03-07:00","updated":"2026-03-27T13:24:00-07:00","author":{"name":"feld"},"id":"tag:blog.feld.me,2026-03-27:\/posts\/2026\/03\/blocking-traffic-docker\/","summary":"<p>I had an issue the other day where a VPS hosting services with Docker was being hammered by a scraper and I wanted to block the IPs of this traffic to reduce the load on the server. Running Linux\/Docker is not my preferred platform, but I'm not that rusty \u2026<\/p>","content":"<p>I had an issue the other day where a VPS hosting services with Docker was being hammered by a scraper and I wanted to block the IPs of this traffic to reduce the load on the server. Running Linux\/Docker is not my preferred platform, but I'm not that rusty when it comes to Linux or so I thought...<\/p>\n<p>First try: add a rule to the <code>INPUT<\/code> chain of iptables [failure]<\/p>\n<p>That makes sense, it's host traffic. I know we need forwarding enabled for Docker though...<\/p>\n<p>Second try: add a rule to the <code>FORWARD<\/code> chain of iptables [failure]<\/p>\n<p>Alright, I guess since there's some NAT involved and whatnot maybe it needs something else. Documentation says there's a specific chain for this...<\/p>\n<p>Third try: add a rule to the <code>DOCKER-USER<\/code> chain of iptables [failure]<\/p>\n<p>What the heck is going on?<\/p>\n<p>Digging, digging digging... somehow <code>docker-proxy<\/code> is bypassing iptables completely. I get that there are network namespaces, but this makes no sense to me. How do you control the iptables rules for another namespace? Is that a thing? In FreeBSD we'd have VNET and separate firewalls completely for them. Apparently the fix is to disable the userland proxy in Docker. This has some potential drawbacks (go look them up), but for most people it's probably fine. This is not the same thing as running with &quot;host&quot; networking in Docker.<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;userland-proxy&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kc\">false<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>Put that in <code>\/etc\/docker\/daemon.json<\/code>, restart <code>dockerd<\/code> and now the DROP rule will work when placed in the <code>DOCKER-USER<\/code> chain.<\/p>\n","category":[{"@attributes":{"term":"tech"}},{"@attributes":{"term":"docker"}},{"@attributes":{"term":"firewall"}}]},{"title":"Pairing A PS5 DualSense Controller On Windows 11","link":{"@attributes":{"href":"https:\/\/blog.feld.me\/posts\/2026\/03\/pairing-ps5-dualsense-bluetooth-windows\/","rel":"alternate"}},"published":"2026-03-25T18:49:11-07:00","updated":"2026-03-25T18:57:28-07:00","author":{"name":"feld"},"id":"tag:blog.feld.me,2026-03-25:\/posts\/2026\/03\/pairing-ps5-dualsense-bluetooth-windows\/","summary":"<p>I have a Windows 11 gaming PC and was struggling to get my PS5 DualSense controller to show up and pair correctly with Bluetooth. I tried resetting the controller, etc etc but it would never show up. I had plugged it in via USB-C before, so the controller was known \u2026<\/p>","content":"<p>I have a Windows 11 gaming PC and was struggling to get my PS5 DualSense controller to show up and pair correctly with Bluetooth. I tried resetting the controller, etc etc but it would never show up. I had plugged it in via USB-C before, so the controller was known at least as a USB device and that perhaps could be part of the problem. Dunno for certain.<\/p>\n<p>Anyway, a <a class=\"reference external\" href=\"https:\/\/learn.microsoft.com\/en-us\/answers\/questions\/5590014\/ps5-dualsense-controller-won-t-pair-on-windows-11\">Microsoft Questions article<\/a> has a suggestion that ended up working, but the Powershell example was slightly wrong as they used a <code>\/delete-device<\/code> flag that should have been a <code>\/remove-device<\/code> flag. This variant worked when run in PowerShell as administrator:<\/p>\n<pre class=\"code powershell literal-block\">\n<span class=\"c\"># Run as Administrator<\/span>\n<span class=\"nv\">$devices<\/span> <span class=\"p\">=<\/span> <span class=\"nb\">Get-PnpDevice<\/span> <span class=\"p\">|<\/span> <span class=\"nb\">Where-Object<\/span> <span class=\"p\">{<\/span> <span class=\"nv\">$_<\/span><span class=\"p\">.<\/span><span class=\"n\">FriendlyName<\/span> <span class=\"o\">-like<\/span> <span class=\"s2\">&quot;*DualSense*&quot;<\/span> <span class=\"p\">}<\/span>\n<span class=\"k\">foreach<\/span> <span class=\"p\">(<\/span><span class=\"nv\">$device<\/span> <span class=\"k\">in<\/span> <span class=\"nv\">$devices<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n    <span class=\"nb\">Write-Host<\/span> <span class=\"s2\">&quot;Removing device: <\/span><span class=\"p\">$(<\/span><span class=\"nv\">$device<\/span><span class=\"p\">.<\/span><span class=\"n\">FriendlyName<\/span><span class=\"p\">)<\/span><span class=\"s2\">&quot;<\/span>\n    <span class=\"nv\">$instanceId<\/span> <span class=\"p\">=<\/span> <span class=\"nv\">$device<\/span><span class=\"p\">.<\/span><span class=\"n\">InstanceId<\/span>\n    <span class=\"n\">pnputil<\/span> <span class=\"p\">\/<\/span><span class=\"nb\">remove-device<\/span> <span class=\"s2\">&quot;$instanceId&quot;<\/span>\n<span class=\"p\">}<\/span>\n<\/pre>\n<p>After this I restarted the Bluetooth service with <code>net stop bthserv<\/code> and <code>net start bthserv<\/code>, attempted to pair the controller again (hold PS button and Share button), and it worked.<\/p>\n","category":[{"@attributes":{"term":"tech"}},{"@attributes":{"term":"ps5"}},{"@attributes":{"term":"windows"}},{"@attributes":{"term":"bluetooth"}}]},{"title":"Phoenix and Tailwind on FreeBSD","link":{"@attributes":{"href":"https:\/\/blog.feld.me\/posts\/2026\/02\/phoenix-tailwind-freebsd\/","rel":"alternate"}},"published":"2026-02-25T16:48:48-08:00","updated":"2026-03-17T10:30:19-07:00","author":{"name":"feld"},"id":"tag:blog.feld.me,2026-02-25:\/posts\/2026\/02\/phoenix-tailwind-freebsd\/","summary":"<p>Phoenix development on FreeBSD can be a bit of a pain because they integrated TailwindCSS nicely, but it depends on some prebuilt tailwind and esbuild binaries that it auto-fetches for you. Unfortunately, there aren't any prebuilt binaries for tailwind on FreeBSD so lots of people seem to run into issues \u2026<\/p>","content":"<p>Phoenix development on FreeBSD can be a bit of a pain because they integrated TailwindCSS nicely, but it depends on some prebuilt tailwind and esbuild binaries that it auto-fetches for you. Unfortunately, there aren't any prebuilt binaries for tailwind on FreeBSD so lots of people seem to run into issues. I've banged my head about this a lot with various hacky solutions, but so far this one seems the simplest. It also should work if you try to build or deploy on different OSes as long as you have node and npm installed.<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"n\">diff<\/span><span class=\"w\"> <\/span><span class=\"o\">--<\/span><span class=\"n\">git<\/span><span class=\"w\"> <\/span><span class=\"n\">a<\/span><span class=\"o\">\/<\/span><span class=\"n\">assets<\/span><span class=\"o\">\/<\/span><span class=\"n\">package<\/span><span class=\"o\">.<\/span><span class=\"n\">json<\/span><span class=\"w\"> <\/span><span class=\"n\">b<\/span><span class=\"o\">\/<\/span><span class=\"n\">assets<\/span><span class=\"o\">\/<\/span><span class=\"n\">package<\/span><span class=\"o\">.<\/span><span class=\"n\">json<\/span>\n<span class=\"n\">new<\/span><span class=\"w\"> <\/span><span class=\"n\">file<\/span><span class=\"w\"> <\/span><span class=\"n\">mode<\/span><span class=\"w\"> <\/span><span class=\"mi\">100644<\/span>\n<span class=\"n\">index<\/span><span class=\"w\"> <\/span><span class=\"mi\">0000000<\/span><span class=\"o\">..<\/span><span class=\"n\">b5813eb<\/span>\n<span class=\"o\">---<\/span><span class=\"w\"> <\/span><span class=\"o\">\/<\/span><span class=\"n\">dev<\/span><span class=\"o\">\/<\/span><span class=\"n\">null<\/span>\n<span class=\"o\">+++<\/span><span class=\"w\"> <\/span><span class=\"n\">b<\/span><span class=\"o\">\/<\/span><span class=\"n\">assets<\/span><span class=\"o\">\/<\/span><span class=\"n\">package<\/span><span class=\"o\">.<\/span><span class=\"n\">json<\/span>\n<span class=\"err\">@@<\/span><span class=\"w\"> <\/span><span class=\"o\">-<\/span><span class=\"mi\">0<\/span><span class=\"p\">,<\/span><span class=\"mi\">0<\/span><span class=\"w\"> <\/span><span class=\"o\">+<\/span><span class=\"mi\">1<\/span><span class=\"p\">,<\/span><span class=\"mi\">8<\/span><span class=\"w\"> <\/span><span class=\"err\">@@<\/span>\n<span class=\"o\">+<\/span><span class=\"p\">{<\/span>\n<span class=\"o\">+<\/span><span class=\"w\">  <\/span><span class=\"s2\">&quot;name&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;assets&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"o\">+<\/span><span class=\"w\">  <\/span><span class=\"s2\">&quot;dependencies&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"o\">+<\/span><span class=\"w\">    <\/span><span class=\"s2\">&quot;daisyui&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;^5.0.16&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"o\">+<\/span><span class=\"w\">    <\/span><span class=\"s2\">&quot;tailwindcss&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;^4.1.3&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"o\">+<\/span><span class=\"w\">    <\/span><span class=\"s2\">&quot;@tailwindcss\/cli&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;^4.1.3&quot;<\/span>\n<span class=\"o\">+<\/span><span class=\"w\">  <\/span><span class=\"p\">}<\/span>\n<span class=\"o\">+<\/span><span class=\"p\">}<\/span>\n<\/pre><\/div>\n<div class=\"highlight\"><pre><span><\/span><span class=\"n\">diff<\/span><span class=\"w\"> <\/span><span class=\"o\">--<\/span><span class=\"n\">git<\/span><span class=\"w\"> <\/span><span class=\"n\">a<\/span><span class=\"o\">\/<\/span><span class=\"n\">assets<\/span><span class=\"o\">\/<\/span><span class=\"n\">css<\/span><span class=\"o\">\/<\/span><span class=\"n\">app<\/span><span class=\"o\">.<\/span><span class=\"n\">css<\/span><span class=\"w\"> <\/span><span class=\"n\">b<\/span><span class=\"o\">\/<\/span><span class=\"n\">assets<\/span><span class=\"o\">\/<\/span><span class=\"n\">css<\/span><span class=\"o\">\/<\/span><span class=\"n\">app<\/span><span class=\"o\">.<\/span><span class=\"n\">css<\/span>\n<span class=\"n\">index<\/span><span class=\"w\"> <\/span><span class=\"n\">ebb15d9<\/span><span class=\"o\">..<\/span><span class=\"n\">f564733<\/span><span class=\"w\"> <\/span><span class=\"mi\">100644<\/span>\n<span class=\"o\">---<\/span><span class=\"w\"> <\/span><span class=\"n\">a<\/span><span class=\"o\">\/<\/span><span class=\"n\">assets<\/span><span class=\"o\">\/<\/span><span class=\"n\">css<\/span><span class=\"o\">\/<\/span><span class=\"n\">app<\/span><span class=\"o\">.<\/span><span class=\"n\">css<\/span>\n<span class=\"o\">+++<\/span><span class=\"w\"> <\/span><span class=\"n\">b<\/span><span class=\"o\">\/<\/span><span class=\"n\">assets<\/span><span class=\"o\">\/<\/span><span class=\"n\">css<\/span><span class=\"o\">\/<\/span><span class=\"n\">app<\/span><span class=\"o\">.<\/span><span class=\"n\">css<\/span>\n<span class=\"err\">@@<\/span><span class=\"w\"> <\/span><span class=\"o\">-<\/span><span class=\"mi\">13<\/span><span class=\"p\">,<\/span><span class=\"mi\">7<\/span><span class=\"w\"> <\/span><span class=\"o\">+<\/span><span class=\"mi\">13<\/span><span class=\"p\">,<\/span><span class=\"mi\">7<\/span><span class=\"w\"> <\/span><span class=\"err\">@@<\/span>\n<span class=\"w\"> <\/span><span class=\"o\">\/*<\/span><span class=\"w\"> <\/span><span class=\"n\">daisyUI<\/span><span class=\"w\"> <\/span><span class=\"nc\">Tailwind<\/span><span class=\"w\"> <\/span><span class=\"nc\">Plugin<\/span><span class=\"o\">.<\/span><span class=\"w\"> <\/span><span class=\"nc\">You<\/span><span class=\"w\"> <\/span><span class=\"n\">can<\/span><span class=\"w\"> <\/span><span class=\"n\">update<\/span><span class=\"w\"> <\/span><span class=\"n\">this<\/span><span class=\"w\"> <\/span><span class=\"n\">file<\/span><span class=\"w\"> <\/span><span class=\"n\">by<\/span><span class=\"w\"> <\/span><span class=\"n\">fetching<\/span><span class=\"w\"> <\/span><span class=\"n\">the<\/span><span class=\"w\"> <\/span><span class=\"n\">latest<\/span><span class=\"w\"> <\/span><span class=\"n\">version<\/span><span class=\"w\"> <\/span><span class=\"ss\">with<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">curl<\/span><span class=\"w\"> <\/span><span class=\"o\">-<\/span><span class=\"n\">sLO<\/span><span class=\"w\"> <\/span><span class=\"n\">https<\/span><span class=\"ss\">:\/<\/span><span class=\"o\">\/<\/span><span class=\"n\">github<\/span><span class=\"o\">.<\/span><span class=\"n\">com<\/span><span class=\"o\">\/<\/span><span class=\"n\">saadeghi<\/span><span class=\"o\">\/<\/span><span class=\"n\">daisyui<\/span><span class=\"o\">\/<\/span><span class=\"n\">releases<\/span><span class=\"o\">\/<\/span><span class=\"n\">latest<\/span><span class=\"o\">\/<\/span><span class=\"n\">download<\/span><span class=\"o\">\/<\/span><span class=\"n\">daisyui<\/span><span class=\"o\">.<\/span><span class=\"n\">js<\/span>\n<span class=\"w\">    <\/span><span class=\"nc\">Make<\/span><span class=\"w\"> <\/span><span class=\"n\">sure<\/span><span class=\"w\"> <\/span><span class=\"n\">to<\/span><span class=\"w\"> <\/span><span class=\"n\">look<\/span><span class=\"w\"> <\/span><span class=\"n\">at<\/span><span class=\"w\"> <\/span><span class=\"n\">the<\/span><span class=\"w\"> <\/span><span class=\"n\">daisyUI<\/span><span class=\"w\"> <\/span><span class=\"ss\">changelog<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"n\">https<\/span><span class=\"ss\">:\/<\/span><span class=\"o\">\/<\/span><span class=\"n\">daisyui<\/span><span class=\"o\">.<\/span><span class=\"n\">com<\/span><span class=\"o\">\/<\/span><span class=\"n\">docs<\/span><span class=\"o\">\/<\/span><span class=\"n\">changelog<\/span><span class=\"o\">\/<\/span><span class=\"w\"> <\/span><span class=\"o\">*\/<\/span>\n<span class=\"o\">-<\/span><span class=\"na\">@plugin<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;..\/vendor\/daisyui&quot;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"o\">+<\/span><span class=\"na\">@plugin<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;daisyui&quot;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">   <\/span><span class=\"ss\">themes<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"no\">false<\/span><span class=\"p\">;<\/span>\n<span class=\"w\"> <\/span><span class=\"p\">}<\/span>\n\n<span class=\"err\">@@<\/span><span class=\"w\"> <\/span><span class=\"o\">-<\/span><span class=\"mi\">21<\/span><span class=\"p\">,<\/span><span class=\"mi\">7<\/span><span class=\"w\"> <\/span><span class=\"o\">+<\/span><span class=\"mi\">21<\/span><span class=\"p\">,<\/span><span class=\"mi\">7<\/span><span class=\"w\"> <\/span><span class=\"err\">@@<\/span>\n<span class=\"w\">   <\/span><span class=\"n\">curl<\/span><span class=\"w\"> <\/span><span class=\"o\">-<\/span><span class=\"n\">sLO<\/span><span class=\"w\"> <\/span><span class=\"n\">https<\/span><span class=\"ss\">:\/<\/span><span class=\"o\">\/<\/span><span class=\"n\">github<\/span><span class=\"o\">.<\/span><span class=\"n\">com<\/span><span class=\"o\">\/<\/span><span class=\"n\">saadeghi<\/span><span class=\"o\">\/<\/span><span class=\"n\">daisyui<\/span><span class=\"o\">\/<\/span><span class=\"n\">releases<\/span><span class=\"o\">\/<\/span><span class=\"n\">latest<\/span><span class=\"o\">\/<\/span><span class=\"n\">download<\/span><span class=\"o\">\/<\/span><span class=\"n\">daisyui<\/span><span class=\"o\">-<\/span><span class=\"n\">theme<\/span><span class=\"o\">.<\/span><span class=\"n\">js<\/span>\n<span class=\"w\">   <\/span><span class=\"nc\">We<\/span><span class=\"w\"> <\/span><span class=\"n\">ship<\/span><span class=\"w\"> <\/span><span class=\"n\">with<\/span><span class=\"w\"> <\/span><span class=\"n\">two<\/span><span class=\"w\"> <\/span><span class=\"n\">themes<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">a<\/span><span class=\"w\"> <\/span><span class=\"n\">light<\/span><span class=\"w\"> <\/span><span class=\"n\">one<\/span><span class=\"w\"> <\/span><span class=\"n\">inspired<\/span><span class=\"w\"> <\/span><span class=\"n\">on<\/span><span class=\"w\"> <\/span><span class=\"nc\">Phoenix<\/span><span class=\"w\"> <\/span><span class=\"n\">colors<\/span><span class=\"w\"> <\/span><span class=\"ow\">and<\/span><span class=\"w\"> <\/span><span class=\"n\">a<\/span><span class=\"w\"> <\/span><span class=\"n\">dark<\/span><span class=\"w\"> <\/span><span class=\"n\">one<\/span><span class=\"w\"> <\/span><span class=\"n\">inspired<\/span>\n<span class=\"w\">   <\/span><span class=\"n\">on<\/span><span class=\"w\"> <\/span><span class=\"nc\">Elixir<\/span><span class=\"w\"> <\/span><span class=\"n\">colors<\/span><span class=\"o\">.<\/span><span class=\"w\"> <\/span><span class=\"nc\">Build<\/span><span class=\"w\"> <\/span><span class=\"n\">your<\/span><span class=\"w\"> <\/span><span class=\"n\">own<\/span><span class=\"w\"> <\/span><span class=\"ss\">at<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"n\">https<\/span><span class=\"ss\">:\/<\/span><span class=\"o\">\/<\/span><span class=\"n\">daisyui<\/span><span class=\"o\">.<\/span><span class=\"n\">com<\/span><span class=\"o\">\/<\/span><span class=\"n\">theme<\/span><span class=\"o\">-<\/span><span class=\"n\">generator<\/span><span class=\"o\">\/<\/span><span class=\"w\"> <\/span><span class=\"o\">*\/<\/span>\n<span class=\"o\">-<\/span><span class=\"na\">@plugin<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;..\/vendor\/daisyui-theme&quot;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"o\">+<\/span><span class=\"na\">@plugin<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;daisyui\/theme&quot;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">   <\/span><span class=\"ss\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;dark&quot;<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">   <\/span><span class=\"ss\">default<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"no\">false<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">   <\/span><span class=\"ss\">prefersdark<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"no\">true<\/span><span class=\"p\">;<\/span>\n<span class=\"err\">@@<\/span><span class=\"w\"> <\/span><span class=\"o\">-<\/span><span class=\"mi\">56<\/span><span class=\"p\">,<\/span><span class=\"mi\">7<\/span><span class=\"w\"> <\/span><span class=\"o\">+<\/span><span class=\"mi\">56<\/span><span class=\"p\">,<\/span><span class=\"mi\">7<\/span><span class=\"w\"> <\/span><span class=\"err\">@@<\/span>\n<span class=\"w\">   <\/span><span class=\"o\">--<\/span><span class=\"ss\">noise<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"mi\">0<\/span><span class=\"p\">;<\/span>\n<span class=\"w\"> <\/span><span class=\"p\">}<\/span>\n\n<span class=\"o\">-<\/span><span class=\"na\">@plugin<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;..\/vendor\/daisyui-theme&quot;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"o\">+<\/span><span class=\"na\">@plugin<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;daisyui\/theme&quot;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">   <\/span><span class=\"ss\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;light&quot;<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">   <\/span><span class=\"ss\">default<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"no\">true<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">   <\/span><span class=\"ss\">prefersdark<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"no\">false<\/span><span class=\"p\">;<\/span>\n<\/pre><\/div>\n<div class=\"highlight\"><pre><span><\/span><span class=\"n\">diff<\/span><span class=\"w\"> <\/span><span class=\"o\">--<\/span><span class=\"n\">git<\/span><span class=\"w\"> <\/span><span class=\"n\">a<\/span><span class=\"o\">\/<\/span><span class=\"n\">config<\/span><span class=\"o\">\/<\/span><span class=\"n\">config<\/span><span class=\"o\">.<\/span><span class=\"n\">exs<\/span><span class=\"w\"> <\/span><span class=\"n\">b<\/span><span class=\"o\">\/<\/span><span class=\"n\">config<\/span><span class=\"o\">\/<\/span><span class=\"n\">config<\/span><span class=\"o\">.<\/span><span class=\"n\">exs<\/span>\n<span class=\"n\">index<\/span><span class=\"w\"> <\/span><span class=\"mi\">331305<\/span><span class=\"n\">d<\/span><span class=\"o\">..<\/span><span class=\"n\">b300fc7<\/span><span class=\"w\"> <\/span><span class=\"mi\">100644<\/span>\n<span class=\"o\">---<\/span><span class=\"w\"> <\/span><span class=\"n\">a<\/span><span class=\"o\">\/<\/span><span class=\"n\">config<\/span><span class=\"o\">\/<\/span><span class=\"n\">config<\/span><span class=\"o\">.<\/span><span class=\"n\">exs<\/span>\n<span class=\"o\">+++<\/span><span class=\"w\"> <\/span><span class=\"n\">b<\/span><span class=\"o\">\/<\/span><span class=\"n\">config<\/span><span class=\"o\">\/<\/span><span class=\"n\">config<\/span><span class=\"o\">.<\/span><span class=\"n\">exs<\/span>\n<span class=\"err\">@@<\/span><span class=\"w\"> <\/span><span class=\"o\">-<\/span><span class=\"mi\">36<\/span><span class=\"p\">,<\/span><span class=\"mi\">7<\/span><span class=\"w\"> <\/span><span class=\"o\">+<\/span><span class=\"mi\">36<\/span><span class=\"p\">,<\/span><span class=\"mi\">7<\/span><span class=\"w\"> <\/span><span class=\"err\">@@<\/span><span class=\"w\"> <\/span><span class=\"n\">config<\/span><span class=\"w\"> <\/span><span class=\"ss\">:esbuild<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">   <\/span><span class=\"ss\">version<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;0.25.4&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">   <\/span><span class=\"ss\">your_project<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span>\n<span class=\"w\">     <\/span><span class=\"ss\">args<\/span><span class=\"p\">:<\/span>\n<span class=\"o\">-<\/span><span class=\"w\">      <\/span><span class=\"sx\">~w(js\/app.js --bundle --target=es2022 --outdir=..\/priv\/static\/assets\/js --external:\/fonts\/* --external:\/images\/* --alias:@=.)<\/span><span class=\"p\">,<\/span>\n<span class=\"o\">+<\/span><span class=\"w\">      <\/span><span class=\"sx\">~w(js\/app.js --bundle --target=es2022 --outdir=..\/priv\/static\/assets\/js --external:\/fonts\/* --external:\/images\/* --alias:@=. --conditions=style)<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">     <\/span><span class=\"ss\">cd<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">Path<\/span><span class=\"o\">.<\/span><span class=\"n\">expand<\/span><span class=\"p\">(<\/span><span class=\"s2\">&quot;..\/assets&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"bp\">__DIR__<\/span><span class=\"p\">),<\/span>\n<span class=\"w\">     <\/span><span class=\"ss\">env<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">%{<\/span><span class=\"s2\">&quot;NODE_PATH&quot;<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"nc\">Path<\/span><span class=\"o\">.<\/span><span class=\"n\">expand<\/span><span class=\"p\">(<\/span><span class=\"s2\">&quot;..\/deps&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"bp\">__DIR__<\/span><span class=\"p\">),<\/span><span class=\"w\"> <\/span><span class=\"nc\">Mix.Project<\/span><span class=\"o\">.<\/span><span class=\"n\">build_path<\/span><span class=\"p\">()]}<\/span>\n<span class=\"w\">   <\/span><span class=\"p\">]<\/span>\n<span class=\"err\">@@<\/span><span class=\"w\"> <\/span><span class=\"o\">-<\/span><span class=\"mi\">44<\/span><span class=\"p\">,<\/span><span class=\"mi\">13<\/span><span class=\"w\"> <\/span><span class=\"o\">+<\/span><span class=\"mi\">44<\/span><span class=\"p\">,<\/span><span class=\"mi\">15<\/span><span class=\"w\"> <\/span><span class=\"err\">@@<\/span><span class=\"w\"> <\/span><span class=\"n\">config<\/span><span class=\"w\"> <\/span><span class=\"ss\">:esbuild<\/span><span class=\"p\">,<\/span>\n<span class=\"w\"> <\/span><span class=\"c1\"># Configure tailwind (the version is required)<\/span>\n<span class=\"w\"> <\/span><span class=\"n\">config<\/span><span class=\"w\"> <\/span><span class=\"ss\">:tailwind<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">   <\/span><span class=\"ss\">version<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;4.1.12&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"o\">+<\/span><span class=\"w\">  <\/span><span class=\"ss\">version_check<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"no\">false<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">   <\/span><span class=\"ss\">your_project<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span>\n<span class=\"w\">     <\/span><span class=\"ss\">args<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"sx\">~w(<\/span>\n<span class=\"sx\">       --input=assets\/css\/app.css<\/span>\n<span class=\"sx\">       --output=priv\/static\/assets\/css\/app.css<\/span>\n<span class=\"sx\">     )<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">     <\/span><span class=\"ss\">cd<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">Path<\/span><span class=\"o\">.<\/span><span class=\"n\">expand<\/span><span class=\"p\">(<\/span><span class=\"s2\">&quot;..&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"bp\">__DIR__<\/span><span class=\"p\">)<\/span>\n<span class=\"o\">-<\/span><span class=\"w\">  <\/span><span class=\"p\">]<\/span>\n<span class=\"o\">+<\/span><span class=\"w\">  <\/span><span class=\"p\">],<\/span>\n<span class=\"o\">+<\/span><span class=\"w\">  <\/span><span class=\"ss\">path<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">Path<\/span><span class=\"o\">.<\/span><span class=\"n\">expand<\/span><span class=\"p\">(<\/span><span class=\"s2\">&quot;..\/assets\/node_modules\/.bin\/tailwindcss&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"bp\">__DIR__<\/span><span class=\"p\">)<\/span>\n<\/pre><\/div>\n<p>\u26a0\ufe0f Note the esbuild change which adds <code>--conditions=style<\/code>. I needed to add this to get around errors which I forgot to document. Sorry.<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"n\">diff<\/span><span class=\"w\"> <\/span><span class=\"o\">--<\/span><span class=\"n\">git<\/span><span class=\"w\"> <\/span><span class=\"n\">a<\/span><span class=\"o\">\/<\/span><span class=\"n\">mix<\/span><span class=\"o\">.<\/span><span class=\"n\">exs<\/span><span class=\"w\"> <\/span><span class=\"n\">b<\/span><span class=\"o\">\/<\/span><span class=\"n\">mix<\/span><span class=\"o\">.<\/span><span class=\"n\">exs<\/span>\n<span class=\"n\">index<\/span><span class=\"w\"> <\/span><span class=\"mi\">2<\/span><span class=\"n\">c3e407<\/span><span class=\"o\">..<\/span><span class=\"mi\">1<\/span><span class=\"n\">d59701<\/span><span class=\"w\"> <\/span><span class=\"mi\">100644<\/span>\n<span class=\"o\">---<\/span><span class=\"w\"> <\/span><span class=\"n\">a<\/span><span class=\"o\">\/<\/span><span class=\"n\">mix<\/span><span class=\"o\">.<\/span><span class=\"n\">exs<\/span>\n<span class=\"o\">+++<\/span><span class=\"w\"> <\/span><span class=\"n\">b<\/span><span class=\"o\">\/<\/span><span class=\"n\">mix<\/span><span class=\"o\">.<\/span><span class=\"n\">exs<\/span>\n<span class=\"err\">@@<\/span><span class=\"w\"> <\/span><span class=\"o\">-<\/span><span class=\"mi\">81<\/span><span class=\"p\">,<\/span><span class=\"mi\">7<\/span><span class=\"w\"> <\/span><span class=\"o\">+<\/span><span class=\"mi\">90<\/span><span class=\"p\">,<\/span><span class=\"mi\">10<\/span><span class=\"w\"> <\/span><span class=\"err\">@@<\/span><span class=\"w\"> <\/span><span class=\"kd\">defmodule<\/span><span class=\"w\"> <\/span><span class=\"nc\">Booter.MixProject<\/span><span class=\"w\"> <\/span><span class=\"k\">do<\/span>\n<span class=\"w\">       <\/span><span class=\"s2\">&quot;ecto.setup&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"s2\">&quot;ecto.create&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;ecto.migrate&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;run priv\/repo\/seeds.exs&quot;<\/span><span class=\"p\">],<\/span>\n<span class=\"w\">       <\/span><span class=\"s2\">&quot;ecto.reset&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"s2\">&quot;ecto.drop&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;ecto.setup&quot;<\/span><span class=\"p\">],<\/span>\n<span class=\"w\">       <\/span><span class=\"ss\">test<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"s2\">&quot;ecto.create --quiet&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;ecto.migrate --quiet&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;test&quot;<\/span><span class=\"p\">],<\/span>\n<span class=\"o\">-<\/span><span class=\"w\">      <\/span><span class=\"s2\">&quot;assets.setup&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"s2\">&quot;tailwind.install --if-missing&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;esbuild.install --if-missing&quot;<\/span><span class=\"p\">],<\/span>\n<span class=\"o\">+<\/span><span class=\"w\">      <\/span><span class=\"s2\">&quot;assets.setup&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span>\n<span class=\"o\">+<\/span><span class=\"w\">        <\/span><span class=\"s2\">&quot;cmd --cd assets npm install&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"o\">+<\/span><span class=\"w\">        <\/span><span class=\"s2\">&quot;esbuild.install --if-missing&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"o\">+<\/span><span class=\"w\">      <\/span><span class=\"p\">],<\/span>\n<span class=\"w\">       <\/span><span class=\"s2\">&quot;assets.build&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"s2\">&quot;compile&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;tailwind your_project&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;esbuild your_project&quot;<\/span><span class=\"p\">],<\/span>\n<span class=\"w\">       <\/span><span class=\"s2\">&quot;assets.deploy&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span>\n<span class=\"w\">         <\/span><span class=\"s2\">&quot;tailwind your_project --minify&quot;<\/span><span class=\"p\">,<\/span>\n<\/pre><\/div>\n<p>Apply those changes and now <code>mix assets.setup<\/code> etc will do the right thing: tailwind and daisy will be installed with npm and stored in the <code>assets\/<\/code> directory, building and deploying the assets will work without errors.<\/p>\n","category":[{"@attributes":{"term":"tech"}},{"@attributes":{"term":"elixir"}},{"@attributes":{"term":"phoenix"}},{"@attributes":{"term":"tailwindcss"}}]},{"title":"Using The New Bridges of FreeBSD 15","link":{"@attributes":{"href":"https:\/\/blog.feld.me\/posts\/2026\/02\/using-new-bridges-freebsd-15\/","rel":"alternate"}},"published":"2026-02-20T11:44:15-08:00","updated":"2026-03-19T11:37:27-07:00","author":{"name":"feld"},"id":"tag:blog.feld.me,2026-02-20:\/posts\/2026\/02\/using-new-bridges-freebsd-15\/","summary":"<p>FreeBSD 15 comes with a <a class=\"reference external\" href=\"https:\/\/people.freebsd.org\/~ivy\/bridge_vlan_filtering.txt\">new bridging implementation<\/a> which has native\nsupport for VLANs. They have also soft-deprecated the ability to have\nany layer 3 addresses on member interfaces which makes it behave like a\nreal hardware switch. The <code>net.link.bridge.member_ifaddrs<\/code> sysctl\ncontrols this behavior and it will \u2026<\/p>","content":"<p>FreeBSD 15 comes with a <a class=\"reference external\" href=\"https:\/\/people.freebsd.org\/~ivy\/bridge_vlan_filtering.txt\">new bridging implementation<\/a> which has native\nsupport for VLANs. They have also soft-deprecated the ability to have\nany layer 3 addresses on member interfaces which makes it behave like a\nreal hardware switch. The <code>net.link.bridge.member_ifaddrs<\/code> sysctl\ncontrols this behavior and it will be removed in FreeBSD 16.0-RELEASE,\nsame as if set to zero.<\/p>\n<p>One of the primary benefits of the new\nimplementation is that you can have a single bridge for everything and\nthe packet processing has been optimized. Previously the\n<a class=\"reference external\" href=\"https:\/\/cgit.freebsd.org\/src\/commit\/?id=85967694b4590a436c56a061b397620e342784ae\">bridge performance would degrade<\/a>\nfor some operations as the number of member interfaces increased,\nbut I'm not clear on how significant it really was. It didn't impact\ngigabit for me, but perhaps you'd see it if you were trying to get\n10gbit line rate out of it.<\/p>\n<div class=\"section\" id=\"old-bridging\">\n<a class=\"toclink\" href=\"#old-bridging\" title=\"Permalink to this section\"><h2>Old Bridging<\/h2>\n<\/a><p>The old design when working with bridges and VLANs was like so:<\/p>\n<ul class=\"simple\">\n<li>Create a bridge for a specific VLAN<\/li>\n<li>Create a VLAN interface off a physical interface<\/li>\n<li>Attach the VLAN interface to the bridge<\/li>\n<li>Now all your bridge members are able to communicate to that VLAN<\/li>\n<\/ul>\n<p>As you can imagine this starts to get messy especially if you have a lot of\nVLANs to work with or try to deploy jails that are meant to communicate over\nmultiple VLANs. An example from my old <code>rc.conf<\/code> looked something like this:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\"># Bhyve and VNET<\/span>\n<span class=\"c1\"># previously a long list of flags of disabled features were here<\/span>\n<span class=\"c1\"># and are irrelevant to this example<\/span>\n<span class=\"nv\">ifconfig_ix1<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;up&quot;<\/span>\n\n<span class=\"nv\">vlans_ix1<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;vlan2 vlan3 vlan128&quot;<\/span>\n<span class=\"nv\">create_args_vlan2<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;vlan 2&quot;<\/span>\n<span class=\"nv\">create_args_vlan3<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;vlan 3&quot;<\/span>\n<span class=\"nv\">create_args_vlan128<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;vlan 128&quot;<\/span>\n\n<span class=\"nv\">cloned_interfaces<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;bridge0 bridge1 bridge2&quot;<\/span>\n\n<span class=\"nv\">ifconfig_bridge0_name<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;vlan2bridge&quot;<\/span>\n<span class=\"nv\">ifconfig_vlan2bridge<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;addm vlan2 up&quot;<\/span>\n\n<span class=\"nv\">ifconfig_bridge1_name<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;vlan3bridge&quot;<\/span>\n<span class=\"nv\">ifconfig_vlan3bridge<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;addm vlan3 up&quot;<\/span>\n\n<span class=\"nv\">ifconfig_bridge2_name<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;vlan128bridge&quot;<\/span>\n<span class=\"nv\">ifconfig_vlan128bridge<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;addm vlan128 addm&quot;<\/span>\n<\/pre><\/div>\n<p>It's a lot of work for something that feels so simple if you're familiar with real network gear.<\/p>\n<\/div>\n<div class=\"section\" id=\"new-bridging\">\n<a class=\"toclink\" href=\"#new-bridging\" title=\"Permalink to this section\"><h2>New Bridging<\/h2>\n<\/a><p>The new design allows you to have a single bridge and specify\ntagged and\/or untagged VLANs for each bridge member. Here's what all of\nthat condenses down to.<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\"># Bhyve and VNET<\/span>\n<span class=\"c1\"># previously a long list of flags of disabled features were here<\/span>\n<span class=\"c1\"># and are irrelevant to this example<\/span>\n<span class=\"nv\">ifconfig_ix1<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;up&quot;<\/span>\n\n<span class=\"nv\">cloned_interfaces<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;bridge0&quot;<\/span>\n<span class=\"nv\">ifconfig_bridge0<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;vlanfilter addm ix1 tagged 2,3,128&quot;<\/span>\n<\/pre><\/div>\n<p>That's it. Much simpler, isn't it? Please don't overlook the <code>vlanfilter<\/code> flag on the bridge. If you are missing this you can add members to the bridge with untagged VLANs but you'll get this error when trying to add tagged VLANs:<\/p>\n<pre class=\"literal-block\">\nifconfig: BRDGSIFVLANSET ix1: Invalid argument (extended error VLAN filtering not enabled)\n<\/pre>\n<\/div>\n<div class=\"section\" id=\"vnet-jails\">\n<a class=\"toclink\" href=\"#vnet-jails\" title=\"Permalink to this section\"><h2>VNET Jails<\/h2>\n<\/a><p>VNET jails posed a problem for me because I was relying on the &quot;unofficial&quot; <code>\/usr\/share\/examples\/jails\/jib<\/code> script which can handle creating <a class=\"reference external\" href=\"https:\/\/man.freebsd.org\/cgi\/man.cgi?epair(4)\">epair(4)<\/a> devices for the jails. I'd copy this script into my <code>PATH<\/code>, make it executable, and then I could use it. A simplified example of this jail configuration looks like this:<\/p>\n<pre class=\"literal-block\">\nwebserver {\n    exec.prestart += &quot;jib addm $name vlan2&quot;;\n    exec.poststop = &quot;jib destroy $name&quot;;\n    vnet;\n    vnet.interface = &quot;e0b_$name&quot;;\n}\n<\/pre>\n<p>This would handle creating a stable name of <code>epair(4)<\/code> devices and attach it to the right bridge. e.g., &quot;vlan2&quot; becomes &quot;vlan2bridge&quot; in the jib script.<\/p>\n<p>The jib script is old and has some features that I don't believe are necessary anymore. One feature tries to ensure your MAC addreses are stable, but this seems to be a native kernel feature of <code>epair(4)<\/code> now:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$<span class=\"w\"> <\/span>sysctl<span class=\"w\"> <\/span>-d<span class=\"w\"> <\/span>net.link.epair.ether_gen_addr\nnet.link.epair.ether_gen_addr:<span class=\"w\"> <\/span>Generate<span class=\"w\"> <\/span>MAC<span class=\"w\"> <\/span>with<span class=\"w\"> <\/span>FreeBSD<span class=\"w\"> <\/span>OUI<span class=\"w\"> <\/span>using<span class=\"w\"> <\/span>ether_gen_addr<span class=\"o\">(<\/span><span class=\"m\">9<\/span><span class=\"o\">)<\/span>\n<\/pre><\/div>\n<p>And <a class=\"reference external\" href=\"https:\/\/man.freebsd.org\/cgi\/man.cgi?ether_gen_addr(9)\">ether_gen_addr(9)<\/a> says:<\/p>\n<pre class=\"literal-block\">\nBy default, ether_gen_addr attempts to generate a stable MAC address\nusing the hostid of the jail that the ifp is being added to. During\nearly boot, the hostid may not be set on machines that haven't yet pop-\nulated \/etc\/hostid, or on machines that do not use loader(8).\n<\/pre>\n<p>Great, we seem to be covered there. But I still need a way to make stable <code>epair<\/code> device names for the jails and attach them to the bridge the new way, so I wrote a script for that:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"ch\">#!\/bin\/sh<\/span>\n<span class=\"c1\"># \/scripts\/vnetif<\/span>\n\n<span class=\"nb\">set<\/span><span class=\"w\"> <\/span>-eu\n\n<span class=\"nv\">ENAME<\/span><span class=\"o\">=<\/span><span class=\"nv\">$1<\/span>\n<span class=\"nv\">BRIDGE<\/span><span class=\"o\">=<\/span><span class=\"nv\">$2<\/span>\n<span class=\"nv\">VLAN<\/span><span class=\"o\">=<\/span><span class=\"nv\">$3<\/span>\n\n<span class=\"nv\">NEW_EPAIR<\/span><span class=\"o\">=<\/span><span class=\"k\">$(<\/span>ifconfig<span class=\"w\"> <\/span>-D<span class=\"w\"> <\/span>epair<span class=\"w\"> <\/span>create<span class=\"w\"> <\/span>-vlanhwfilter<span class=\"w\"> <\/span>up<span class=\"k\">)<\/span>\n<span class=\"nv\">EPAIR_NUM<\/span><span class=\"o\">=<\/span><span class=\"k\">$(<\/span><span class=\"nb\">echo<\/span><span class=\"w\"> <\/span><span class=\"si\">${<\/span><span class=\"nv\">NEW_EPAIR<\/span><span class=\"p\">##epair<\/span><span class=\"si\">}<\/span><span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>tr<span class=\"w\"> <\/span>-d<span class=\"w\"> <\/span><span class=\"s1\">&#39;[a]&#39;<\/span><span class=\"k\">)<\/span>\n\n<span class=\"c1\"># Do not even want ipv6 link local on here, layer3 not allowed<\/span>\n<span class=\"c1\"># anymore on bridge members<\/span>\nifconfig<span class=\"w\"> <\/span><span class=\"si\">${<\/span><span class=\"nv\">NEW_EPAIR<\/span><span class=\"si\">}<\/span><span class=\"w\"> <\/span>inet6<span class=\"w\"> <\/span>ifdisabled<span class=\"w\"> <\/span>-auto_linklocal<span class=\"w\"> <\/span>-accept_rtadv<span class=\"w\"> <\/span>no_radr\n\nifconfig<span class=\"w\"> <\/span>epair<span class=\"si\">${<\/span><span class=\"nv\">EPAIR_NUM<\/span><span class=\"si\">}<\/span>a<span class=\"w\"> <\/span>name<span class=\"w\"> <\/span>e0a_<span class=\"si\">${<\/span><span class=\"nv\">ENAME<\/span><span class=\"si\">}<\/span>\nifconfig<span class=\"w\"> <\/span>epair<span class=\"si\">${<\/span><span class=\"nv\">EPAIR_NUM<\/span><span class=\"si\">}<\/span>b<span class=\"w\"> <\/span>name<span class=\"w\"> <\/span>e0b_<span class=\"si\">${<\/span><span class=\"nv\">ENAME<\/span><span class=\"si\">}<\/span>\nifconfig<span class=\"w\"> <\/span><span class=\"si\">${<\/span><span class=\"nv\">BRIDGE<\/span><span class=\"si\">}<\/span><span class=\"w\"> <\/span>addm<span class=\"w\"> <\/span>e0a_<span class=\"si\">${<\/span><span class=\"nv\">ENAME<\/span><span class=\"si\">}<\/span><span class=\"w\"> <\/span>untagged<span class=\"w\"> <\/span><span class=\"si\">${<\/span><span class=\"nv\">VLAN<\/span><span class=\"si\">}<\/span>\n<\/pre><\/div>\n<p>This is not well designed but it gets the job done.<\/p>\n<blockquote>\nNote, on the <code>epair<\/code> device I had to set <code>-vlanhwfilter<\/code> -- another hidden requirement!<\/blockquote>\n<p>Now that this is sorted, my VNET jail config looks like this:<\/p>\n<pre class=\"literal-block\">\nwebserver {\n    exec.prestart += &quot;\/scripts\/vnetif $name bridge0 2&quot;;\n    exec.poststop += &quot;ifconfig e0a_$name destroy&quot;;\n    vnet;\n    vnet.interface = &quot;e0b_$name&quot;;\n}\n<\/pre>\n<p>It works! The VNET interface gets added to the bridge with the correct VLAN membership. It even seems to start the jail faster now...<\/p>\n<\/div>\n<div class=\"section\" id=\"bhyve-vms\">\n<a class=\"toclink\" href=\"#bhyve-vms\" title=\"Permalink to this section\"><h2>Bhyve VMs<\/h2>\n<\/a><p>The Bhyve VM solution is not finalized for me right now. I'm using the <a class=\"reference external\" href=\"https:\/\/github.com\/churchers\/vm-bhyve\/\">vm-bhyve<\/a> tooling to manage my VMs, but it has no support for this new bridging+VLAN functionality. I've resorted to defining a manually managed switch in the configuration and pre-creating <code>tap<\/code> interfaces for each VM. Technically I could use <code>cloned_interfaces<\/code> in <code>\/etc\/rc.conf<\/code> and add them all to the bridge when the bridge is created, but as the number of VMs grow the configuration for this is getting really long and messy. I've opted to sidestep it for the moment and create all the <code>tap<\/code> interfaces in <code>\/etc\/rc.local<\/code>, and then the VM config looks like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nv\">loader<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;grub&quot;<\/span>\n<span class=\"nv\">cpu<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;2&quot;<\/span>\n<span class=\"nv\">memory<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;4G&quot;<\/span>\n<span class=\"nv\">network0_type<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;virtio-net&quot;<\/span>\n<span class=\"nv\">network0_switch<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;bridge0&quot;<\/span><span class=\"w\"> <\/span><span class=\"c1\"># the predefined manual bridge config in \/vm-bhyve\/.config\/system.conf<\/span>\n<span class=\"nv\">network0_device<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;tap133&quot;<\/span><span class=\"w\"> <\/span><span class=\"c1\"># my precreated tap interface<\/span>\n<span class=\"nv\">disk0_type<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;nvme&quot;<\/span>\n<span class=\"nv\">disk0_name<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;disk0.img&quot;<\/span>\n<span class=\"nv\">uuid<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;2109c019-9fd0-11f0-9ab1-d05099db8057&quot;<\/span>\n<span class=\"nv\">network0_mac<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;58:9c:fc:0f:74:60&quot;<\/span><span class=\"w\"> <\/span><span class=\"c1\"># seems to be ineffective now<\/span>\n<span class=\"nv\">disk1_type<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;ahci-cd&quot;<\/span>\n<span class=\"nv\">disk1_name<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;seed.iso&quot;<\/span>\n<span class=\"nv\">disk1_dev<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;file&quot;<\/span>\n<\/pre><\/div>\n<p>So if you want a stable MAC address, you need to set it where you create the <code>tap<\/code> interface.<\/p>\n<p>For the record, that <code>\/vm-bhyve\/.config\/system.conf<\/code> looks like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nv\">switch_list<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;bridge0&quot;<\/span>\n<span class=\"nv\">type_bridge0<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;manual&quot;<\/span>\n<span class=\"nv\">bridge_bridge0<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;bridge0&quot;<\/span>\n<\/pre><\/div>\n<p>and my <code>\/etc\/rc.conf<\/code> has entries like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\"># Linuxdev VM<\/span>\n<span class=\"nv\">cloned_interfaces<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;<\/span><span class=\"si\">${<\/span><span class=\"nv\">cloned_interfaces<\/span><span class=\"si\">}<\/span><span class=\"s2\"> tap133&quot;<\/span>\n<span class=\"nv\">ifconfig_tap133<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;up&quot;<\/span>\n<span class=\"nv\">bridge_members<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;<\/span><span class=\"si\">${<\/span><span class=\"nv\">bridge_members<\/span><span class=\"si\">}<\/span><span class=\"s2\"> addm tap133 untagged 2&quot;<\/span>\n\n<span class=\"c1\"># ... later<\/span>\n<span class=\"nv\">cloned_interfaces<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;<\/span><span class=\"si\">${<\/span><span class=\"nv\">cloned_interfaces<\/span><span class=\"si\">}<\/span><span class=\"s2\"> bridge0&quot;<\/span>\n<span class=\"nv\">ifconfig_bridge0<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;vlanfilter <\/span><span class=\"si\">${<\/span><span class=\"nv\">bridge_members<\/span><span class=\"si\">}<\/span><span class=\"s2\">&quot;<\/span>\n<\/pre><\/div>\n<p>This seems to be the cleanest way to handle it -- just keep building\/accumulating <code>cloned_interfaces<\/code> and <code>bridge_members<\/code> variables and then it's easy to comment out\/delete exactly what you want.<\/p>\n<\/div>\n<div class=\"section\" id=\"classic-jails\">\n<a class=\"toclink\" href=\"#classic-jails\" title=\"Permalink to this section\"><h2>Classic Jails<\/h2>\n<\/a><p>Classic jails share the host network stack with the jail, but what if the interface you want to use is attached to a bridge? You can create a VLAN sub-interface off the bridge and use that.<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\"># \/etc\/rc.conf<\/span>\n<span class=\"c1\"># For Classic jails to use the 10gbit LAN interface<\/span>\n<span class=\"c1\"># we create a vlan sub-interface of the bridge<\/span>\n<span class=\"c1\"># which we can dynamically add IP addresses to<\/span>\n<span class=\"nv\">cloned_interfaces<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;bridge0&quot;<\/span>\n<span class=\"nv\">vlans_bridge0<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;3&quot;<\/span>\n<span class=\"nv\">ifconfig_bridge0<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;vlanfilter&quot;<\/span>\n<span class=\"nv\">ifconfig_bridge0_3<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;inet x.x.x.x\/yy&quot;<\/span><span class=\"w\"> <\/span><span class=\"c1\"># if you want an IP added at boot<\/span>\n<\/pre><\/div>\n<p>And a corresponding jail config:<\/p>\n<pre class=\"literal-block\">\n# jail.conf\nhomeweb {\n    host.hostname = &quot;homeweb&quot;;\n    ip4.addr = &quot;bridge0.3|10.27.3.202\/24&quot;;\n}\n<\/pre>\n<p>Now it will apply the new IP address alias to the bridge's VLAN sub-interface.<\/p>\n<\/div>\n<div class=\"section\" id=\"conclusion\">\n<a class=\"toclink\" href=\"#conclusion\" title=\"Permalink to this section\"><h2>Conclusion<\/h2>\n<\/a><p>We've covered how to handle this new bridge design when working with VNET jails, Classic jails, and some Bhyve VMs. Hopefully this should be able to get you up and running with the new bridge+VLAN design in FreeBSD 15.0-RELEASE. I think in the long run the elegance of bridges working like a real hardware switch will outweigh the inconveniences.<\/p>\n<\/div>\n","category":[{"@attributes":{"term":"tech"}},{"@attributes":{"term":"freebsd"}},{"@attributes":{"term":"networking"}},{"@attributes":{"term":"jails"}},{"@attributes":{"term":"bhyve"}},{"@attributes":{"term":"vlan"}}]},{"title":"Using nsnotifyd with a PowerDNS Secondary","link":{"@attributes":{"href":"https:\/\/blog.feld.me\/posts\/2026\/02\/nsnotifyd-with-powerdns-secondary\/","rel":"alternate"}},"published":"2026-02-18T16:16:52-08:00","updated":"2026-02-20T11:55:28-08:00","author":{"name":"feld"},"id":"tag:blog.feld.me,2026-02-18:\/posts\/2026\/02\/nsnotifyd-with-powerdns-secondary\/","summary":"<p>The <a class=\"reference external\" href=\"https:\/\/dotat.at\/prog\/nsnotifyd\/\">nsnotifyd<\/a> tool is a neat little utility that can be used for automating tasks when a DNS zone changes. It listens for a NOTIFY message from a DNS server and then you can choose to use a custom script to do things like send alerts or backup the zone \u2026<\/p>","content":"<p>The <a class=\"reference external\" href=\"https:\/\/dotat.at\/prog\/nsnotifyd\/\">nsnotifyd<\/a> tool is a neat little utility that can be used for automating tasks when a DNS zone changes. It listens for a NOTIFY message from a DNS server and then you can choose to use a custom script to do things like send alerts or backup the zone for your domain. I use it to automate issuing an AXFR to get a dump of a zone and then commit it to a git repo so I have a reliable history of all DNS changes that have happened to my zones.<\/p>\n<p>My current architecture is that I have my domains and their NS managed by the registrar, but I added a secondary (slave) DNS server running PowerDNS which will essentially accept a NOTIFY for any domain it can verify it's listed as an NS for and then AXFR (or maybe an IXFR -- incremental) to sync itself with the latest changes. This elminates the need for me to have a list of hardcoded domains that I might forget about or screw up. In the PowerDNS world this is called an <code>autosecondary<\/code>, but it used to be called a superslave (and the master, a supermaster). My memory of this from about 15 years ago is that this functionality really only worked if the master was also a PowerDNS server, but I might be wrong about that. Anyway, these days you can run an <code>autosecondary<\/code> which handles normal NOTIFY\/AXFR and does the right thing.<\/p>\n<p>Now to tie nsnotifyd into this, I run it on localhost:5353. The biggest hurdle I had was getting the PowerDNS in secondary mode to be able to forward a NOTIFY to nsnotifyd. The configuration for this wasn't very clear but I was able to piece it together and I'll quickly explain the configuration. The key configuration parameters you need are as follows:<\/p>\n<pre class=\"literal-block\">\nallow-axfr-ips=127.0.0.1\nsecondary-do-renotify=yes\nonly-notify=\nalso-notify=127.0.0.1:5353\n<\/pre>\n<ul class=\"simple\">\n<li><code>allow-axfr-ips<\/code> will allow my localhost nsnotifyd to AXFR zones<\/li>\n<li><code>secondary-do-renotify<\/code> causes the PowerDNS secondary to send an additional NOTIFY to all configured NSes for the zone<\/li>\n<li><code>only-notify<\/code> is blank, which tells it to not actually notify any of the configured NSes for the zone<\/li>\n<li><code>also-notify<\/code> specifies the nsnotifyd service's address:port, which makes it the only target of a NOTIFY from PowerDNS as this setting supercedes the only-notify<\/li>\n<\/ul>\n<p>That's the trick to making it work. It wasn't obvious this was the solution and I was distracted by a <a class=\"reference external\" href=\"https:\/\/doc.powerdns.com\/authoritative\/settings.html#forward-notify\">forward-notify<\/a> setting that exists and by its own description appears to be exactly what I wanted, but it had no effect. The problem may be due to the fact that the config description says it wants &quot;IP addresses, separated by commas&quot; which means I can't specify the port 5353, but the <a class=\"reference external\" href=\"https:\/\/doc.powerdns.com\/authoritative\/settings.html#also-notify\">also-notify<\/a> setting does allow you to specify a port. Weird.<\/p>\n<p>Maybe this will save you some time if you're in a similar situation.<\/p>\n","category":[{"@attributes":{"term":"tech"}},{"@attributes":{"term":"unix"}},{"@attributes":{"term":"dns"}},{"@attributes":{"term":"powerdns"}},{"@attributes":{"term":"axfr"}}]},{"title":"WolfSSL Doesn't Suck","link":{"@attributes":{"href":"https:\/\/blog.feld.me\/posts\/2026\/02\/wolfssl-doesnt-suck\/","rel":"alternate"}},"published":"2026-02-16T14:00:05-08:00","updated":"2026-02-16T14:40:45-08:00","author":{"name":"feld"},"id":"tag:blog.feld.me,2026-02-16:\/posts\/2026\/02\/wolfssl-doesnt-suck\/","summary":"<div class=\"section\" id=\"you-re-just-holding-it-wrong\">\n<a class=\"toclink\" href=\"#you-re-just-holding-it-wrong\" title=\"Permalink to this section\"><h2>You're Just Holding It Wrong<\/h2>\n<\/a><p>My previous <a class=\"reference external\" href=\"\/posts\/2026\/02\/wolfssl-sucks-too\/\">post<\/a> made some rounds on the internet. However, there has been a happy ending to this saga. I've revisited my test environment as their requirement of building the library with <strong>WOLFSSL_TLS13_MIDDLEBOX_COMPAT<\/strong> and still not seeing any changes in behavior was bothering me. I \u2026<\/p><\/div>","content":"<div class=\"section\" id=\"you-re-just-holding-it-wrong\">\n<a class=\"toclink\" href=\"#you-re-just-holding-it-wrong\" title=\"Permalink to this section\"><h2>You're Just Holding It Wrong<\/h2>\n<\/a><p>My previous <a class=\"reference external\" href=\"\/posts\/2026\/02\/wolfssl-sucks-too\/\">post<\/a> made some rounds on the internet. However, there has been a happy ending to this saga. I've revisited my test environment as their requirement of building the library with <strong>WOLFSSL_TLS13_MIDDLEBOX_COMPAT<\/strong> and still not seeing any changes in behavior was bothering me. I took a fresh stab at this again and discovered a problem that was causing the correctly built WolfSSL package to not be the one which was installed. It is a little annoying that there's no way to easily verify the build flags etc of the WolfSSL library (no helper program\/binary which displays this in some useful --help or other flag), but I fully take the blame for screwing that one up.<\/p>\n<center><img alt=\"Me IRL\" src=\"\/static\/wolfssl-doesnt-suck\/shame.gif\" \/>\n<\/center><p>I've had some more back-and-forth with a WolfSSL dev and they've chased this problem further. They're adding an <a class=\"reference external\" href=\"https:\/\/github.com\/wolfSSL\/wolfssl\/pull\/9769\/changes\/c3c9acc5bf671b71bae9cf21a34d9c269323da6f\">extra check<\/a> for compliance as well as some tests. This is good.<\/p>\n<\/div>\n<div class=\"section\" id=\"but\">\n<a class=\"toclink\" href=\"#but\" title=\"Permalink to this section\"><h2>But?<\/h2>\n<\/a><p>What's not so good is that this feature is still gated behind defining a CFLAG that most people probably won't even know exists. They already have <strong>--enable-tls13<\/strong> and <strong>--enable-tls13-draft18<\/strong> configure args, so perhaps I can convince them to document this more thoroughly or even add it as another arg.<\/p>\n<\/div>\n<div class=\"section\" id=\"fresh-verdict\">\n<a class=\"toclink\" href=\"#fresh-verdict\" title=\"Permalink to this section\"><h2>Fresh Verdict<\/h2>\n<\/a><p>WolfSSL is extremely configurable. Maybe even too configurable for its own good. That is probably the nature of things when you are also targeting very tiny embedded environments and want them to have a modern crypto library. I've already run into other things before, like needing <strong>WOLFSSL_GETRANDOM<\/strong> so it works in a chroot where there is no \/dev. WolfSSL might very well be the Lego of TLS libraries, and clearly I'm just not old enough (or smart enough) to play with a set that has this many pieces.<\/p>\n<p>Perhaps WolfSSL really is the OpenSSL replacement we need. Just be aware that it's not as &quot;drop-in&quot; of a replacement as you'd think. You still need to know how to hold it right.<\/p>\n<p>Steve would be proud.<\/p>\n<center><img alt=\"The Man, The Myth, The Legend\" src=\"\/static\/wolfssl-doesnt-suck\/steve-holding-it-wrong.jpg\" \/>\n<\/center><\/div>\n","category":[{"@attributes":{"term":"tech"}},{"@attributes":{"term":"unix"}},{"@attributes":{"term":"linux"}},{"@attributes":{"term":"ssl"}},{"@attributes":{"term":"tls"}},{"@attributes":{"term":"openssl"}},{"@attributes":{"term":"wolfssl"}},{"@attributes":{"term":"gnutls"}},{"@attributes":{"term":"libressl"}},{"@attributes":{"term":"elixir"}},{"@attributes":{"term":"erlang"}}]},{"title":"WolfSSL Sucks Too, So Now What?","link":{"@attributes":{"href":"https:\/\/blog.feld.me\/posts\/2026\/02\/wolfssl-sucks-too\/","rel":"alternate"}},"published":"2026-02-12T18:15:15-08:00","updated":"2026-02-16T14:41:37-08:00","author":{"name":"feld"},"id":"tag:blog.feld.me,2026-02-12:\/posts\/2026\/02\/wolfssl-sucks-too\/","summary":"<p>OpenSSL sucks. The BoringSSL and AWS-LC forks are Googled and Amazoned to death; they don't care about anyone but their own use cases. I can't remember ever having a good experience with software using GnuTLS. LibreSSL is incomplete...<\/p>\n<div class=\"section\" id=\"foreward\">\n<a class=\"toclink\" href=\"#foreward\" title=\"Permalink to this section\"><h2>FOREWARD<\/h2>\n<\/a><p>This post is about the <em>experience<\/em> of taking a leap of \u2026<\/p><\/div>","content":"<p>OpenSSL sucks. The BoringSSL and AWS-LC forks are Googled and Amazoned to death; they don't care about anyone but their own use cases. I can't remember ever having a good experience with software using GnuTLS. LibreSSL is incomplete...<\/p>\n<div class=\"section\" id=\"foreward\">\n<a class=\"toclink\" href=\"#foreward\" title=\"Permalink to this section\"><h2>FOREWARD<\/h2>\n<\/a><p>This post is about the <em>experience<\/em> of taking a leap of faith and using WolfSSL as a drop-in replacement for an existing Haproxy server which traditionally uses OpenSSL. The WolfSSL project specifically has an OpenSSL API compatibility layer so you can presumably swap out OpenSSL almost anywhere. I encountered some unexplainable errors with it in my initial testing, reported a bug, forgot about it, and moved on. Later I revisited my infra and swapped Haproxy+WolfSSL in, hit the bug again, recognized the bug, remembered I did indeed report this already, and finally followed up on it.<\/p>\n<p>Anthony was responding on the Github issue in near real-time which is pretty interesting as I've never had that happen before. It was more clumsy than being in a proper chat, but if that's how he responds on Github I bet their support contracts have a pretty incredible SLA response time.<\/p>\n<p>The goal of this post was to document a bizarre debugging journey for a problem I could not reproduce with anything except one language (Erlang\/OTP via Elixir) and also to make it clear to Elixir\/Erlang devs searching the web in the future how to identify the root cause of this error. In the process I learned that TLS 1.3 has this feature I didn't realize even existed, WolfSSL 5.8.4 doesn't handle it properly, and a workaround.<\/p>\n<p>The <em>experience<\/em> of swapping WolfSSL in and running into unexplainable errors is what sucked. The inability to successfully communicate the urgency and importance of fixing this sucked. I don't have any interest in arguing with people on Github especially when I'm not a TLS expert (why listen to me?), so I walked away.<\/p>\n<p>Also, orange site commenters need to get a life. They have amplified this post to point that it's really dragging WolfSSL itself through the mud.<\/p>\n<p>It sounds like WolfSSL is going to get this sorted out pretty quick which is great to hear. Swapping out a TLS library for a server is a pretty risky change, so it's no wonder we nearly have a monoculture in this area. I dream of a world where we have several full-featured production-ready options other than OpenSSL or its forks. Perhaps we're almost there and this is one of the last roadbumps?<\/p>\n<\/div>\n<div class=\"section\" id=\"what-happened-now\">\n<a class=\"toclink\" href=\"#what-happened-now\" title=\"Permalink to this section\"><h2>What happened now?<\/h2>\n<\/a><p>Last year an <a class=\"reference external\" href=\"https:\/\/www.haproxy.com\/blog\/state-of-ssl-stacks\">article<\/a> from Haproxy about how terribly slow OpenSSL has become was published. This made the rounds a few times, and I had an itch to scratch so I helped enable FreeBSD to package a variant of Haproxy built against WolfSSL. This seemed like an easy way to get people wider exposure to WolfSSL as it's unlikely we'll see this happen on most Linux distros, so in reality the only people who are experiencing a WolfSSL-backed Haproxy are people who know what they're getting into and custom built it themselves. I haven't actually checked if Arch, Gentoo, Nix, etc have done similar, but they'd be the easiest to produce a similar <strong>haproxy-wolfssl<\/strong> package.<\/p>\n<p>So I did it, and I ran WolfSSL in a few places, and then I hit a bug. I reported the bug, forgot about it, and moved on. And then I hit it again and was motivated to actually figure it out. So I reopened the <a class=\"reference external\" href=\"https:\/\/github.com\/wolfSSL\/wolfssl\/issues\/9156\">bug<\/a> and fumbled my way through debugging the issue until the root cause was identified.<\/p>\n<p>TLS 1.3 is defined in <a class=\"reference external\" href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc8446\">RFC 8446<\/a>. It works quite a bit different from TLS 1.2 which caused them no end of issues, such that they documented &quot;The design of TLS 1.3 was constrained by widely deployed non-compliant TLS middleboxes&quot;.<\/p>\n<p>Ahh yes, the infamous middleboxes. Great. Those invisible pieces of garbage that can tamper with your traffic and you'll generally never know they exist until they cause you grief. And they will.<\/p>\n<\/div>\n<div class=\"section\" id=\"middlebox-hell\">\n<a class=\"toclink\" href=\"#middlebox-hell\" title=\"Permalink to this section\"><h2>Middlebox Hell<\/h2>\n<\/a><p>Hell is definitely a place where middleboxes were invented and no amount of wishcasting will remove them from existence. Although maybe some Etsy witches could provide some guidance as they have incredible luck solving problems...<\/p>\n<p>Anyway, so we have all these middleboxes and they suck and we want the network to have better security guarantees than TLS 1.2 but the boxes only understand TLS 1.2 and TLS 1.3 can't exist if the boxes break them so TLS 1.3 has to be able to pretend to be TLS 1.2. That's where we're at with this.<\/p>\n<p>So the authors hemmed and hawwed about this and came up with a solution: <a class=\"reference external\" href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc8446#appendix-D.4\">Middlebox Compatibility Mode<\/a>.<\/p>\n<p>Essentially, clients can optionally set a non-empty session ID in the ClientHello to fool the middleboxes, and the client and server exchange dummy change_cipher_spec records. This is useless and just adds latency to establishing the TLS session, but it will work. Fair.<\/p>\n<\/div>\n<div class=\"section\" id=\"the-upside-down\">\n<a class=\"toclink\" href=\"#the-upside-down\" title=\"Permalink to this section\"><h2>The Upside Down<\/h2>\n<\/a><p>The RFC is pretty clear about how this is all meant to play out.<\/p>\n<blockquote>\nThis &quot;compatibility mode&quot; is partially negotiated: the client\ncan opt to provide a session ID or not, and the server has to echo it.<\/blockquote>\n<p>and<\/p>\n<blockquote>\nif the client sends a non-empty session ID,\nthe server MUST send the change_cipher_spec as described in this\nappendix.<\/blockquote>\n<p>But WolfSSL says &quot;thanks but no thanks&quot;. The entire middlebox compatibility functionality is gated behind compiling the library with <strong>WOLFSSL_TLS13_MIDDLEBOX_COMPAT<\/strong>. [Update: fresh rounding of testing, this feature DOES work when enabled. Fault was discovered in my test environment.]<\/p>\n<p>So the current state is that WolfSSL cannot be trusted to work correctly with TLS 1.3 clients unless the library was specifically built with this flag enabled. The normal TLS 1.3 configure flags aren't enough. So by default, interoperability depends on how forgiving the clients are, but you shouldn't be forgiving when implementing the RFC correctly... The GitHub issue comment left at the end leads me to believe that they aren't really interested in RFC compliance. There isn't a middleground here or a &quot;different way&quot; of implementing middlebox compatibility. It's either RFC compliant or not. And they're not. Asking me to open a new issue to discuss this behavior instead of it being a high priority for them to open up a new issue internally to fix this is odd. I'm not here to do their homework for them.<\/p>\n<blockquote>\nCorrection: previous edit mentioned WolfSSL is owned by ARM here, but I've mentally swapped PolarSSL for WolfSSL. Whoops.<\/blockquote>\n<p>This sucks extra hard because WolfSSL is also used in a lot of embedded devices. The right thing to do from a security PoV should be to always enable Middlebox Compatibility Mode on your TLS 1.3 clients to increase the chances that your TLS 1.3 handshake will be successfully established. But now we can't do that. Should we wait a few years for WolfSSL to have releases out there with this fixed? Well, since it's often used in embedded devices too you might want to wait a decade before you can use this feature blindly if you don't control both the client and server deployments. Big yikes.<\/p>\n<\/div>\n<div class=\"section\" id=\"the-plaintiff\">\n<a class=\"toclink\" href=\"#the-plaintiff\" title=\"Permalink to this section\"><h2>The Plaintiff<\/h2>\n<\/a><p>Currently I've only identified one victim of this decision, but there's bound to be more out there. Erlang\/OTP has its own ssl library implementation and you can rightfully assume that they've taken Joe's advice to heart when adding TLS 1.3 support:<\/p>\n<blockquote>\nMake it work, then make it beautiful, then if you really, really have to, make it fast. - Joe Armstrong<\/blockquote>\n<p>So to cover their butts, they opted to enable <a class=\"reference external\" href=\"https:\/\/github.com\/erlang\/otp\/blob\/7f599b97ce2dbdc672866bd709b12b9789c86cc8\/lib\/ssl\/src\/ssl_session.erl#L248\">middlebox_comp_mode<\/a> by default. (If you want it fast and you know it's safe to do so -- turn it off)<\/p>\n<p>And now every Elixir\/Erlang\/etc HTTP client fails to be able to connect to a WolfSSL HTTPS server if TLS 1.3 is available.<\/p>\n<\/div>\n<div class=\"section\" id=\"where-do-we-go-from-here\">\n<a class=\"toclink\" href=\"#where-do-we-go-from-here\" title=\"Permalink to this section\"><h2>Where Do We Go From Here?<\/h2>\n<\/a><p>OpenBSD was probably right. We just need to get people to focus on LibreSSL and forget about these other libraries. As Haproxy noted, it's not a victim of the OpenSSL 3.0 screwups because they forked earlier, but it's missing some optimizations. I think that's probably a fair trade-off and the gaps will be filled in due time.<\/p>\n<p>So don't be like me. This was hubris I guess. Sure, I thought I could be clever and have faster TLS termination for my websites, but all it did was lead me to wasting a lot of time learning about something I really didn't care to know, and then writing this stupid blog post. You've been warned.<\/p>\n<div class=\"section\" id=\"elixir-poc\">\n<a class=\"toclink\" href=\"#elixir-poc\" title=\"Permalink to this section\"><h3>Elixir PoC<\/h3>\n<\/a><p>A PoC for Elixir 1.17.3 (compiled with Erlang\/OTP 26) is as simple as below:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\">#!\/usr\/bin\/env elixir<\/span>\n\n<span class=\"n\">url<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;https:\/\/some-wolfssl-endpoint&quot;<\/span>\n\n<span class=\"n\">url<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"nc\">String<\/span><span class=\"o\">.<\/span><span class=\"n\">to_charlist<\/span><span class=\"p\">(<\/span><span class=\"n\">url<\/span><span class=\"p\">)<\/span>\n\n<span class=\"p\">{<\/span><span class=\"ss\">:ok<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"bp\">_<\/span><span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"nc\">Application<\/span><span class=\"o\">.<\/span><span class=\"n\">ensure_all_started<\/span><span class=\"p\">(<\/span><span class=\"ss\">:inets<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">{<\/span><span class=\"ss\">:ok<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"bp\">_<\/span><span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"nc\">Application<\/span><span class=\"o\">.<\/span><span class=\"n\">ensure_all_started<\/span><span class=\"p\">(<\/span><span class=\"ss\">:ssl<\/span><span class=\"p\">)<\/span>\n\n<span class=\"ss\">:logger<\/span><span class=\"o\">.<\/span><span class=\"n\">set_application_level<\/span><span class=\"p\">(<\/span><span class=\"ss\">:ssl<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"ss\">:debug<\/span><span class=\"p\">)<\/span>\n\n<span class=\"n\">http_options<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">[<\/span>\n<span class=\"w\">    <\/span><span class=\"ss\">ssl<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span>\n<span class=\"w\">      <\/span><span class=\"ss\">verify<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"ss\">:verify_peer<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">      <\/span><span class=\"ss\">cacerts<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"ss\">:public_key<\/span><span class=\"o\">.<\/span><span class=\"n\">cacerts_get<\/span><span class=\"p\">(),<\/span>\n<span class=\"w\">      <\/span><span class=\"ss\">depth<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"mi\">2<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">      <\/span><span class=\"ss\">customize_hostname_check<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span>\n<span class=\"w\">        <\/span><span class=\"ss\">match_fun<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"ss\">:public_key<\/span><span class=\"o\">.<\/span><span class=\"n\">pkix_verify_hostname_match_fun<\/span><span class=\"p\">(<\/span><span class=\"ss\">:https<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">      <\/span><span class=\"p\">],<\/span>\n<span class=\"w\">      <\/span><span class=\"ss\">versions<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"ss\">:&quot;tlsv1.2&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"ss\">:&quot;tlsv1.3&quot;<\/span><span class=\"p\">],<\/span>\n<span class=\"w\">      <\/span><span class=\"ss\">middlebox_comp_mode<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"no\">true<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">]<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">]<\/span>\n\n<span class=\"n\">options<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"ss\">body_format<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"ss\">:binary<\/span><span class=\"p\">]<\/span>\n\n<span class=\"ss\">:httpc<\/span><span class=\"o\">.<\/span><span class=\"n\">request<\/span><span class=\"p\">(<\/span><span class=\"ss\">:get<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"n\">url<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"p\">[]},<\/span><span class=\"w\"> <\/span><span class=\"n\">http_options<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">options<\/span><span class=\"p\">)<\/span>\n<\/pre><\/div>\n<p>The error is going to look like this:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"mi\">11<\/span><span class=\"p\">:<\/span><span class=\"mi\">00<\/span><span class=\"p\">:<\/span><span class=\"mf\">44.996<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">warning<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"ss\">Description<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"sx\">~c&quot;Failed to assert middlebox server message&quot;<\/span>\n<span class=\"w\">     <\/span><span class=\"ss\">Reason<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"ss\">missing<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"ss\">:change_cipher_spec<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"mi\">1<\/span><span class=\"p\">}]<\/span>\n\n<span class=\"mi\">11<\/span><span class=\"p\">:<\/span><span class=\"mi\">00<\/span><span class=\"p\">:<\/span><span class=\"mf\">45.014<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">notice<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"nc\">TLS<\/span><span class=\"w\"> <\/span><span class=\"ss\">:client<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">In<\/span><span class=\"w\"> <\/span><span class=\"n\">state<\/span><span class=\"w\"> <\/span><span class=\"ss\">:hello_middlebox_assert<\/span><span class=\"w\"> <\/span><span class=\"n\">at<\/span><span class=\"w\"> <\/span><span class=\"n\">ssl_gen_statem<\/span><span class=\"o\">.<\/span><span class=\"n\">erl<\/span><span class=\"p\">:<\/span><span class=\"mi\">821<\/span><span class=\"w\"> <\/span><span class=\"n\">generated<\/span><span class=\"w\"> <\/span><span class=\"nc\">CLIENT<\/span><span class=\"w\"> <\/span><span class=\"ss\">ALERT<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">Fatal<\/span><span class=\"w\"> <\/span><span class=\"o\">-<\/span><span class=\"w\"> <\/span><span class=\"nc\">Unexpected<\/span><span class=\"w\"> <\/span><span class=\"nc\">Message<\/span>\n<span class=\"w\"> <\/span><span class=\"o\">-<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"ss\">:unexpected_msg<\/span><span class=\"p\">,<\/span>\n<span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"ss\">:internal<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">{<\/span><span class=\"ss\">:encrypted_extensions<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">   <\/span><span class=\"p\">%{<\/span>\n<span class=\"w\">     <\/span><span class=\"ss\">elliptic_curves<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"ss\">:supported_groups<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">      <\/span><span class=\"p\">[<\/span><span class=\"ss\">:secp521r1<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"ss\">:secp384r1<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"ss\">:secp256r1<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"ss\">:x25519<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"ss\">:ffdhe2048<\/span><span class=\"p\">]}<\/span>\n<span class=\"w\">   <\/span><span class=\"p\">}}}}<\/span>\n<\/pre><\/div>\n<p>That's when you'll know you've been thrown to the wolves. \ud83e\udd2c If you change those <strong>http_options<\/strong> to set <strong>middlebox_comp_mode<\/strong> to <strong>false<\/strong> it will work as expected.<\/p>\n<\/div>\n<\/div>\n","category":[{"@attributes":{"term":"tech"}},{"@attributes":{"term":"unix"}},{"@attributes":{"term":"linux"}},{"@attributes":{"term":"ssl"}},{"@attributes":{"term":"tls"}},{"@attributes":{"term":"openssl"}},{"@attributes":{"term":"wolfssl"}},{"@attributes":{"term":"gnutls"}},{"@attributes":{"term":"libressl"}},{"@attributes":{"term":"elixir"}},{"@attributes":{"term":"erlang"}}]}]}