家里有一台闲着的主机,需要用linux做什么时我就会把它打开,用完就关上,因为台式机,费电。

偶尔我需要在外面访问一下家里的什么东西,需要一个机器一直开着,怎么办呢?

还是那个机顶盒,我战胜它了!

利用ChatGPT的力量,战胜它了。

起因

这个机顶盒上面可以运行Go程序,这个已经测试过了,我想在上面运行一个QQ机器人,利用QQ机器人发送命令,用它唤醒一下机器还是可以的。

然而我发现,别看人家安卓版本低,原生就支持IPV6,至于之前为什么说它不支持IPV6,是因为测试,它的确上不去IPV6。

解决域名解析问题

我之前写的DDNS程序,直接运行在一个机器上,就能测试网络IPV6通不通。

交叉编译,然后运行,意料之中,报了错,为什么呢?我去问了聪明的AI。

Ai的回答

他说DNS服务器的问题。

然后我就经历了漫长的查资料、问AI、测试,最终,都不行。

既然这样,那不如自己一点一点找原因。

首先,根据测试,直接IP是可以连接的,但是域名无论是IPV4域名还是IPV6域名,都不行,我让AI写了一个测试DNS的程序:

package main

import (
    "fmt"
    "net"
)

func main() {
    // 查询域名 www.example.com 的 IP 地址
    addr, err := net.LookupHost("www.example.com")
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(addr)
}

当然测试程序返回Error啦。

但是这个测试程序太简短了,不好具体测试问题出在哪里,于是,我让他写更底层的代码:

package main

import (
	"context"
	"fmt"
	"net"
	"os"
	"time"
)

func main() {
	// 创建一个新的 Resolver
	resolver := &net.Resolver{
		PreferGo: true,// 我特意修改了这里的true和false,都成功了
		Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
			d := net.Dialer{
				Timeout: time.Second * 10,
			}
			return d.DialContext(ctx, "udp", net.JoinHostPort("8.8.8.8", "53"))
		},
	}

	// 输入要测试的域名
	var domain string
	fmt.Println("Enter the domain name:")
	fmt.Scanln(&domain)

	// 测试域名
	_, err := resolver.LookupHost(context.Background(), domain)
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(1)
	}

	// 输出测试结果
	fmt.Println("The system DNS is working properly.")
	os.Exit(0)
}

这一个却可以。

我的编辑器是windows的VS Code,看Go的底层代码看不到linux的(编辑器不给跳转),也不好检测问题出在那一步上面了。

简单描述问题就是,给传入DNS,成功;不给传入DNS,失败。猜测是,机顶盒上DNS有问题。

考虑到机顶盒并不是完完整整的linux,出这种问题也不以外,我难道要把所有运行在这上面的程序都改成手动解析域名的吗?

于是,我问了AI这个问题:

!确实,我之前找wifi配置文件时就注意到了,linux都有的resolv.conf文件,Android上面没有。

虽然系统不会用到这个文件,但是其他程序,我的程序需要这个文件。

创建resolv.conf文件,写入像是这样的内容:

nameserver 192.168.1.1
nameserver fe80::1 #odhcp6c:eth0:

第二行是处理Dhcp6的某个系统服务自动填进去的。

配置开机自启

我需要我的程序开机的时候自动运行。

Android和一般的Linux有些不同,这里需要修改 .rc 文件,rc文件里面写了安卓的系统服务,当一些属性变化成一些值时发生什么事什么的。

我直接边问AI边写程序,先将根目录挂载为可读可写,然后加入我自己的一个服务,之后在启动系统的时候启动我的服务。

这一操作,在其它的Android可能可以,但是在这里,不行,重启之后,根目录的文件不会保存,这可能是需要更加底层的操作,直接修改flash之类的,我呢,没戏。

然后就翻遍了整个存储,处了根目录,没有其他目录有rc文件了。

我留意到,有一个rc文件里写了开机执行一个叫set_display_mode.sh的sh脚本,这个脚本里面写了,运行bootanimation这个程序,这个显示别人Logo的程序我早就看他不顺眼了,我从sh脚本里面删除bootanimation,加上我的sh脚本,重启。

结果是,sh修改成功,bootanimation依然启动,我的sh没执行。

为什么?肯定是这个脚本根本就没执行啊,从它的名字也能看出来,只在设置显示模式的时候才执行啊。

反正bootanimation我也不想要,直接拿自己的程序替换掉不就好了嘛。

package main

import (
	"fmt"
	"os/exec"
)

func main() {
	cmd := exec.Command("/system/tvboxserver/start.sh")

	// 获取命令输出
	out, err := cmd.CombinedOutput()
	if err != nil {
		fmt.Printf("运行命令失败: %s\n", err)
		return
	}

	// 打印命令输出
	fmt.Printf("%s\n", out)
}

(这个也是AI写的)这样,成功了。

紧接着来了一个问题,不启动bootanimation,会在执行一些命令的时候会花屏(因为用了一块小屏幕,到底是花屏还是显示了控制台我不清楚),而且无法显示安卓应用。

面对这个问题,简单!直接让我的冒牌bootanimation去启动一次bootanimation不就好了嘛。

抓内鬼

我发现,IPV6不稳定,在刚刚启动IPV6服务的时候,能用,过一会儿就偶尔能用偶尔不能用了。

看百度,查谷歌,问AI,得出结论,默认路由丢失。

猜测是我之前删掉的系统应用有控制IPV6的,于是我把他们都安装回来了(直接把APK复制过来),但是没用。

之前在set_display_mode.sh里面看到,有一个叫getproc的命令,用于获取安卓上的系统变量,我问了AI,它的另一半是setproc

我用getproc命令,发现,在刚启动,还没有得到IPV6地址时,系统变量里没有IPV6的事,在得到IPV6之后,系统变量里一直都是正确的值。

去看rc文件,发现了一些线索:odhcp6c进程(分配IPV6地址的),传入了一个ipv6-android-script.sh作为参数,我可以看看这个脚本。

这个脚本似乎内容很全面,从得到路由器分配的IP地址,到设置给系统变量、赋值给网卡、写入DNS文件。

我查询网卡地址,都是在用ifconfig eth0netcfg这两个命令,这两个命令无一例外,都只能查询到IPV4地址,我问AI,AI告诉我,ip命令也可以查询。

用ip命令查询,我发现,IPV6地址在刚开始和系统变量一样是正确,但在ping不通之后,这个IPV6地址不正确了,它变成了另外一个地址,变化之后的IP可以ping通,但是错误的IP不是分配来的,即便局域网ping通,外网也ping不通,测试,它也无法通过这个地址访问出去。

回到ipv6-android-script.sh,IP的配置是由它配置的,出问题肯定找它啊。

这个脚本通过log命令输出了中间量,sh代码对我来说也是比较困难,我看一下输出的中间量对不对不就好了嘛。

我问AI,AI告诉我log命令的另一半是logcat,通过logcat,得知问题不是出在这里。

logcat里,我发现,在突然无法ping通的时候,logcat总是有VLanService这个东西,经过查资料得知,IPTV利用虚拟局域网使用多播通信,因为大量的机顶盒一个一个发视频画面需要大量的带宽,这个VLanService就是内鬼。

当我手动添加IPV6的时候,他还会大摇大摆删除我的IP,并且给输出:

D/VlanService( 4089): isVlanDeviceAdded(eth0.2003) return true
E/VlanStateTracker( 4089): vlan dev eth0.2003 is added, dont't need add again.
D/NetdConnector( 4089): RCV <- {614 Address removed XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX/128 eth0 128 0}

(这个eth0.2003就是虚拟局域网的虚拟网卡)

试了好多办法,都没能停止这个内鬼服务。

我倒是可以写一次程序不断设置正确的IP,虽然费点性能,但是不是不行,这样做虽然能让机器ping通,但是在ip变化的时候TCP连接会丢失吧。

我想到,设置IP用的是ip命令(至少在ipv6-android-script.sh里面是这么做的),那么我可以用我的程序替换掉ip命令的这个程序。

package main

import (
	"fmt"
	"os"
	"os/exec"
	"strings"
	"time"
)

const theExe = "/system/bin/indeedip"
const logfile = "/tmp/reip.log"

func main() {
	cmdstr := strings.Join(os.Args[1:], " ")
	acc := (!strings.Contains(cmdstr, "fe80::1")) || strings.Contains(cmdstr, "::/0")

	// 写日志
	if _, err := os.Stat(logfile); err == nil { // 只有日志文件存在才写
		if file, err := os.OpenFile(logfile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil {
			file.WriteString(fmt.Sprintf("[%s] %s - %t\n", time.Now().String(), cmdstr, acc))
			file.Close()
		}
	}
	if !acc {
		return
	}
	// 启动命令行程序

	cmd := exec.Cmd{
		Path: theExe,
		Args: append([]string{"ip"}, os.Args[1:]...),
	}

	out, _ := cmd.Output()
	fmt.Print(string(out))
}

AI写的程序加上些许修改,完成!

拿去替换ip,把原理的ip改名成indeedip

这个程序会试图过滤掉内鬼程序的操作。

查日志,万万没想到,内鬼程序不用ip命令,他用sock直接与网络服务通信(拿着日志问AI,AI说这是通过sock的请求)。

猜测这个内鬼很可能是因为IPTV的某个进程才变成内鬼的,要不怎么这个服务还没办法停止?

先删除了大部分APK文件,无果。

然后试着删除ps命令看到的可疑程序,删掉surfaceflinger这个程序就没问题了。

宁可错杀一万

所以断定surfaceflinger就是内鬼!

根据查百度的结果,这个是安卓用来画前台页面的程序,这个程序应该人畜无害,之所以有问题很可能是因为删除这个程序造成系统停止启动后续服务或者这个程序启动了内鬼。

删掉这个程序的后果就是,无论bootanimation开不开都会花屏、能打开应用但是什么也看不到、IPV4分配不到IP。

前台什么的无所谓啦,我又不插显示器,IPV4也好解决,本来就打算用静态IPV4的,在开机启动脚本里面加上:

ip addr flush dev eth0
ip addr add 192.168.1.12/24 dev eth0 broadcast 192.168.1.255 scope global
ip route add default via 192.168.1.1 dev eth0
start dhcpv6_start

因为需要使用系统设置打开IPV6才会启动IPV6服务,所以最后一行自动启动服务。

再配置配置开机自启

没了surfaceflingerbootanimation也不启动了,我干脆将bootanimation改名成了surfaceflinger,但是,结果是sh脚本执行了,里面的程序却都没有执行。

考虑到可能程序启动太早了,系统还没初始化完成,我sh加了sleep代码,还是不行。

这一次没有问AI,用ps命令,加上我的直觉,权限

surfaceflinger是以system用户运行的,bootanimation是以root用户运行的。

有个叫IptvService的进程以root运行,毫无疑问,它没用,顶掉。


节能又静音的服务器,完成。

因为没找到合适的ssh服务端,目前还得adb输入命令。

虽然有IPV6,但我还是想写个QQ机器人呢。

我能想到的,最大的成功就是无愧于自己的心。