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