@@ -503,72 +503,139 @@ func TestInterfaceAddrsWithNetsh(t *testing.T) {
503
503
}
504
504
}
505
505
506
- func contains (needle string , haystack []string ) bool {
507
- for _ , v := range haystack {
508
- if v == needle {
509
- return true
506
+ // check that getmac exists as a powershell command, and that it
507
+ // speaks English.
508
+ func checkGetmac (t * testing.T ) {
509
+ out , err := runCmd ("getmac" , "/?" )
510
+ if err != nil {
511
+ if strings .Contains (err .Error (), "term 'getmac' is not recognized as the name of a cmdlet" ) {
512
+ t .Skipf ("getmac not available" )
510
513
}
514
+ t .Fatal (err )
515
+ }
516
+ if ! bytes .Contains (out , []byte ("network adapters on a system" )) {
517
+ t .Skipf ("skipping test on non-English system" )
511
518
}
512
- return false
513
519
}
514
520
515
- func TestInterfaceHardwareAddrWithWmic (t * testing.T ) {
521
+ func TestInterfaceHardwareAddrWithGetmac (t * testing.T ) {
522
+ if isWindowsXP (t ) {
523
+ t .Skip ("Windows XP does not have powershell command" )
524
+ }
525
+ checkGetmac (t )
526
+
516
527
ift , err := Interfaces ()
517
528
if err != nil {
518
529
t .Fatal (err )
519
530
}
520
- goMacToName := make (map [string ]string )
531
+ have := make (map [string ]string )
521
532
for _ , ifi := range ift {
522
533
if ifi .Flags & FlagLoopback != 0 {
523
534
// no MAC address for loopback interfaces
524
535
continue
525
536
}
526
- goMacToName [ifi .HardwareAddr . String () ] = ifi .Name
537
+ have [ifi .Name ] = ifi .HardwareAddr . String ()
527
538
}
528
539
529
- //wmic nic get MACAddress,NetConnectionID /format:csv
530
- //
531
- //Node,MACAddress,NetConnectionID
532
- //SERVER-2008R2-V,,
533
- //SERVER-2008R2-V,42:01:0A:F0:00:18,Local Area Connection
534
- //SERVER-2008R2-V,42:01:0A:F0:00:18,Duplicate Adapter
535
- //SERVER-2008R2-V,20:41:53:59:4E:FF,
536
- out , err := exec .Command ("wmic" , "nic" , "get" , "MACAddress,NetConnectionID" , "/format:csv" ).CombinedOutput ()
540
+ out , err := runCmd ("getmac" , "/fo" , "list" , "/v" )
537
541
if err != nil {
538
542
t .Fatal (err )
539
543
}
540
- winMacToNames := make (map [string ][]string )
544
+ // getmac output looks like:
545
+ //
546
+ //Connection Name: Local Area Connection
547
+ //Network Adapter: Intel Gigabit Network Connection
548
+ //Physical Address: XX-XX-XX-XX-XX-XX
549
+ //Transport Name: \Device\Tcpip_{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
550
+ //
551
+ //Connection Name: Wireless Network Connection
552
+ //Network Adapter: Wireles WLAN Card
553
+ //Physical Address: XX-XX-XX-XX-XX-XX
554
+ //Transport Name: Media disconnected
555
+ //
556
+ //Connection Name: Bluetooth Network Connection
557
+ //Network Adapter: Bluetooth Device (Personal Area Network)
558
+ //Physical Address: N/A
559
+ //Transport Name: Hardware not present
560
+ //
561
+ //Connection Name: VMware Network Adapter VMnet8
562
+ //Network Adapter: VMware Virtual Ethernet Adapter for VMnet8
563
+ //Physical Address: Disabled
564
+ //Transport Name: Disconnected
565
+ //
566
+ want := make (map [string ]string )
567
+ group := make (map [string ]string ) // name / values for single adapter
568
+ getValue := func (name string ) string {
569
+ value , found := group [name ]
570
+ if ! found {
571
+ t .Fatalf ("%q has no %q line in it" , group , name )
572
+ }
573
+ if value == "" {
574
+ t .Fatalf ("%q has empty %q value" , group , name )
575
+ }
576
+ return value
577
+ }
578
+ processGroup := func () {
579
+ if len (group ) == 0 {
580
+ return
581
+ }
582
+ tname := strings .ToLower (getValue ("Transport Name" ))
583
+ if tname == "n/a" {
584
+ // skip these
585
+ return
586
+ }
587
+ addr := strings .ToLower (getValue ("Physical Address" ))
588
+ if addr == "disabled" || addr == "n/a" {
589
+ // skip these
590
+ return
591
+ }
592
+ addr = strings .Replace (addr , "-" , ":" , - 1 )
593
+ cname := getValue ("Connection Name" )
594
+ want [cname ] = addr
595
+ group = make (map [string ]string )
596
+ }
541
597
lines := bytes .Split (out , []byte {'\r' , '\n' })
542
-
543
598
for _ , line := range lines {
544
- entry := strings .Split (string (line ), "," )
545
- if len (entry ) != 3 || entry [1 ] == "MACAddress" {
546
- // skip empty lines, header
599
+ if len (line ) == 0 {
600
+ processGroup ()
547
601
continue
548
602
}
549
-
550
- mac , name := strings .ToLower (entry [1 ]), strings .TrimSpace (entry [2 ])
551
- if mac == "" || name == "" {
552
- // skip non-physical devices
553
- continue
603
+ i := bytes .IndexByte (line , ':' )
604
+ if i == - 1 {
605
+ t .Fatalf ("line %q has no : in it" , line )
554
606
}
555
-
556
- winMacToNames [mac ] = append (winMacToNames [mac ], name )
607
+ group [string (line [:i ])] = string (bytes .TrimSpace (line [i + 1 :]))
557
608
}
609
+ processGroup ()
558
610
559
- if len (goMacToName ) != len (winMacToNames ) {
560
- t .Errorf ("go interface count (%d, %v) differs from wmic count (%d, %v)" , len (goMacToName ), goMacToName , len (winMacToNames ), winMacToNames )
611
+ dups := make (map [string ][]string )
612
+ for name , addr := range want {
613
+ if _ , ok := dups [addr ]; ! ok {
614
+ dups [addr ] = make ([]string , 0 )
615
+ }
616
+ dups [addr ] = append (dups [addr ], name )
561
617
}
562
618
563
- for mac , name := range goMacToName {
564
- // Windows appears to associate multiple names to a single MAC
565
- // Consider it a success if one of those names was found
566
- if cmdNames , ok := winMacToNames [mac ]; ok {
567
- if contains (name , cmdNames ) {
568
- continue
619
+ nextWant:
620
+ for name , wantAddr := range want {
621
+ if haveAddr , ok := have [name ]; ok {
622
+ if haveAddr != wantAddr {
623
+ t .Errorf ("unexpected MAC address for %q - %v, want %v" , name , haveAddr , wantAddr )
569
624
}
625
+ continue
570
626
}
571
-
572
- t .Errorf ("go found interface (name: %s, mac: %s) not found by wmic (%v)" , name , mac , winMacToNames )
627
+ // We could not find the interface in getmac output by name.
628
+ // But sometimes getmac lists many interface names
629
+ // for the same MAC address. If that is the case here,
630
+ // and we can match at least one of those names,
631
+ // let's ignore the other names.
632
+ if dupNames , ok := dups [wantAddr ]; ok && len (dupNames ) > 1 {
633
+ for _ , dupName := range dupNames {
634
+ if haveAddr , ok := have [dupName ]; ok && haveAddr == wantAddr {
635
+ continue nextWant
636
+ }
637
+ }
638
+ }
639
+ t .Errorf ("getmac lists %q, but it could not be found among Go interfaces %v" , name , have )
573
640
}
574
641
}
0 commit comments