分类 工作&技术 下的文章

众所周知,OVH在后台管理面板中显示/128,但其实是提供的/64。不过,OVH默认仅将 ::1 路由到了网口,这就是/128的缘由。
用NDPPD是解决之道,原理什么就不扯了,直接贴命令步骤吧。 本文由GPT友情提供技术支持,我自己亲身实测在Debian13上完全好用。以前的一些方法长时间空闲时会中断路由或者空闲后需要好几秒才能恢复完成第一次连接的问题,用本方法都不存在。

apt update
apt install ndppd -y

cat <<EOF >/etc/sysctl.d/99-ipv6-proxy.conf
net.ipv6.conf.all.forwarding=1
net.ipv6.conf.default.forwarding=1
net.ipv6.conf.all.proxy_ndp=1
net.ipv6.conf.default.proxy_ndp=1
EOF

sysctl --system

cat >/etc/ndppd.conf <<'EOF'
route-ttl 30000
proxy eno1 {      #注意改成你的外网网卡
    router yes
    ttl 30000
    rule 你的ipv6::/64 {      #比如面板的是 A:B:C:D::1/128,这里就填 A:B:C:D::/64
        static
    }
}
EOF

systemctl enable --now ndppd
systemctl restart ndppd
cat << 'EOF' > /usr/local/bin/ipv6-keepalive.sh
#!/bin/sh
while true; do
    ip -6 neigh show >/dev/null 2>&1
    sleep 60
done
EOF

chmod +x /usr/local/bin/ipv6-keepalive.sh
cat << 'EOF' > /etc/systemd/system/ipv6-keepalive.service
[Unit]
Description=IPv6 NDP Keepalive
After=network.target

[Service]
ExecStart=/usr/local/bin/ipv6-keepalive.sh
Restart=always

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now ipv6-keepalive.service

搞定。现在,你可以在主机中,或者在虚拟机中任意分配/64子网地址,NDPPD就会将其自动宣告出去。
这个博客现在就是Cloudflare通过IPV6回源。好处是啥?这样直接可以回源到虚拟机80/443上,因为是个独立的IP,而如果通过V4则需要在主机上做额外的7层转发。

这几天上网卡死了,访问网站十个有九个都卡,包括我这倒霉的博客我自己也很难打开。但只限于访问网站,ssh和网络测试等又没有任何问题(但speedtest.net也很难打开),路由器和chrome都快被我重启得冒烟了。
今天终于发现,是cloudflare卡了,我太信任它了,居然都没去检查它。结果就是,同城ping居然能用掉100多ms。例子只是1.1.1.1,其实所有的节点ip都一样卡。
知道问题就简单了,因为要回国,我机器常年开着clash的,加条分流规则 IP-ASN,13335,xxxxx (xxxxx是邻国的节点,ping值10ms。感谢chatgpt,让我知道可以ASN),瞬间世界重新恢复了美好。

root@OpenWrt:~# ping 1.1.1.1
PING 1.1.1.1 (1.1.1.1): 56 data bytes
64 bytes from 1.1.1.1: seq=0 ttl=54 time=112.493 ms
64 bytes from 1.1.1.1: seq=1 ttl=54 time=114.242 ms
64 bytes from 1.1.1.1: seq=2 ttl=54 time=113.530 ms

root@OpenWrt:~# traceroute 1.1.1.1
traceroute to 1.1.1.1 (1.1.1.1), 30 hops max, 46 byte packets
 1  x.x.x.x (x.x.x.x)  2.521 ms  1.529 ms  5.002 ms
 2  10.55.49.45 (10.55.49.45)  4.486 ms  10.55.49.47 (10.55.49.47)  2.787 ms  10.55.49.45 (10.55.49.45)  4.572 ms
 3  10.55.37.86 (10.55.37.86)  6.924 ms  10.55.37.90 (10.55.37.90)  3.662 ms  10.55.37.86 (10.55.37.86)  5.241 ms
 4  *  *  x.x.x.x (x.x.x.x)  61.770 ms     俺们村的IP
 5  one.one.one.one (1.1.1.1)  112.578 ms  113.631 ms  112.346 ms    
      /cdn-cgi/trace显示是俺们乡的node

前几天才把代码再次分享出来,今天就可以用上了,甚是欣慰。
不过,别人用这脚本买到的机器比我自己买到的还好。唉,我就是这么手气背。
源代码如下,go的:
https://blog.lostshit.com/index.php/archives/325/#comment-245

ks-le-b参数是:

  required_plan_code: "26skleb01-v1"
  options:
    - "bandwidth-500-26skle"
    - "ram-32g-ecc-2400-26skleb01-v1"
    - "softraid-2x450nvme-26skleb01-v1"

不过已经断货了,当然,挂挂机也许可以买到别人退货的。不要中奖的话其实也可以了,性价比拉满的。

这几天无聊写了一个小小的项目,现在完成度有80%了。
整个项目我基本没有手搓代码,全部都是cursor完成的。我要做的事情就是把项目进行细分以及审核和测试代码(当然,单元测试也是让cursor写甚至直接运行),以及一次次叫cursor修改。
代码写得不算漂亮,但胜在它能运行。虽然bug挺多的,不过慢慢修还是总能修好,即便有时把人气得够呛。
要让AI完全构建一个工程现阶段还是不现实的。只有小白才敢说拿一个项目全部AI自行生成,那些单页应用就别扯了,那不能算项目。不过等AI再继续进化下去的话,看这样也许真就可以小白也能了。
还好我打算退休颐养天年了,以后的程序员们看来是挺难的了。

root@vps:~/test# find . -name "*.go" -type f -exec cat {} + | wc -l
6544

家里路由器,以前用的网件,它自带了noip的支持。 不过呢,可能是太老了,速度上不去,我家800M的宽带只能跑到4-500M。换成小米并刷openwrt的话,可以跑到700多M,算跑满了。不过问题就是ddns不太支持了,虽然有一堆,但我都没号也不想花钱呀,自己搓一个吧。用起来其实挺简单的,在openwrt里面的ddns那里服务商选自定义然后填好参数就可以了。
域名的话是用自己的托管在cloudflare的域名。用户名密码固定写死在代码中了,不是商业的东西,简单防刷就好。
自定义URL格式是标准的,就是 https://ddns服务器域名/updatedns?hostname=[DOMAIN]&myip=[IP]&username=[USERNAME]&password=[PASSWORD]

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
)

var (
	apiKey     = "cloudflare API KEY"
	email      = "cloudflare登录email"
	zoneID     = "域名的zone id"
	username   = "客户端认证的用户名"
	password   = "密码"
	ListenPort = "监听端口"
	ListenAddr = "监听ip"
)

type dnsRecord struct {
	ID      string `json:"id"`
	Type    string `json:"type"`
	Name    string `json:"name"`
	Content string `json:"content"`
}

func updateDNS(ip, hostname string) error {
	client := &http.Client{}

	reqURL := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records?type=A&name=%s", zoneID, hostname)

	req, _ := http.NewRequest("GET", reqURL, nil)
	req.Header.Add("X-Auth-Email", email)
	req.Header.Add("X-Auth-Key", apiKey)
	req.Header.Add("Content-Type", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("request failed: %w", err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)

	var result struct {
		Success bool        `json:"success"`
		Result  []dnsRecord `json:"result"`
		Errors  []struct {
			Code    int    `json:"code"`
			Message string `json:"message"`
		} `json:"errors"`
	}

	if err := json.Unmarshal(body, &result); err != nil {
		return fmt.Errorf("json unmarshal failed: %w", err)
	}

	if !result.Success {
		return fmt.Errorf("API returned success=false, errors: %+v", result.Errors)
	}

	if len(result.Result) == 0 {
		return fmt.Errorf("record not found for %s - the DNS record may not exist yet. Please create it first in Cloudflare Dashboard", hostname)
	}

	rec := result.Result[0]

	if rec.Content == ip {
		log.Printf("No change: %s already -> %s", hostname, ip)
		return nil
	}

	data := map[string]interface{}{
		"type":    "A",
		"name":    hostname,
		"content": ip,
		"ttl":     120,
	}
	j, _ := json.Marshal(data)

	putURL := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records/%s", zoneID, rec.ID)

	req2, _ := http.NewRequest("PUT", putURL, bytes.NewBuffer(j))
	req2.Header.Add("X-Auth-Email", email)
	req2.Header.Add("X-Auth-Key", apiKey)
	req2.Header.Add("Content-Type", "application/json")

	resp2, err := client.Do(req2)
	if err != nil {
		return fmt.Errorf("update request failed: %w", err)
	}
	defer resp2.Body.Close()

	io.ReadAll(resp2.Body)
	log.Printf("DNS updated: %s -> %s", hostname, ip)

	return nil
}

func main() {
	r := gin.Default()

	r.GET("/updatedns", func(c *gin.Context) {
		hostname := c.Query("hostname")
		myip := c.Query("myip")
		reqUsername := c.Query("username")
		reqPassword := c.Query("password")

		if hostname == "" || myip == "" || reqUsername == "" || reqPassword == "" {
			c.String(http.StatusBadRequest, "missing required parameters: hostname, myip, username, password")
			return
		}

		if reqUsername != username || reqPassword != password {
			c.String(http.StatusUnauthorized, "invalid username or password")
			return
		}

		log.Printf("Update request: %s -> %s", hostname, myip)

		if err := updateDNS(myip, hostname); err != nil {
			log.Printf("Error: %v", err)
			c.String(http.StatusInternalServerError, err.Error())
			return
		}

		c.String(http.StatusOK, "good %s", myip)
	})

	addr := fmt.Sprintf("%s:%s", ListenAddr, ListenPort)
	log.Printf("DDNS server running on %s", addr)
	r.Run(addr)
}