package service import ( "errors" "fmt" "hy2xs-admin/dao" "hy2xs-admin/model/constant" "hy2xs-admin/util" "strings" ) var ( netManager string ingressInterface string Add = "add" Delete = "delete" Table = "hui_porthopping" Comment = "hui_hysteria_porthopping" ) func InitForward() { if nft, err := util.Exec("command -v nft"); err == nil && strings.TrimSpace(nft) != "" { netManager = "nft" } else if iptables, err := util.Exec("command -v iptables"); err == nil && strings.TrimSpace(iptables) != "" { netManager = "iptables" } if ii, err := util.Exec("ls /sys/class/net | grep -E '^en|^eth'"); err == nil && strings.TrimSpace(ii) != "" { iiList := strings.Split(ii, "\n") ingressInterface = strings.TrimSpace(iiList[0]) } } func InitTableAndChain() error { if netManager == "nft" { _, err := util.Exec(fmt.Sprintf("nft add table inet %s", Table)) if err != nil { return err } _, err = util.Exec(fmt.Sprintf("nft add chain inet %s prerouting { type nat hook prerouting priority dstnat\\; policy accept\\; }", Table)) if err != nil { return err } } return nil } func InitPortHopping() error { if err := RemoveByComment(); err != nil { return err } hysteria2Config, err := GetHysteria2Config() if err != nil { return err } // set port forward hysteria2ConfigPortHopping, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigPortHopping) if err != nil { return err } if *hysteria2ConfigPortHopping.Value != "" { listen := strings.Split(*hysteria2Config.Listen, ":") if len(listen) == 2 { portHoppings := strings.Split(*hysteria2ConfigPortHopping.Value, ",") for _, item := range portHoppings { if err := portForward(item, listen[1], Add); err != nil { return err } } } } return nil } func portForward(rules string, target string, option string) error { switch netManager { case "nft": switch option { case Add, Delete: return nftForward(rules, target, option) default: return errors.New("unsupported command option") } case "iptables": switch option { case Add: return iptablesForward(rules, target, "-A") case Delete: return iptablesForward(rules, target, "-D") default: return errors.New("unsupported command option") } default: return errors.New("port hopping not supported on this system") } } func RemoveByComment() error { switch netManager { case "nft": return ntfRemoveByComment(Comment) case "iptables": return iptablesRemoveByComment(Comment) default: return errors.New("port hopping not supported on this system") } } func nftForward(rules string, target string, option string) error { if ingressInterface == "" { return fmt.Errorf("no network interface detected") } // nft list ruleset // 创建表:nft add table inet hui_hysteria_porthopping // 创建链:nft add chain inet hui_hysteria_porthopping prerouting { type nat hook prerouting priority dstnat\; policy accept\; } // 添加规则:nft add rule inet hui_hysteria_porthopping prerouting iifname enp1s0 udp dport {30000-40000} counter redirect to :444 comment hui_hysteria_porthopping _, err := util.Exec(fmt.Sprintf("nft %s rule inet %s prerouting iifname %s udp dport {%s} counter redirect to :%s comment %s", option, Table, ingressInterface, rules, target, Comment)) if err != nil { return err } return nil } func ntfRemoveByComment(comment string) error { rules, err := nftRules() if err != nil { return err } for _, rule := range rules { if strings.Contains(rule, comment) { parts := strings.Fields(rule) handle := parts[len(parts)-1] _, err := util.Exec(fmt.Sprintf("nft delete rule inet %s prerouting handle %s", Table, strings.TrimSpace(handle))) if err != nil { return err } } } return nil } func nftRules() ([]string, error) { listOutput, err := util.Exec(fmt.Sprintf("nft list ruleset | grep -q %s && echo 'found' || echo 'not found'", Comment)) if err != nil { return nil, err } if strings.TrimSpace(listOutput) == "not found" { return []string{}, nil } output, err := util.Exec(fmt.Sprintf("nft --handle list chain inet %s prerouting", Table)) if err != nil { return nil, err } rules := strings.Split(output, "\n") return rules, nil } func iptablesForward(rules string, target string, option string) error { if ingressInterface == "" { return fmt.Errorf("no network interface detected") } rulePairs := strings.Split(rules, ",") for _, pair := range rulePairs { ports := "" portRange := strings.Split(pair, "-") if len(portRange) == 1 { ports = strings.TrimSpace(portRange[0]) } else if len(portRange) == 2 { startPort := strings.TrimSpace(portRange[0]) endPort := strings.TrimSpace(portRange[1]) ports = startPort + ":" + endPort } else { return fmt.Errorf("invalid port range format: %s", pair) } if len(ports) != 0 { if err := iptablesAddRule(option, ports, target); err != nil { return err } } } return nil } func iptablesAddRule(option, ports, target string) error { protocols := [2]string{"iptables", "ip6tables"} for _, protocol := range protocols { // iptables -t nat -A PREROUTING -i enp1s0 -p udp --dport 30000:40000 -j REDIRECT --to-port 444 -m comment --comment hui_hysteria_porthopping _, err := util.Exec(fmt.Sprintf("%s -t nat %s PREROUTING -i %s -p udp --dport %s -j REDIRECT --to-port %s -m comment --comment %s", protocol, option, ingressInterface, ports, target, Comment)) if err != nil { return err } } return nil } func iptablesRemoveByComment(comment string) error { protocols := [2]string{"iptables", "ip6tables"} for _, protocol := range protocols { rules, err := iptablesRules(protocol) if err != nil { return err } for _, rule := range rules { if strings.Contains(rule, comment) { parts := strings.Fields(rule) handle := parts[0] _, err := util.Exec(fmt.Sprintf("%s -t nat -D PREROUTING %s", protocol, strings.TrimSpace(handle))) if err != nil { return err } } } } return nil } func iptablesRules(protocol string) ([]string, error) { // iptables -t nat -L PREROUTING -v --line-numbers output, err := util.Exec(fmt.Sprintf("%s -t nat -L PREROUTING -v --line-numbers", protocol)) if err != nil { return nil, err } rules := strings.Split(output, "\n") return rules, nil }