debugging to_WG core_developer_branch
authorThomas Walker Lynch <eknp9n@reasoningtechnology.com>
Fri, 31 Oct 2025 05:05:25 +0000 (05:05 +0000)
committerThomas Walker Lynch <eknp9n@reasoningtechnology.com>
Fri, 31 Oct 2025 05:05:25 +0000 (05:05 +0000)
developer/cc/Rabbit_2017_to_WG.kmod.c
developer/tool/hook.git [new file with mode: 0644]
release/kmod/Rabbit_2017_to_WG.ko
release/machine/hello [deleted file]

index 8a29aa3..ff889b4 100644 (file)
@@ -1,4 +1,6 @@
-// rabbit_2017_to_WG.c — forward UID 2017 traffic to WG
+// -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*-
+// rabbit_2017_to_WG.c — force UID 2017 traffic via WireGuard dev "US" (IPv4)
+// Debuggable build with versioning and no-leak behavior.
 
 #include <linux/module.h>
 #include <linux/kernel.h>
 #include <linux/netfilter.h>
 #include <linux/netfilter_ipv4.h>
 #include <linux/ip.h>
+#include <linux/if_ether.h>
+#include <linux/rcupdate.h>
+#include <linux/inetdevice.h>  /* __in_dev_get_rcu */
 #include <net/sock.h>
 #include <net/inet_sock.h>
+#include <net/route.h>      /* ip_route_output_flow */
+#include <net/ip.h>         /* RT_TOS() */
+#include <net/dst.h>       /* sk_dst_set() / dst APIs */
 
-#define US_UID 2017
-#define DEV_NAME   "US"   /* WireGuard iface to force */
+#define RABBIT_WG_VERSION "1.2.2-2025-10-30"
+
+#define US_UID   2017
+#define DEV_NAME "US"       /* WireGuard iface to force */
+
+/* dbg param (echo 1 > /sys/module/Rabbit_2017_to_WG/parameters/dbg) */
+static bool dbg = true;
+module_param(dbg ,bool ,0644);
+MODULE_PARM_DESC(dbg ,"Enable debug logs (default: true)");
+
+#define DBG(fmt, ...) do { \
+  if (dbg) pr_info_ratelimited("rabbit_uid2017: " fmt ,##__VA_ARGS__); \
+} while (0)
 
 static atomic64_t cnt_v4_local_out = ATOMIC64_INIT(0);
 static atomic64_t cnt_v4_postroute = ATOMIC64_INIT(0);
+static atomic64_t cnt_drop_no_us   = ATOMIC64_INIT(0);
+static atomic64_t cnt_drop_route   = ATOMIC64_INIT(0);
 
 struct v4_cidr { __be32 net, mask; };
 #define V4(x) (__force __be32)cpu_to_be32(x)
+
+/* Exception destinations: never force to US. Adjust to taste. */
 static struct v4_cidr v4_excepts[] = {
-  { V4(0x7f000000), V4(0xff000000) }, /* 127.0.0.0/8 */
-  { V4(0xa9fe0000), V4(0xffff0000) }, /* 169.254.0.0/16 */
-  { V4(0xc0a80000), V4(0xffff0000) }, /* 192.168.0.0/16  (adjust) */
-  { V4(0x0a000002), V4(0xffffffff) }, /* 10.0.0.2/32    US local */
-  { V4(0x0a080004), V4(0xffffffff) }, /* 10.8.0.4/32    x6 local */
+  { V4(0x7f000000) ,V4(0xff000000) } /* 127.0.0.0/8 (loopback) */
+  ,{ V4(0xa9fe0000) ,V4(0xffff0000) } /* 169.254.0.0/16 (LLA)   */
+  ,{ V4(0xc0a80000) ,V4(0xffff0000) } /* 192.168.0.0/16 (LAN)   */
+  ,{ V4(0x0a000002) ,V4(0xffffffff) } /* 10.0.0.2/32    US local */
+  ,{ V4(0x0a080004) ,V4(0xffffffff) } /* 10.8.0.4/32    x6 local */
 };
-static inline bool v4_in_cidr(__be32 a, struct v4_cidr c){ return (a & c.mask) == (c.net & c.mask); }
-static bool v4_is_except(__be32 d){ int i; for (i=0;i<ARRAY_SIZE(v4_excepts);i++) if (v4_in_cidr(d, v4_excepts[i])) return true; return false; }
+
+static inline bool v4_in_cidr(__be32 a ,struct v4_cidr c) {
+  return (a & c.mask) == (c.net & c.mask);
+}
+static bool v4_is_except(__be32 d) {
+  int i;
+  for (i = 0; i < ARRAY_SIZE(v4_excepts); i++)
+    if (v4_in_cidr(d ,v4_excepts[i]))
+      return true;
+  return false;
+}
 
 static int us_ifindex;  /* cached ifindex for DEV_NAME */
 
-static inline bool from_uid_2017(const struct nf_hook_state *st, struct sk_buff *skb){
+static bool us_has_ipv4_addr(void) {
+  bool has = false;
+  rcu_read_lock();
+  {
+    struct net_device *dev = dev_get_by_index_rcu(&init_net ,us_ifindex);
+    if (dev) {
+      struct in_device *in_dev = __in_dev_get_rcu(dev);
+      if (in_dev) {
+        struct in_ifaddr *ifa;
+        for (ifa = rcu_dereference(in_dev->ifa_list); ifa; ifa = rcu_dereference(ifa->ifa_next)) {
+          if (ifa->ifa_address) { has = true; break; }
+        }
+      }
+    }
+  }
+  rcu_read_unlock();
+  return has;
+}
+
+static inline bool from_uid_2017(const struct nf_hook_state *st ,struct sk_buff *skb) {
   struct sock *sk = st->sk ? st->sk : skb_to_full_sk(skb);
   if (!sk) return false;
   return __kuid_val(sock_i_uid(sk)) == US_UID;
 }
 
-/* Bind the socket to DEV_NAME once we see its first packet */
-static inline void maybe_bind_socket_to_us(struct sk_buff *skb, const struct iphdr *iph, const struct nf_hook_state *st){
+/* Ensure US device exists and is up (oper up). */
+static bool us_dev_ok(void) {
+  bool ok = false;
+  rcu_read_lock();
+  {
+    struct net_device *dev = dev_get_by_index_rcu(&init_net ,us_ifindex);
+    if (dev && netif_running(dev) && netif_oper_up(dev))
+      ok = true;
+  }
+  rcu_read_unlock();
+  return ok;
+}
+
+/* Attach a route (dst) to skb forcing oif=US so WG can encapsulate/send. */
+/* helper: return first IPv4 address on the US device (net byte order) or 0 */
+static __be32 us_first_ipv4(void)
+{
+  __be32 addr = 0;
+  rcu_read_lock();
+  {
+    struct net_device *dev = dev_get_by_index_rcu(&init_net, us_ifindex);
+    if (dev) {
+      struct in_device *in_dev = __in_dev_get_rcu(dev);
+      if (in_dev) {
+        struct in_ifaddr *ifa;
+        for (ifa = rcu_dereference(in_dev->ifa_list); ifa; ifa = rcu_dereference(ifa->ifa_next)) {
+          if (ifa->ifa_address) { addr = ifa->ifa_address; break; }
+        }
+      }
+    }
+  }
+  rcu_read_unlock();
+  return addr;
+}
+
+/* Attach a route (dst) to skb forcing oif=US so WG can encapsulate/send. */
+static bool ensure_route_to_us(struct sk_buff *skb ,const struct iphdr *iph)
+{
+  struct rtable *rt;
+  __be32 saddr_us = us_first_ipv4(); /* may be 0 */
+
+  struct flowi4 fl4 = {
+    .daddr        = iph->daddr,
+    .saddr        = saddr_us ? saddr_us : 0,   /* prefer US IP as source */
+    .flowi4_tos   = RT_TOS(iph->tos),
+    .flowi4_oif   = us_ifindex,                /* force egress on US */
+    .flowi4_mark  = skb->mark,
+    .flowi4_proto = iph->protocol,
+  };
+
+  /* If a dst is already set to US, keep it. */
+  if (skb_dst(skb)) {
+    const struct dst_entry *dst = skb_dst(skb);
+    const struct net_device *out = dst ? dst->dev : NULL;
+    if (out && out->ifindex == us_ifindex) {
+      DBG("dst already US(ifindex=%d)\n", us_ifindex);
+      return true;
+    }
+    DBG("replacing existing dst dev(ifindex=%d) with US(ifindex=%d)\n",
+        out ? out->ifindex : -1, us_ifindex);
+  }
+
+  rt = ip_route_output_flow(&init_net, &fl4, NULL);
+  if (IS_ERR(rt)) {
+    DBG("ip_route_output_flow failed: daddr=%pI4 tos=0x%x oif=%d err=%ld\n",
+        &iph->daddr, iph->tos, us_ifindex, PTR_ERR(rt));
+    return false;
+  }
+
+  /* Log out_dev before transferring ownership to the skb */
+  DBG("dst attached: daddr=%pI4 tos=0x%x oif=US(ifindex=%d) out_dev=%s\n",
+      &iph->daddr, iph->tos, us_ifindex,
+      rt->dst.dev ? rt->dst.dev->name : "(null)");
+
+  /* Transfer reference to skb and stop using rt afterwards */
+  skb_dst_drop(skb);
+  skb_dst_set(skb, &rt->dst);  /* ownership moves to skb */
+
+  return true;
+}
+
+
+/* Bind the socket to US; idempotent. */
+static inline void bind_socket_to_us(struct sk_buff *skb ,const struct nf_hook_state *st) {
   struct sock *sk = st->sk ? st->sk : skb_to_full_sk(skb);
-  if (!sk || !us_ifindex) return;
-  if (v4_is_except(iph->daddr)) return;
-  if (READ_ONCE(sk->sk_bound_dev_if) == us_ifindex) return;
-  WRITE_ONCE(sk->sk_bound_dev_if, us_ifindex);
+  if (sk && READ_ONCE(sk->sk_bound_dev_if) != us_ifindex) {
+    WRITE_ONCE(sk->sk_bound_dev_if ,us_ifindex);
+    DBG("sk_bound_dev_if <- US(ifindex=%d)\n" ,us_ifindex);
+  }
 }
 
-static unsigned int rabbit_v4_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *st){
-  if (st->pf != NFPROTO_IPV4) return NF_ACCEPT;
+static unsigned int rabbit_v4_hook(void *priv ,struct sk_buff *skb ,const struct nf_hook_state *st) {
+  if (st->pf != NFPROTO_IPV4)
+    return NF_ACCEPT;
 
   if (st->hook == NF_INET_LOCAL_OUT) {
-    if (from_uid_2017(stskb)) {
+    if (from_uid_2017(st ,skb)) {
       const struct iphdr *iph = ip_hdr(skb);
-      if (iph) maybe_bind_socket_to_us(skb, iph, st);
+      if (iph) {
+        DBG("LOCAL_OUT uid=2017 daddr=%pI4 tos=0x%x proto=%u\n"
+            ,&iph->daddr ,iph->tos ,iph->protocol);
+
+        if (!v4_is_except(iph->daddr)) {
+          if (likely(us_dev_ok())) {
+            if (likely(ensure_route_to_us(skb ,iph))) {
+              bind_socket_to_us(skb ,st);
+            } else {
+              atomic64_inc(&cnt_drop_route);
+              DBG("DROP: no route via US\n");
+              return NF_DROP; /* no leaks */
+            }
+          } else {
+            atomic64_inc(&cnt_drop_no_us);
+            DBG("DROP: US not up\n");
+            return NF_DROP;   /* no leaks */
+          }
+        } else {
+          DBG("EXCEPT dst=%pI4 — not forcing US\n" ,&iph->daddr);
+        }
+      }
       atomic64_inc(&cnt_v4_local_out);
     }
   } else if (st->hook == NF_INET_POST_ROUTING) {
-    if (from_uid_2017(st, skb)) atomic64_inc(&cnt_v4_postroute);
+    if (from_uid_2017(st ,skb))
+      atomic64_inc(&cnt_v4_postroute);
   }
   return NF_ACCEPT;
 }
 
 static struct nf_hook_ops rabbit_ops[] = {
-  { .hook = rabbit_v4_hook, .pf = NFPROTO_IPV4, .hooknum = NF_INET_LOCAL_OUT,    .priority = NF_IP_PRI_FIRST },
-  { .hook = rabbit_v4_hook, .pf = NFPROTO_IPV4, .hooknum = NF_INET_POST_ROUTING, .priority = NF_IP_PRI_FIRST },
+  { .hook = rabbit_v4_hook , .pf = NFPROTO_IPV4 , .hooknum = NF_INET_LOCAL_OUT    , .priority = NF_IP_PRI_FIRST }
+  ,{ .hook = rabbit_v4_hook , .pf = NFPROTO_IPV4 , .hooknum = NF_INET_POST_ROUTING , .priority = NF_IP_PRI_FIRST }
 };
 
-static int __init rabbit_init(void){
+static int __init rabbit_init(void) {
   struct net_device *dev;
+
   rcu_read_lock();
-  dev = dev_get_by_name_rcu(&init_netDEV_NAME);
+  dev = dev_get_by_name_rcu(&init_net ,DEV_NAME);
   us_ifindex = dev ? dev->ifindex : 0;
   rcu_read_unlock();
+
   if (!us_ifindex) {
-    pr_err("rabbit_uid2017_bind: device \"%s\" not found\n", DEV_NAME);
+    pr_err("rabbit_uid2017_bind[%s]: device \"%s\" not found\n" ,RABBIT_WG_VERSION ,DEV_NAME);
     return -ENODEV;
   }
+
+  if (!us_has_ipv4_addr())
+    pr_warn("rabbit_uid2017_bind[%s]: US has no IPv4 address; inner flows may stall\n"
+            ,RABBIT_WG_VERSION);
+
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,3,0)
   {
-    int ret = nf_register_net_hooks(&init_net, rabbit_ops, ARRAY_SIZE(rabbit_ops));
+    int ret = nf_register_net_hooks(&init_net ,rabbit_ops ,ARRAY_SIZE(rabbit_ops));
     if (ret) return ret;
   }
 #else
-  { int ret = nf_register_hooks(rabbit_ops, ARRAY_SIZE(rabbit_ops)); if (ret) return ret; }
+  {
+    int ret = nf_register_hooks(rabbit_ops ,ARRAY_SIZE(rabbit_ops));
+    if (ret) return ret;
+  }
 #endif
-  pr_info("rabbit_uid2017_bind: loaded; UID=%d bound to dev %s(ifindex=%d)\n", US_UID, DEV_NAME, us_ifindex);
+  pr_info("rabbit_uid2017_bind: loaded v%s; UID=%d bound to dev %s(ifindex=%d)\n"
+          ,RABBIT_WG_VERSION ,US_UID ,DEV_NAME ,us_ifindex);
   return 0;
 }
 
-static void __exit rabbit_exit(void){
+static void __exit rabbit_exit(void) {
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,3,0)
-  nf_unregister_net_hooks(&init_net, rabbit_ops, ARRAY_SIZE(rabbit_ops));
+  nf_unregister_net_hooks(&init_net ,rabbit_ops ,ARRAY_SIZE(rabbit_ops));
 #else
-  nf_unregister_hooks(rabbit_opsARRAY_SIZE(rabbit_ops));
+  nf_unregister_hooks(rabbit_ops ,ARRAY_SIZE(rabbit_ops));
 #endif
-  pr_info("rabbit_uid2017_bind: unload v4(lo=%lld,po=%lld)\n",
-          (long long)atomic64_read(&cnt_v4_local_out),
-          (long long)atomic64_read(&cnt_v4_postroute));
+  pr_info("rabbit_uid2017_bind: unload v%s v4(lo=%lld,po=%lld,drop_no_us=%lld,drop_no_route=%lld)\n"
+          ,RABBIT_WG_VERSION
+          ,(long long)atomic64_read(&cnt_v4_local_out)
+          ,(long long)atomic64_read(&cnt_v4_postroute)
+          ,(long long)atomic64_read(&cnt_drop_no_us)
+          ,(long long)atomic64_read(&cnt_drop_route));
 }
 
 module_init(rabbit_init);
 module_exit(rabbit_exit);
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Rabbit");
-MODULE_DESCRIPTION("Force UID 2017 traffic onto dev \"US\" at LOCAL_OUT with IPv4 exceptions");
+MODULE_DESCRIPTION("Force UID 2017 traffic onto dev \"US\" at LOCAL_OUT with IPv4 exceptions and attached dst");
+MODULE_VERSION(RABBIT_WG_VERSION);
diff --git a/developer/tool/hook.git b/developer/tool/hook.git
new file mode 100644 (file)
index 0000000..3237011
--- /dev/null
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+# .git/hooks/pre-commit (make executable):
+set -euo pipefail
+changed=$(git diff --cached --name-only --diff-filter=AM | grep -E '\.(c|h|cpp|hpp|py|json|ya?ml)$' || true)
+[ -z "$changed" ] && exit 0
+fail=0
+for f in $changed; do
+  tmp=$(mktemp)
+  python3 tool/rtfmt <"$f" >"$tmp" || { rm -f "$tmp"; fail=1; continue; }
+  if ! diff -q "$f" "$tmp" >/dev/null; then
+    mv "$tmp" "$f"
+    git add "$f"
+    echo "rtfmt: rewrote $f"
+  else
+    rm -f "$tmp"
+  fi
+done
+exit $fail
index c60a70f..9b01531 100644 (file)
Binary files a/release/kmod/Rabbit_2017_to_WG.ko and b/release/kmod/Rabbit_2017_to_WG.ko differ
diff --git a/release/machine/hello b/release/machine/hello
deleted file mode 100755 (executable)
index 9f26c0f..0000000
Binary files a/release/machine/hello and /dev/null differ