// Author: NiuJiuRu // Email: niujiuru@qq.com // 以下函数需"root"权限运行 package ec200u import ( "fmt" "io/fs" "net" "os" "os/exec" "path/filepath" "strconv" "strings" "syscall" "time" "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" "hnyfkj.com.cn/rtu/linux/baseapp" ) // 禁用"eth0"网口, 使其不可用 func disableEth0() error { iface, err := net.InterfaceByName("eth0") if err != nil { return fmt.Errorf("未找到网口\"eth0\": %w", err) } cmd := exec.Command("ip", "link", "set", "dev", iface.Name, "down") cmd.Stdout = baseapp.Logger.WriterLevel(logrus.DebugLevel) cmd.Stderr = baseapp.Logger.WriterLevel(logrus.ErrorLevel) if err := cmd.Run(); err != nil { return fmt.Errorf("关闭网口\"%s\"失败: %w", iface.Name, err) } return nil } // 启动"usb0"网口, 使其变可用 func enableUSB0() error { cmd := exec.Command("ip", "link", "set", "dev", "usb0", "up") cmd.Stdout = baseapp.Logger.WriterLevel(logrus.DebugLevel) cmd.Stderr = baseapp.Logger.WriterLevel(logrus.ErrorLevel) if err := cmd.Run(); err != nil { return fmt.Errorf("启动网口\"usb0\"失败: %w", err) } return nil } // 启动DHCP客户端, 请求IP地址 func dialupUSB0() error { if err := enableUSB0(); err != nil { return err } usb0pid := baseapp.RUN_DIR + "/udhcpc.usb0.pid" cmd := exec.Command("udhcpc", "-b", "-i", "usb0", "-p", usb0pid) cmd.Stdout = baseapp.Logger.WriterLevel(logrus.DebugLevel) cmd.Stderr = baseapp.Logger.WriterLevel(logrus.ErrorLevel) if err := cmd.Run(); err != nil { return fmt.Errorf("\"usb0\"请求地址失败: %w", err) } return nil } // 检测"usb0"网口上的"udhcpc"后台进程是否正在运行, 只保持一个实例 func udhcpcUSB0Exists() (bool, error) { usb0pid := baseapp.RUN_DIR + "/udhcpc.usb0.pid" data, err := os.ReadFile(usb0pid) if err != nil { return false, err } pid, err := strconv.Atoi(strings.TrimSpace(string(data))) if err != nil { return false, err } err = syscall.Kill(pid, 0) if err == nil { // 存在 return true, nil } if err == syscall.ESRCH { // 不存在 return false, nil } return false, err } // 杀死"usb0"网口上的"udhcpc"进程(根据上次运行时它的PID-文件记录) func killUSB0Udhcpc() error { usb0pid := baseapp.RUN_DIR + "/udhcpc.usb0.pid" data, err := os.ReadFile(usb0pid) if err != nil { return err } pid, err := strconv.Atoi(strings.TrimSpace(string(data))) if err != nil { return err } if err := syscall.Kill(pid, syscall.SIGTERM); err != nil && err != syscall.ESRCH { syscall.Kill(pid, syscall.SIGKILL) } return nil } // 强制杀死所有运行中的"udhcpc"进程,包括驻留在后台运行的(SIGKILL) func killAllUdhcpc() { cmd := exec.Command("sh", "-c", `ps | grep '[u]dhcpc' | awk '{print $1}'`) output, err := cmd.Output() if err != nil { return // 没找到也不是错误 } pids := strings.FieldsSeq(string(output)) for pidStr := range pids { pid, err := strconv.Atoi(pidStr) if err != nil { continue } if err := syscall.Kill(pid, syscall.SIGTERM); err != nil && err != syscall.ESRCH { syscall.Kill(pid, syscall.SIGKILL) } } } // 运行"udhcpc"后, 获取"usb0"网口分配到的IPv4地址, 并校检其合法性 func getUSB0Addr() (ip, mask string, err error) { iface, err := net.InterfaceByName("usb0") if err != nil { return "", "", fmt.Errorf("找不到网络接口\"usb0\": %w", err) } addrs, err := iface.Addrs() if err != nil { return "", "", fmt.Errorf("获取\"usb0\"的地址失败: %w", err) } for _, addr := range addrs { ipNet, ok := addr.(*net.IPNet) if !ok { continue } ip4 := ipNet.IP.To4() if ip4 == nil { continue } if ip4.IsPrivate() { // 判断得到IP地址是否合法 return ip4.String(), net.IP(ipNet.Mask).String(), nil } else { return "", "", fmt.Errorf("分配给\"usb0\"的地址\"%s\"无效", ip4.String()) } } return "", "", fmt.Errorf("在\"usb0\"上未找到有效的IPv4 地址") } // 查找并杀死所有占用"/dev/ttyUSB0"设备的进程,返回被杀死进程的PID func freeTTYUSB0() ([]int, error) { var pids []int seen := make(map[int]struct{}) err := filepath.WalkDir("/proc", func(path string, d fs.DirEntry, walkErr error) error { if walkErr != nil { return nil } if filepath.Base(filepath.Dir(path)) != "fd" { return nil } link, err := os.Readlink(path) if err != nil || link != "/dev/ttyUSB0" { return nil } parts := strings.Split(path, "/") if len(parts) < 3 { return nil } pid, err := strconv.Atoi(parts[2]) if err != nil { return nil } if _, exists := seen[pid]; exists { return nil // 已处理过这个PID, 跳过 } seen[pid] = struct{}{} if err := syscall.Kill(pid, syscall.SIGTERM); err != nil && err != syscall.ESRCH { syscall.Kill(pid, syscall.SIGKILL) } pids = append(pids, pid) return nil }) return pids, err } // 定时检测"usb0"网卡上的"udhcpc"后台服务进程, 发现退出时自动拉起 func monitorUSB0Udhcpc(exitCh <-chan struct{}) { t := time.NewTicker(time.Duration(5) * time.Second) defer t.Stop() for { select { case <-t.C: bExists, _ := udhcpcUSB0Exists() if !bExists { // 清场一次 killAllUdhcpc() disableEth0() } if !bExists && dialupUSB0() == nil { // 重新进场 ip, mask, _ := getUSB0Addr() baseapp.Logger.Warnf("[%s] \"usb0\"重新分配的地址: %s/%s!", MODULE_NAME, ip, mask) } case <-exitCh: return } // select end } // for end } func isInterfaceUp(name string) (bool, error) { link, err := netlink.LinkByName(name) if err != nil { return false, err } return link.Attrs().Flags&net.FlagUp != 0, nil } func Is4GCableConnected() (bool, error) { if up, _ := isInterfaceUp("usb0"); !up { // 管理状态 err := enableUSB0() if err != nil { return false, err } } if data, err := os.ReadFile("/sys/class/net/usb0/carrier"); err == nil { // 物理状态 return strings.TrimSpace(string(data)) == "1", nil } link, err := netlink.LinkByName("usb0") if err != nil { return false, err } return link.Attrs().OperState == netlink.OperUp, nil // 操作状态 }