Wednesday, October 21, 2026

Python3 script for checking IPv6 Mostly Networks

 Python script to check if your IPv6 Mostly Network is running ok


--- cut here validate_mostly_v6.py ---


#!/usr/bin/env python3
import subprocess
import platform
import re
import sys

# Terminal output colors
GREEN = '\033[0;32m'
RED = '\033[0;31m'
YELLOW = '\033[1;33m'
NC = '\033[0m' # No Color

print("==================================================")
print(" Starting IPv6-Mostly Network Validation Test (Py)")
print("==================================================")

# Detect Host Operating System
OS_TYPE = platform.system() # 'Darwin' for macOS, 'Linux' for Linux

def run_command(command):
    """Utility to run system commands and return stdout as string."""
    try:
        result = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        return result.stdout.strip()
    except Exception:
        return ""

def test_ping(command):
    """Utility to run ping tests and return Boolean success."""
    try:
        result = subprocess.run(command, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        return result.returncode == 0
    except Exception:
        return False


# --- TEST 1: Verify Global IPv6 Allocation ---
print("Test 1: Checking Global IPv6 Address... ", end="", flush=True)
if OS_TYPE == "Darwin":
    ifconfig_out = run_command("ifconfig en0 2>/dev/null")
    ipv6_addresses = re.findall(r'inet6\s+([a-fA-F0-9:]+)', ifconfig_out)
    # Filter out link-local (fe80) and loopback (::1)
    global_v6 = [ip for ip in ipv6_addresses if not ip.lower().startswith("fe80") and ip != "::1"]
else:
    ip_out = run_command("ip -6 addr show scope global 2>/dev/null")
    global_v6 = re.findall(r'inet6\s+([a-fA-F0-9:]+)', ip_out)

if global_v6:
    print(f"{GREEN}PASSED{NC} (Found: {' '.join(global_v6)})")
else:
    print(f"{RED}FAILED{NC} (No Global IPv6 address detected on the main interface)")


# --- TEST 2: Verify IPv6-Mostly / DHCPv4 Option 108 Behavior ---
print("Test 2: Checking DHCPv4 Option 108 & CLAT Status... ", end="", flush=True)
clat_active = False
real_ipv4_found = []

if OS_TYPE == "Darwin":
    ifconfig_out = run_command("ifconfig en0 2>/dev/null")
    # macOS flags 'clat46' or 'nat64' when Option 108 forces an IPv6-only state
    if "clat46" in ifconfig_out or "nat64" in ifconfig_out:
        clat_active = True
    
    ipv4_addresses = re.findall(r'inet\s+([0-9.]+)', ifconfig_out)
    # Filter out the 192.0.0.2 dummy address created locally by the macOS CLAT process
    real_ipv4_found = [ip for ip in ipv4_addresses if ip != "192.0.0.2" and ip != "127.0.0.1"]
else:
    ip_out = run_command("ip addr 2>/dev/null")
    if "clat" in ip_out:
        clat_active = True
    
    ip4_out = run_command("ip -4 addr show scope global 2>/dev/null")
    ipv4_addresses = re.findall(r'inet\s+([0-9.]+)', ip4_out)
    real_ipv4_found = [ip for ip in ipv4_addresses if ip != "192.0.0.2"]

if clat_active and not real_ipv4_found:
    print(f"{GREEN}PASSED{NC} (IPv6-Only Preferred active. Network IPv4 is disabled. Local CLAT active)")
elif real_ipv4_found:
    print(f"{YELLOW}WARNING{NC} (Real native IPv4 address found: {' '.join(real_ipv4_found)}. Option 108 might be missing or ignored)")
else:
    print(f"{RED}FAILED{NC} (Neither Option 108 behavior nor local CLAT was detected)")


# --- TEST 3: Native IPv6 Egress Connectivity ---
print("Test 3: Testing Native IPv6 Connectivity... ", end="", flush=True)
if test_ping("ping6 -c 2 2001:4860:4860::8888"):
    print(f"{GREEN}PASSED{NC}")
else:
    print(f"{RED}FAILED{NC} (Cannot reach Google IPv6 DNS via native routing)")


# --- TEST 4: OS-Level IPv4-Only Hostname Resolution ---
print("Test 4: Checking IPv4-only Resolution via CLAT/DNS64... ", end="", flush=True)
if test_ping("ping -c 2 ipv4.google.com"):
    print(f"{GREEN}PASSED{NC} (System successfully routes IPv4-only hostnames)")
else:
    print(f"{RED}FAILED{NC} (System cannot reach or resolve an IPv4-only hostname)")


# --- TEST 4b: Optional Network-Side DNS64 Synthesis Verification ---
print("Test 4b: Checking Optional Network-side DNS64 Synthesis... ", end="", flush=True)
dns_out = run_command("host -t AAAA ://google.com 2>/dev/null")

if "has AAAA address" in dns_out:
    synth_ips = re.findall(r'has AAAA address\s+([a-fA-F0-9:]+)', dns_out)
    print(f"{GREEN}ACTIVE{NC} (Network DNS is actively synthesizing AAAA records: {' '.join(synth_ips)})")
else:
    print(f"{YELLOW}NOT ACTIVE{NC} (Network DNS provides clean IPv4 records. Expected behavior for native CLAT)")


# --- TEST 5: Literal IPv4 Address Translation (End-to-End NAT64/CLAT) ---
print("Test 5: Testing NAT64/CLAT Translation (Ping to Literal IPv4)... ", end="", flush=True)
if test_ping("ping -c 2 8.8.8.8"):
    print(f"{GREEN}PASSED{NC} (End-to-end legacy IPv4 literal translation works perfectly)")
else:
    print(f"{RED}FAILED{NC} (Literal IPv4 traffic is blocked or translation gateway failed)")

print("==================================================")
print(" Validation Completed.")
print("==================================================")




--- cut here validate_mostly_v6.py ---


Humble bash script to check your IPv6 Mostly Network

Imagine you connect to an IPv6 Mostly Network, is it ok?


--- cut here validate_mostly_v6.sh ---

 

#!/bin/bash


# Terminal output colors

GREEN='\033[0;32m'

RED='\033[0;31m'

YELLOW='\033[1;33m'

NC='\033[0m' # No Color


echo "=================================================="

echo " Starting IPv6-Mostly Network Validation Test"

echo "=================================================="


# Detect Host Operating System

OS="$(uname)"


# --- TEST 1: Verify Global IPv6 Allocation ---

echo -n "Test 1: Checking Global IPv6 Address... "

if [ "$OS" = "Darwin" ]; then

    # macOS network interface scanning

    IPV6_GBL=$(ifconfig en0 2>/dev/null | grep 'inet6 ' | grep -v 'fe80::' | grep -v '::1' | awk '{print $2}')

else

    # Linux network interface scanning

    IPV6_GBL=$(ip -6 addr show scope global | grep 'inet6' | awk '{print $2}')

fi


if [ -n "$IPV6_GBL" ]; then

    echo -e "${GREEN}PASSED${NC} (Found: $(echo $IPV6_GBL | tr '\n' ' '))"

else

    echo -e "${RED}FAILED${NC} (No Global IPv6 address detected on the main interface)"

fi



# --- TEST 2: Verify IPv6-Mostly / DHCPv4 Option 108 Behavior ---

echo -n "Test 2: Checking DHCPv4 Option 108 & CLAT Status... "

if [ "$OS" = "Darwin" ]; then

    # macOS flags 'clat46' or 'nat64' when Option 108 forces an IPv6-only state with translation

    CLAT_CHECK=$(ifconfig en0 2>/dev/null | grep -E 'clat46|nat64')

    # Filter out the 192.0.0.2 dummy address created locally by the macOS CLAT process

    IPV4_REAL=$(ifconfig en0 2>/dev/null | grep 'inet ' | grep -v '192.0.0.2')

else

    # Linux native CLAT or node state verification

    CLAT_CHECK=$(ip addr 2>/dev/null | grep 'clat')

    IPV4_REAL=$(ip -4 addr show scope global 2>/dev/null | grep 'inet' | grep -v '192.0.0.2')

fi


if [ -n "$CLAT_CHECK" ] && [ -z "$IPV4_REAL" ]; then

    echo -e "${GREEN}PASSED${NC} (IPv6-Only Preferred active. Network IPv4 is disabled. Local CLAT active)"

elif [ -n "$IPV4_REAL" ]; then

    echo -e "${YELLOW}WARNING${NC} (Real native IPv4 address found: $IPV4_REAL. Option 108 might be missing or ignored)"

else

    echo -e "${RED}FAILED${NC} (Neither Option 108 behavior nor local CLAT was detected)"

fi



# --- TEST 3: Native IPv6 Egress Connectivity ---

echo -n "Test 3: Testing Native IPv6 Connectivity... "

# Sending ICMPv6 packets to Google Public DNS to verify native routing

if ping6 -c 2 2001:4860:4860::8888 > /dev/null 2>&1; then

    echo -e "${GREEN}PASSED${NC}"

else

    echo -e "${RED}FAILED${NC} (Cannot reach Google IPv6 DNS via native routing)"

fi



# --- TEST 4: OS-Level IPv4-Only Hostname Resolution ---

echo -n "Test 4: Checking IPv4-only Resolution via CLAT/DNS64... "

# Verifies if the OS subsystem can resolve and route an IPv4-only domain name natively

if ping -c 2 ://google.com > /dev/null 2>&1; then

    echo -e "${GREEN}PASSED${NC} (System successfully routes IPv4-only hostnames)"

else

    echo -e "${RED}FAILED${NC} (System cannot reach or resolve an IPv4-only hostname)"

fi



# --- TEST 4b: Optional Network-Side DNS64 Synthesis Verification ---

echo -n "Test 4b: Checking Optional Network-side DNS64 Synthesis... "

# Querying the network DNS server directly for a AAAA record on an IPv4-only target.

# Per RFC 8925, DNS64 is optional if the client runs a local CLAT (464XLAT) environment.

DNS64_SYNTH=$(host -t AAAA ://google.com 2>/dev/null | grep 'has AAAA address')


if [ -n "$DNS64_SYNTH" ]; then

    SYNTH_IP=$(echo "$DNS64_SYNTH" | awk '{print $NF}')

    echo -e "${GREEN}ACTIVE${NC} (Network DNS is actively synthesizing AAAA records: $SYNTH_IP)"

else

    echo -e "${YELLOW}NOT ACTIVE${NC} (Network DNS provides clean IPv4 records. Expected behavior for native CLAT)"

fi



# --- TEST 5: Literal IPv4 Address Translation (End-to-End NAT64/CLAT) ---

echo -n "Test 5: Testing NAT64/CLAT Translation (Ping to Literal IPv4)... "

# Sending a ping to a literal IPv4 address. 

# Success proves that the local CLAT engine successfully translates and encapsulates legacy raw IPv4 traffic into IPv6

if ping -c 2 8.8.8.8 > /dev/null 2>&1; then

    echo -e "${GREEN}PASSED${NC} (End-to-end legacy IPv4 literal translation works perfectly)"

else

    echo -e "${RED}FAILED${NC} (Literal IPv4 traffic is blocked or translation gateway failed)"

fi


echo "=================================================="

echo " Validation Completed."

echo "=================================================="



--- cut here validate_mostly_v6.sh ---



Saturday, September 5, 2026

FRR always displays "No connection to RPKI cache server."

 Situation

   FRR always displays "No connection to RPKI cache server."


Solution

   Specify the source address in the RPKI cache declaration. Example:

  rpki cache tcp 108.162.192.12 8282 source X.Y.Z.N preference 1



Thursday, July 30, 2026

You're not receiving IPv6 when tethering

 Situation

  You're not receiving IPv6 on your computer even though your phone has IPv6 provided by your mobile device.


Solution

  In your mobile network settings --> APN, check if "APN roaming protocol" is set to "IPv4/IPv6".



If it is like the previous image, it won't work.


The correct way is as follows:



Personally, I never imagined that the APN roaming protocol was the problem, but it is.


I hope this helps.

Saturday, May 16, 2026

Absurd or curious correlation? IPv6 & Happiness

After using the World Happiness Report and Google's IPv6 penetration statistics, I found a correlation of 0.4648. So, what does this number mean?


- In statistics, correlation is measured on a scale of -1 to 1.

- 0 means no relationship.

- 1 means a perfect relationship.

- -1 means the variables are inversely proportional.

- A value of 0.4648 is considered a moderate correlation. The two data points move together with some frequency, but not identically.

- Since it's a positive number (+0.4648), the relationship is direct.

- This means that the higher the percentage of IPv6 penetration in a country, the higher its happiness score tends to be.


Don't believe me? Look at the graph below.



What does the graph tell us?

It shows that countries that manage to exceed 40% or 60% of native IPv6 adopters almost never have critically low happiness levels (few score below 5).

Yes, I know it sounds crazy, but it was a fun exercise.


What do you think?


Info

[1] World Happiness Report: 'https://files.worldhappiness.report/WHR26_Data_Figure_2.1.xlsx?_gl=1*zmm7kk*_gcl_au*MzI5Nzg1NTM4LjE3Nzg5NTQ3Mjk.'

[2] Google IPv6 stats: 'https://www.google.com/intl/en_ALL/ipv6/statistics/data/worldmap.js'

[3] Available in google colab here: https://colab.research.google.com/drive/1G8-KObej157c75lNl8kGzywWkBh1VhoW#scrollTo=87c9d1a2

Wednesday, April 29, 2026

fig.show() using Plotly does not work in jupyter

 Case:

  fig.show() using Plotly does not work in jupyter


What to do:

  Force rendering adding this line just before fig.show() using pio

  Example:

import plotly.io as pio

pio.renderers.default = 'iframe' 

fig.show()


Listo!

Thursday, April 2, 2026

FRR Troubleshooting: Fixing Kernel Route Redistribution Issues on Boot

If you are running FRRouting (FRR), you might have encountered a frustrating quirk: after a system reboot, kernel routes (like a static default route) fail to redistribute into RIP or OSPF. Curiously, as soon as you manually restart the FRR service, everything works perfectly.

Here is a breakdown of why this happens and how to fix it for good.

The Problem

During a cold boot, FRR starts its daemons (Zebra, RIPd, etc.), but routes defined at the OS level aren’t being advertised to neighbors. This breaks connectivity after an automated reboot and forces manual intervention (systemctl restart frr), which defeats the purpose of an automated routing stack.

The Root Cause: The Startup "Race Condition"

The culprit is typically a race condition between your network manager (like Netplan or systemd-networkd) and the FRR service.


    Interface Renaming: Modern tools like Netplan often rename interfaces during boot (e.g., from eth0 to enp1s0). If FRR initializes while an interface is in transition, Zebra may ignore routes associated with a "missing" or "changing" interface.

    The "Up" State: If Zebra scans the kernel routing table before the physical or virtual interface is fully marked as "UP" and operational, the routing protocols (like RIP) will deem the route invalid for redistribution.

    Dependency Timing: By default, the FRR service might attempt to load before the network is "fully online," leading to a failed initial synchronization between the kernel and Zebra.


Step-by-Step Solution

The fix involves modifying the systemd unit file to ensure FRR waits until the network stack is completely ready and interface names are finalized.

1. Edit the Service Configuration

Instead of editing the file in /lib/systemd/system/ directly, use a systemd "override" to keep your changes safe during updates:

bash


sudo systemctl edit frr.service


2. Adjust Network Dependencies

In the editor that opens, insert the following lines:

ini


[Unit]

After=network-online.target

Wants=network-online.target


This tells systemd that FRR should only start after the system reports that the network is fully up and functional (network-online.target).

3. Reload and Apply

Save the file and exit the editor. Then, reload the systemd manager to pick up the changes:

bash


sudo systemctl daemon-reload


Verification

On your next reboot, you can verify that Zebra is correctly picking up the routes by checking your FRR logs or using the following debug commands in vtysh:

vtysh


debug zebra kernel

debug rip events



Summary

In modern Linux distributions where interface naming and IP addressing are dynamic, startup timing is everything. Forcing FRR to wait for the network to be "online" ensures that when the daemons ask the kernel for routes, the kernel actually has the final, stable answers ready to give.