Skip to content

This is the multi-page printable view of this section. .

Return to the regular view of this page.

Blog

Posts, design notes, and release announcements.

Follow the project through articles, design decisions, and release notes.

1 - Posts

News, ideas, and practical project stories.

1.1 - Troubleshooting RouterOS DHCPv6 and NetBird's DNS upstream

A layered IPv6 + DNS debugging session — MikroTik prefix delegation and LAN RA, plus a NetBird-managed resolv.conf with no upstream resolver.

The goal was to give the LAN behind my home server a working IPv6 egress, and to fix curl -6 and public DNS resolution failing on the server itself.

It turned out to be two consecutive problems:

  1. RouterOS needed correct DHCPv6 client, Prefix Delegation, LAN RA and IPv6 firewall configuration.
  2. NetBird had taken over the server’s /etc/resolv.conf, but no upstream resolver was configured in NetBird Management, so public DNS lookups failed.

RouterOS DHCPv6 client

Initially the MikroTik only received an IPv6 delegated prefix:

prefix = 240e:36f:15c3:5cf1::/64

The WAN interface never got its own IPv6 address. The fix was to make the DHCPv6 client request both an address and a prefix:

/ipv6 dhcp-client set 0 request=address,prefix
/ipv6 dhcp-client release 0
/ipv6 dhcp-client renew 0

After that RouterOS held two kinds of IPv6:

WAN address:
  240e:36f:15c3:5cf0:.../128

LAN delegated prefix:
  240e:36f:15c3:5cf1::/64

So the ISP hands out one WAN address plus a prefix that can be delegated to the LAN.

Fixing the LAN bridge address

The LAN bridge had auto-generated a wrong address, 240e:36f:15c3:5cf1::/64, which showed up as duplicate / invalid. The fix was to remove the pool-generated address and generate the bridge gateway address manually:

/ipv6 address remove [find from-pool=isp-ipv6-pool]
/ipv6 address add address=::1/64 from-pool=isp-ipv6-pool interface=bridge advertise=yes

The bridge ended up correctly as 240e:36f:15c3:5cf1::1/64.

advertise=yes matters — it lets RouterOS send RAs on the LAN so hosts get public IPv6 via SLAAC.

IPv6 firewall

RouterOS already ships a defconf IPv6 firewall. During debugging I temporarily added a duplicate rule set, then removed it and kept the default baseline, making sure the LAN outbound accept sits before the forward drop:

/ipv6 firewall filter add chain=forward in-interface-list=LAN out-interface-list=WAN action=accept comment="allow LAN IPv6 outbound"

The resulting logic:

input:
  accept established/related
  accept ICMPv6
  accept DHCPv6 client
  drop non-LAN traffic to router

forward:
  accept established/related
  accept ICMPv6
  accept LAN -> WAN
  drop new inbound traffic from WAN

A reasonable home-edge IPv6 baseline: LAN can initiate outbound, WAN cannot initiate inbound.

Verifying IPv6 connectivity

At first 2406:4700:4700::1111 didn’t ping, but IPv6 wasn’t entirely broken — these all worked:

2606:4700:4700::1111  OK
2001:4860:4860::8888  OK
2400:3200::1          OK

And on the server, IPv6 egress was confirmed:

curl -6 --resolve ifconfig.co:443:2606:4700:20::681a:ccf https://ifconfig.co

It returned the host’s public IPv6. So: DHCPv6-PD works, LAN RA works, the host has a valid address, and forwarding works.

The DNS problem

With IPv6 egress fine, the plain command still failed:

curl -6 https://ifconfig.co

Not an IPv6 problem — a DNS one.

The server’s /etc/resolv.conf was taken over by NetBird:

# Generated by NetBird
search netbird.internal
nameserver 100.x.y.z

100.x.y.z is the server’s own NetBird IP — it was configured to query its own NetBird DNS proxy.

But NetBird Management had no upstream nameserver groups at all (/api/dns/nameservers returned []). So the chain was:

NetBird takes over resolv.conf
  -> host uses its own NetBird IP for DNS
  -> NetBird has no upstream resolver
  -> public name resolution fails

Fixing the NetBird upstream DNS

I created a primary nameserver group via the NetBird API:

name:     public-upstream-dns
enabled:  true
primary:  true
group:    All
domains:  .

With upstreams 1.1.1.1, 8.8.8.8 and 2606:4700:4700::1111. The NetBird API currently limits a nameserver group to 1-3 resolvers, so 2001:4860:4860::8888 didn’t make it in.

After the fix, netbird status -d showed:

[1.1.1.1:53, 8.8.8.8:53, [2606:4700:4700::1111]:53] for [.] is Available

Verification:

dig ifconfig.co @100.x.y.z
dig AAAA ifconfig.co @100.x.y.z
curl -4 https://ifconfig.co
curl -6 https://ifconfig.co

All passing, and the other mesh peers picked up the same upstreams.

Takeaways

It’s easy to misread this as “IPv6 is broken”, but there were two separate layers:

network layer:  RouterOS DHCPv6-PD + RA + firewall
resolver layer: NetBird DNS management + upstream resolver

Debug in layers:

  1. Use ping6 or curl --resolve to bypass DNS and prove IPv6 routing works.
  2. Use dig @specific-resolver to test each resolver.
  3. Only then test plain curl -6 https://name.

If /etc/resolv.conf is generated by NetBird, don’t stop at the local DNS config — check the DNS Nameserver Groups in NetBird Management too.

Quick rules of thumb:

  • dig @1.1.1.1 works but dig @100.x.x.x fails → NetBird DNS proxy / upstream problem
  • curl --resolve works but curl <name> fails → DNS problem, not routing
  • RouterOS pings IPv6 but LAN hosts can’t → check RA, bridge address, forward firewall first

Follow-ups

  • Keep the NetBird public-upstream-dns nameserver group.
  • Don’t point at a peer’s own NetBird IP as the sole DNS upstream.
  • For internal split DNS later, add a dedicated domains nameserver group instead of touching the primary public resolver.
  • Maintain the RouterOS IPv6 firewall on top of defconf; duplicate rules just scramble the ordering.

1.2 - Notes on building my own agent framework

From a Go coding-agent loop to a Python framework stitched together from bub, Tape System, Kapybara and jido.

After weeks of tinkering and research, this weekend I finally settled on the agent framework I wanted. It started as a Go-based, purely coding-focused agent loop, and ended up as a Python implementation that borrows from bub, Tape System, Kapybara and jido — my own agent framework stitched out of their ideas. The overall approach borrows from bub too.

Declarative design

A lot of it is declarative. Extensibility is arguably the most important property of a framework, and declarative design is what buys it. I took bub’s “everything is a hook” and, per Opus’s suggestion, jido’s state_key-based fused design to keep plugins from getting too fine-grained. Whether it works as intended is still unverified.

Layered memory

The memory part follows kapybara’s design (which is also built on tape): layered, with grounding & getter — the former leaning toward external knowledge bases, the latter toward internal context. I haven’t fully thought this part through myself yet.

Lessons from practice

I rediscovered “Context Engineering from the Inside Out” sitting in my tab bar, and it turned out to be more useful than expected — especially the Failure section. On the point of switching to a better model with higher reasoning effort: when I tested the coding agent loop, gemini 3 flash versus gpt 5.2 or even 5.4, the latter two didn’t show a clear advantage. That could be down to my task design or the code, though. I hit a few other pitfalls too, which I’ll skip here.

Some stray thoughts

In polebug’s latest WeChat article she wrote about over-indulging in vibe coding, and it resonated. Lately I’ve had little urge to record my life at all — my expressiveness feels dulled. I need to rebalance; once this framework is done, it’s probably time for a break.

Maybe next I should figure out what kind of work and life I actually want.

1.3 - 2025 year in review

A year of transitions — leaving school, letting go of a relationship, and looking for direction amid uncertainty.

Time flies — it’s the last day of the year again. A lot happened this year, and I’ve been recording more of it than before. This long-overdue year-end review barely made it onto the 2025 train.

The biggest change is identity: six months ago I stopped being a student, and since then I’ve been tasting the life of a tech worker, overtime included.

A chapter that closed

I spent the first half of the year in a long-distance relationship, grabbing every chance to meet. In the second half, for various reasons, we broke up. Maybe it was always going to end this way.

It took me a while to move on, but I’ve finally accepted it — time to close the door and walk forward.

That relationship is an inseparable part of me. What I can do now is keep the good parts, thank it for how it changed me, and use it to see myself more clearly as I look for a better version of me.

A future I’m still unsure about

Six months into work, minus training, I’ve barely done real work. Honestly, I’m still unsure what direction I should go in. So I’ve been easing into AI at my own pace — from agents to deep learning concepts to LLM inference infra — and joined a few offline community events (Wuhan Cursor meetup, Shanghai Google DevFest, Wuhan DDUC).

The confusion is real. I need to see more of the world: an AI coding hackathon at ModelScope in Hangzhou, a Rust meetup in Shanghai — to find my real goal and meet more people ahead of me. Stay proactive, keep learning, keep the passion.

Growth? Hopes for next year?

My information diet is my main channel and keeps improving.

Technically I can feel myself getting stronger, but I lack output — I want to find a direction I can write about, and a job I actually like.

Make more money! Not for the so-called grown-up checklist, but because money buys freedom. I’d like to learn personal finance, though neither my principal nor my spare time really qualifies.

As for people — it’s been a lonely stretch without someone close to talk to. Flying back to school over New Year’s to see friends sounds like a good idea; hopefully things go smoother after a change of scenery.

All in all, my biggest hope is finding a job I truly want — finding direction, less confusion, some like-minded friends. Romance can wait.

Thanks for the hard work, 2025-me. May next year treat me and everyone kindly. Hello, 2026.

Anime I watched

  • Death Note — Kami’s expressions are unforgettable
  • Panty & Stocking season 2 — crude, but genuinely good; TRIGGER might be my kind of studio
  • Girls Band Cry — dropped it; this genre doesn’t hold me anymore
  • Chainsaw Man: Reze Arc — beautiful and hard to let go of; maybe love stories just rarely end well

Games I played

  • Stardew Valley — timeless; solo doesn’t appeal, co-op with someone is lovely
  • MapleStory — fun early on; forever a nostalgic icon for my generation
  • Silksong — lost steam after the early game; my appetite for games is fading
  • Hollow Knight — same story; the map grind wore me down — no Dark Souls stamina left