github.com/gofiber/fiber/v2@v2.47.0/internal/gopsutil/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/gofiber/fiber/v2/internal/gopsutil/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 ifconfig, err := exec.LookPath("ifconfig") 211 if err != nil { 212 return nil, err 213 } 214 if out, err = invoke.CommandWithContext(ctx, ifconfig, "-l"); err != nil { 215 return nil, err 216 } 217 interfaceNames := strings.Fields(strings.TrimRight(string(out), endOfLine)) 218 219 // for each of the interface name, run netstat if we don't have any stats yet 220 for _, interfaceName := range interfaceNames { 221 truncated := true 222 for index := range nsInterfaces { 223 if nsInterfaces[index].linkID != nil && nsInterfaces[index].stat.Name == interfaceName { 224 // handle the non truncated name to avoid execute netstat for them again 225 ret[retIndex] = *nsInterfaces[index].stat 226 retIndex++ 227 truncated = false 228 break 229 } 230 } 231 if truncated { 232 // run netstat with -I$ifacename 233 if out, err = invoke.CommandWithContext(ctx, netstat, "-ibdnWI"+interfaceName); err != nil { 234 return nil, err 235 } 236 parsedIfaces, err := parseNetstatOutput(string(out)) 237 if err != nil { 238 return nil, err 239 } 240 if len(parsedIfaces) == 0 { 241 // interface had been removed since `ifconfig -l` had been executed 242 continue 243 } 244 for index := range parsedIfaces { 245 if parsedIfaces[index].linkID != nil { 246 ret = append(ret, *parsedIfaces[index].stat) 247 break 248 } 249 } 250 } 251 } 252 } 253 254 if !pernic { 255 return getIOCountersAll(ret) 256 } 257 return ret, nil 258 } 259 260 // NetIOCountersByFile is an method which is added just a compatibility for linux. 261 func IOCountersByFile(pernic bool, filename string) ([]IOCountersStat, error) { 262 return IOCountersByFileWithContext(context.Background(), pernic, filename) 263 } 264 265 func IOCountersByFileWithContext(ctx context.Context, pernic bool, filename string) ([]IOCountersStat, error) { 266 return IOCounters(pernic) 267 } 268 269 func FilterCounters() ([]FilterStat, error) { 270 return FilterCountersWithContext(context.Background()) 271 } 272 273 func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) { 274 return nil, errors.New("NetFilterCounters not implemented for darwin") 275 } 276 277 func ConntrackStats(percpu bool) ([]ConntrackStat, error) { 278 return ConntrackStatsWithContext(context.Background(), percpu) 279 } 280 281 func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) { 282 return nil, common.ErrNotImplementedError 283 } 284 285 // NetProtoCounters returns network statistics for the entire system 286 // If protocols is empty then all protocols are returned, otherwise 287 // just the protocols in the list are returned. 288 // Not Implemented for Darwin 289 func ProtoCounters(protocols []string) ([]ProtoCountersStat, error) { 290 return ProtoCountersWithContext(context.Background(), protocols) 291 } 292 293 func ProtoCountersWithContext(ctx context.Context, protocols []string) ([]ProtoCountersStat, error) { 294 return nil, errors.New("NetProtoCounters not implemented for darwin") 295 }