github.com/unionj-cloud/go-doudou@v1.3.8-0.20221011095552-0088008e5b31/toolkit/net/net_darwin.go (about) 1 //go:build darwin 2 // +build darwin 3 4 package net 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "os/exec" 11 "regexp" 12 "strconv" 13 "strings" 14 15 "github.com/unionj-cloud/go-doudou/toolkit/internal/common" 16 ) 17 18 var ( 19 errNetstatHeader = errors.New("Can't parse header of netstat output") 20 netstatLinkRegexp = regexp.MustCompile(`^<Link#(\d+)>$`) 21 ) 22 23 const endOfLine = "\n" 24 25 func parseNetstatLine(line string) (stat *IOCountersStat, linkID *uint, err error) { 26 var ( 27 numericValue uint64 28 columns = strings.Fields(line) 29 ) 30 31 if columns[0] == "Name" { 32 err = errNetstatHeader 33 return 34 } 35 36 // try to extract the numeric value from <Link#123> 37 if subMatch := netstatLinkRegexp.FindStringSubmatch(columns[2]); len(subMatch) == 2 { 38 numericValue, err = strconv.ParseUint(subMatch[1], 10, 64) 39 if err != nil { 40 return 41 } 42 linkIDUint := uint(numericValue) 43 linkID = &linkIDUint 44 } 45 46 base := 1 47 numberColumns := len(columns) 48 // sometimes Address is omitted 49 if numberColumns < 12 { 50 base = 0 51 } 52 if numberColumns < 11 || numberColumns > 13 { 53 err = fmt.Errorf("Line %q do have an invalid number of columns %d", line, numberColumns) 54 return 55 } 56 57 parsed := make([]uint64, 0, 7) 58 vv := []string{ 59 columns[base+3], // Ipkts == PacketsRecv 60 columns[base+4], // Ierrs == Errin 61 columns[base+5], // Ibytes == BytesRecv 62 columns[base+6], // Opkts == PacketsSent 63 columns[base+7], // Oerrs == Errout 64 columns[base+8], // Obytes == BytesSent 65 } 66 if len(columns) == 12 { 67 vv = append(vv, columns[base+10]) 68 } 69 70 for _, target := range vv { 71 if target == "-" { 72 parsed = append(parsed, 0) 73 continue 74 } 75 76 if numericValue, err = strconv.ParseUint(target, 10, 64); err != nil { 77 return 78 } 79 parsed = append(parsed, numericValue) 80 } 81 82 stat = &IOCountersStat{ 83 Name: strings.Trim(columns[0], "*"), // remove the * that sometimes is on right on interface 84 PacketsRecv: parsed[0], 85 Errin: parsed[1], 86 BytesRecv: parsed[2], 87 PacketsSent: parsed[3], 88 Errout: parsed[4], 89 BytesSent: parsed[5], 90 } 91 if len(parsed) == 7 { 92 stat.Dropout = parsed[6] 93 } 94 return 95 } 96 97 type netstatInterface struct { 98 linkID *uint 99 stat *IOCountersStat 100 } 101 102 func parseNetstatOutput(output string) ([]netstatInterface, error) { 103 var ( 104 err error 105 lines = strings.Split(strings.Trim(output, endOfLine), endOfLine) 106 ) 107 108 // number of interfaces is number of lines less one for the header 109 numberInterfaces := len(lines) - 1 110 111 interfaces := make([]netstatInterface, numberInterfaces) 112 // no output beside header 113 if numberInterfaces == 0 { 114 return interfaces, nil 115 } 116 117 for index := 0; index < numberInterfaces; index++ { 118 nsIface := netstatInterface{} 119 if nsIface.stat, nsIface.linkID, err = parseNetstatLine(lines[index+1]); err != nil { 120 return nil, err 121 } 122 interfaces[index] = nsIface 123 } 124 return interfaces, nil 125 } 126 127 // map that hold the name of a network interface and the number of usage 128 type mapInterfaceNameUsage map[string]uint 129 130 func newMapInterfaceNameUsage(ifaces []netstatInterface) mapInterfaceNameUsage { 131 output := make(mapInterfaceNameUsage) 132 for index := range ifaces { 133 if ifaces[index].linkID != nil { 134 ifaceName := ifaces[index].stat.Name 135 usage, ok := output[ifaceName] 136 if ok { 137 output[ifaceName] = usage + 1 138 } else { 139 output[ifaceName] = 1 140 } 141 } 142 } 143 return output 144 } 145 146 func (min mapInterfaceNameUsage) isTruncated() bool { 147 for _, usage := range min { 148 if usage > 1 { 149 return true 150 } 151 } 152 return false 153 } 154 155 func (min mapInterfaceNameUsage) notTruncated() []string { 156 output := make([]string, 0) 157 for ifaceName, usage := range min { 158 if usage == 1 { 159 output = append(output, ifaceName) 160 } 161 } 162 return output 163 } 164 165 // example of `netstat -ibdnW` output on yosemite 166 // Name Mtu Network Address Ipkts Ierrs Ibytes Opkts Oerrs Obytes Coll Drop 167 // lo0 16384 <Link#1> 869107 0 169411755 869107 0 169411755 0 0 168 // lo0 16384 ::1/128 ::1 869107 - 169411755 869107 - 169411755 - - 169 // lo0 16384 127 127.0.0.1 869107 - 169411755 869107 - 169411755 - - 170 func IOCounters(pernic bool) ([]IOCountersStat, error) { 171 return IOCountersWithContext(context.Background(), pernic) 172 } 173 174 func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, error) { 175 var ( 176 ret []IOCountersStat 177 retIndex int 178 ) 179 180 netstat, err := exec.LookPath("netstat") 181 if err != nil { 182 return nil, err 183 } 184 185 // try to get all interface metrics, and hope there won't be any truncated 186 out, err := invoke.CommandWithContext(ctx, netstat, "-ibdnW") 187 if err != nil { 188 return nil, err 189 } 190 191 nsInterfaces, err := parseNetstatOutput(string(out)) 192 if err != nil { 193 return nil, err 194 } 195 196 ifaceUsage := newMapInterfaceNameUsage(nsInterfaces) 197 notTruncated := ifaceUsage.notTruncated() 198 ret = make([]IOCountersStat, len(notTruncated)) 199 200 if !ifaceUsage.isTruncated() { 201 // no truncated interface name, return stats of all interface with <Link#...> 202 for index := range nsInterfaces { 203 if nsInterfaces[index].linkID != nil { 204 ret[retIndex] = *nsInterfaces[index].stat 205 retIndex++ 206 } 207 } 208 } else { 209 // duplicated interface, list all interfaces 210 if out, err = invoke.CommandWithContext(ctx, "ifconfig", "-l"); err != nil { 211 return nil, err 212 } 213 interfaceNames := strings.Fields(strings.TrimRight(string(out), endOfLine)) 214 215 // for each of the interface name, run netstat if we don't have any stats yet 216 for _, interfaceName := range interfaceNames { 217 truncated := true 218 for index := range nsInterfaces { 219 if nsInterfaces[index].linkID != nil && nsInterfaces[index].stat.Name == interfaceName { 220 // handle the non truncated name to avoid execute netstat for them again 221 ret[retIndex] = *nsInterfaces[index].stat 222 retIndex++ 223 truncated = false 224 break 225 } 226 } 227 if truncated { 228 // run netstat with -I$ifacename 229 if out, err = invoke.CommandWithContext(ctx, netstat, "-ibdnWI"+interfaceName); err != nil { 230 return nil, err 231 } 232 parsedIfaces, err := parseNetstatOutput(string(out)) 233 if err != nil { 234 return nil, err 235 } 236 if len(parsedIfaces) == 0 { 237 // interface had been removed since `ifconfig -l` had been executed 238 continue 239 } 240 for index := range parsedIfaces { 241 if parsedIfaces[index].linkID != nil { 242 ret = append(ret, *parsedIfaces[index].stat) 243 break 244 } 245 } 246 } 247 } 248 } 249 250 if pernic == false { 251 return getIOCountersAll(ret) 252 } 253 return ret, nil 254 } 255 256 // IOCountersByFile exists just for compatibility with Linux. 257 func IOCountersByFile(pernic bool, filename string) ([]IOCountersStat, error) { 258 return IOCountersByFileWithContext(context.Background(), pernic, filename) 259 } 260 261 func IOCountersByFileWithContext(ctx context.Context, pernic bool, filename string) ([]IOCountersStat, error) { 262 return IOCounters(pernic) 263 } 264 265 func FilterCounters() ([]FilterStat, error) { 266 return FilterCountersWithContext(context.Background()) 267 } 268 269 func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) { 270 return nil, common.ErrNotImplementedError 271 } 272 273 func ConntrackStats(percpu bool) ([]ConntrackStat, error) { 274 return ConntrackStatsWithContext(context.Background(), percpu) 275 } 276 277 func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) { 278 return nil, common.ErrNotImplementedError 279 } 280 281 // NetProtoCounters returns network statistics for the entire system 282 // If protocols is empty then all protocols are returned, otherwise 283 // just the protocols in the list are returned. 284 // Not Implemented for Darwin 285 func ProtoCounters(protocols []string) ([]ProtoCountersStat, error) { 286 return ProtoCountersWithContext(context.Background(), protocols) 287 } 288 289 func ProtoCountersWithContext(ctx context.Context, protocols []string) ([]ProtoCountersStat, error) { 290 return nil, common.ErrNotImplementedError 291 }