9
9
"os"
10
10
"path"
11
11
"runtime"
12
+ "slices"
12
13
"strings"
14
+ "sync"
13
15
"time"
14
16
15
17
"github.com/google/uuid"
@@ -27,6 +29,14 @@ import (
27
29
28
30
var log = logging .Logger ("telemetry" )
29
31
32
+ // Caching for virtualization detection - these values never change during process lifetime
33
+ var (
34
+ containerDetectionOnce sync.Once
35
+ vmDetectionOnce sync.Once
36
+ isContainerCached bool
37
+ isVMCached bool
38
+ )
39
+
30
40
const (
31
41
modeEnvVar = "IPFS_TELEMETRY"
32
42
uuidFilename = "telemetry_uuid"
@@ -476,45 +486,135 @@ func (p *telemetryPlugin) collectPlatformInfo() {
476
486
}
477
487
478
488
func isRunningInContainer () bool {
479
- // Check for Docker container
489
+ containerDetectionOnce .Do (func () {
490
+ isContainerCached = detectContainer ()
491
+ })
492
+ return isContainerCached
493
+ }
494
+
495
+ func detectContainer () bool {
496
+ // Docker creates /.dockerenv inside containers
480
497
if _ , err := os .Stat ("/.dockerenv" ); err == nil {
481
498
return true
482
499
}
483
500
484
- // Check cgroup for container
485
- content , err := os .ReadFile ("/proc/self/cgroup" )
486
- if err == nil {
487
- if strings .Contains (string (content ), "docker" ) || strings .Contains (string (content ), "lxc" ) || strings .Contains (string (content ), "/kubepods" ) {
488
- return true
489
- }
501
+ // Kubernetes mounts service account tokens inside pods
502
+ if _ , err := os .Stat ("/var/run/secrets/kubernetes.io" ); err == nil {
503
+ return true
490
504
}
491
505
492
- content , err = os .ReadFile ("/proc/self/mountinfo" )
493
- if err == nil {
506
+ // systemd-nspawn creates this file inside containers
507
+ if _ , err := os .Stat ("/run/systemd/container" ); err == nil {
508
+ return true
509
+ }
510
+
511
+ // Check if our process is running inside a container cgroup
512
+ // Look for container-specific patterns in the cgroup path after "::/"
513
+ if content , err := os .ReadFile ("/proc/self/cgroup" ); err == nil {
494
514
for line := range strings .Lines (string (content )) {
495
- if strings .Contains (line , "overlay" ) && strings .Contains (line , "/var/lib/containers/storage/overlay" ) {
515
+ // cgroup lines format: "ID:subsystem:/path"
516
+ // We want to check the path part after the last ":"
517
+ parts := strings .SplitN (line , ":" , 3 )
518
+ if len (parts ) == 3 {
519
+ cgroupPath := parts [2 ]
520
+ // Check for container-specific paths
521
+ containerIndicators := []string {
522
+ "/docker/" , // Docker containers
523
+ "/containerd/" , // containerd runtime
524
+ "/cri-o/" , // CRI-O runtime
525
+ "/lxc/" , // LXC containers
526
+ "/podman/" , // Podman containers
527
+ "/kubepods/" , // Kubernetes pods
528
+ }
529
+ for _ , indicator := range containerIndicators {
530
+ if strings .Contains (cgroupPath , indicator ) {
531
+ return true
532
+ }
533
+ }
534
+ }
535
+ }
536
+ }
537
+
538
+ // WSL is technically a container-like environment
539
+ if runtime .GOOS == "linux" {
540
+ if content , err := os .ReadFile ("/proc/sys/kernel/osrelease" ); err == nil {
541
+ osrelease := strings .ToLower (string (content ))
542
+ if strings .Contains (osrelease , "microsoft" ) || strings .Contains (osrelease , "wsl" ) {
496
543
return true
497
544
}
498
545
}
499
546
}
500
547
501
- // Also check for systemd-nspawn
502
- if _ , err := os .Stat ("/run/systemd/container" ); err == nil {
503
- return true
548
+ // LXC sets container environment variable
549
+ if content , err := os .ReadFile ("/proc/1/environ" ); err == nil {
550
+ if strings .Contains (string (content ), "container=lxc" ) {
551
+ return true
552
+ }
553
+ }
554
+
555
+ // Additional check: In containers, PID 1 is often not systemd/init
556
+ if content , err := os .ReadFile ("/proc/1/comm" ); err == nil {
557
+ pid1 := strings .TrimSpace (string (content ))
558
+ // Common container init processes
559
+ containerInits := []string {"tini" , "dumb-init" , "s6-svscan" , "runit" }
560
+ if slices .Contains (containerInits , pid1 ) {
561
+ return true
562
+ }
504
563
}
505
564
506
565
return false
507
566
}
508
567
509
568
func isRunningInVM () bool {
510
- // Check for VM
511
- if _ , err := os .Stat ("/sys/hypervisor/uuid" ); err == nil {
512
- return true
569
+ vmDetectionOnce .Do (func () {
570
+ isVMCached = detectVM ()
571
+ })
572
+ return isVMCached
573
+ }
574
+
575
+ func detectVM () bool {
576
+ // Check for VM-specific files and drivers that only exist inside VMs
577
+ vmIndicators := []string {
578
+ "/proc/xen" , // Xen hypervisor guest
579
+ "/sys/hypervisor/uuid" , // KVM/Xen hypervisor guest
580
+ "/dev/vboxguest" , // VirtualBox guest additions
581
+ "/sys/module/vmw_balloon" , // VMware balloon driver (guest only)
582
+ "/sys/module/hv_vmbus" , // Hyper-V VM bus driver (guest only)
513
583
}
514
584
515
- // Check for other VM indicators
516
- if _ , err := os .Stat ("/dev/virt-0" ); err == nil {
517
- return true
585
+ for _ , path := range vmIndicators {
586
+ if _ , err := os .Stat (path ); err == nil {
587
+ return true
588
+ }
589
+ }
590
+
591
+ // Check DMI for VM vendors - these strings only appear inside VMs
592
+ // DMI (Desktop Management Interface) is populated by the hypervisor
593
+ dmiFiles := map [string ][]string {
594
+ "/sys/class/dmi/id/sys_vendor" : {
595
+ "qemu" , "kvm" , "vmware" , "virtualbox" , "xen" ,
596
+ "parallels" , // Parallels Desktop
597
+ // Note: Removed "microsoft corporation" as it can match Surface devices
598
+ },
599
+ "/sys/class/dmi/id/product_name" : {
600
+ "virtualbox" , "vmware" , "kvm" , "qemu" ,
601
+ "hvm domu" , // Xen HVM guest
602
+ // Note: Removed generic "virtual machine" to avoid false positives
603
+ },
604
+ "/sys/class/dmi/id/chassis_vendor" : {
605
+ "qemu" , "oracle" , // Oracle for VirtualBox
606
+ },
607
+ }
608
+
609
+ for path , signatures := range dmiFiles {
610
+ if content , err := os .ReadFile (path ); err == nil {
611
+ contentStr := strings .ToLower (strings .TrimSpace (string (content )))
612
+ for _ , sig := range signatures {
613
+ if strings .Contains (contentStr , sig ) {
614
+ return true
615
+ }
616
+ }
617
+ }
518
618
}
519
619
520
620
return false
0 commit comments