// util.go v1.0 package sandbox import ( "fmt" "io" "net" "os" "path/filepath" "strconv" "strings" "sync" "time" "github.com/ssgo/log" "github.com/ssgo/u" ) var currentIds = map[string]bool{} var idLock sync.Mutex func getId() string { id := u.Id8() idLock.Lock() defer idLock.Unlock() for currentIds[id] { id = u.Id8() } currentIds[id] = true return id } func releaseId(id string) { idLock.Lock() defer idLock.Unlock() delete(currentIds, id) } func (s *Sandbox) assertLog(title string, err error, extra ...any) { if s.config.NoLog { return } if err != nil { extra = append(extra, "err", err.Error()) log.DefaultLogger.Info("[Sandbox] "+title+" failed", extra...) } else { log.DefaultLogger.Info("[Sandbox] "+title+" success", extra...) } } func (s *Sandbox) log(title string, extra ...any) { if s.config.NoLog { return } log.DefaultLogger.Info("[Sandbox] "+title, extra...) } type Volumes struct { volumes []Volume indexByTarget map[string]int } func (v *Volumes) Add(vv ...Volume) { for _, v1 := range vv { if v1.Target == "" { v1.Target = v1.Source } if v1.Target == "/proc" || v1.Target == "/sys" { continue } if _, ok := v.indexByTarget[v1.Target]; ok { v.volumes[v.indexByTarget[v1.Target]] = v1 } else { v.volumes = append(v.volumes, v1) v.indexByTarget[v1.Target] = len(v.volumes) - 1 } } } func (v *Volumes) Get() []Volume { return v.volumes } func NewVolumes() *Volumes { return &Volumes{ volumes: []Volume{}, indexByTarget: map[string]int{}, } } func copyLog(workDir string, startTime int64) (int, error) { logDate := time.Unix(startTime, 0).Format("20060102") lastLogFile := filepath.Join(workDir, "stdout.log") errorLogFile := filepath.Join(workDir, "stderr.log") lastLogDest := filepath.Join(workDir, "logs", "stdout_"+logDate+".log") errorLogDest := filepath.Join(workDir, "logs", "stderr_"+logDate+".log") ok := 0 var err1 error if !u.FileExists(lastLogDest) { if err := u.CopyFile(lastLogFile, lastLogDest); err == nil { ok++ } else { err1 = err } } else { if to, err := os.OpenFile(lastLogDest, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644); err == nil { defer to.Close() if from, err := os.Open(lastLogFile); err == nil { defer from.Close() to.WriteString("\n") if _, err := io.Copy(to, from); err == nil { ok++ } else { err1 = err } } } } if !u.FileExists(errorLogDest) { if err := u.CopyFile(errorLogFile, errorLogDest); err == nil { ok++ } else { err1 = err } } else { if to, err := os.OpenFile(errorLogDest, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644); err == nil { defer to.Close() if from, err := os.Open(errorLogFile); err == nil { defer from.Close() to.WriteString("\n") if _, err := io.Copy(to, from); err == nil { ok++ } else { err1 = err } } } } return ok, err1 } type NetRule struct { IP [16]byte // 统一 IPv6 长度 Mask int8 // -1 代表不校验掩码 (单IP), 0-128 代表掩码位 Port uint16 // 0 代表所有端口 IsV6 uint8 // 0: v4, 1: v6 } func ParseNetRule(s string) (*NetRule, error) { rule := &NetRule{Mask: -1} hostStr := s // 1. 处理端口 (从后往前找最后一个冒号,且排除 IPv6 的冒号) if lastColon := strings.LastIndex(s, ":"); lastColon != -1 && !strings.HasSuffix(s, "]") { // 检查冒号后面是否是纯数字(端口),如果不是,说明是 IPv6 地址的一部分 if port, err := strconv.Atoi(s[lastColon+1:]); err == nil { rule.Port = uint16(port) hostStr = s[:lastColon] } } // 2. 处理 CIDR ipStr := hostStr if strings.Contains(hostStr, "/") { _, ipNet, err := net.ParseCIDR(hostStr) if err != nil { return nil, err } ones, _ := ipNet.Mask.Size() rule.Mask = int8(ones) ipStr = ipNet.IP.String() } // 3. 处理 IP (去掉 IPv6 的方括号) ipStr = strings.Trim(ipStr, "[]") ip := net.ParseIP(ipStr) if ip == nil { return nil, fmt.Errorf("invalid ip: %s", ipStr) } if ip4 := ip.To4(); ip4 != nil { copy(rule.IP[:4], ip4) rule.IsV6 = 0 } else { copy(rule.IP[:], ip.To16()) rule.IsV6 = 1 } return rule, nil }