package main import ( "bufio" "encoding/json" "fmt" "os" "os/exec" "regexp" "strconv" "strings" ) // Color constants for terminal output const ( Red = "\033[0;31m" Green = "\033[0;32m" Yellow = "\033[1;33m" Blue = "\033[0;34m" Cyan = "\033[0;36m" Bold = "\033[1m" NC = "\033[0m" // No Color ) // ProcessInfo holds information about a process using a port type ProcessInfo struct { PID int ProcessName string Protocol string DockerInfo string } // DockerContainer represents a Docker container type DockerContainer struct { Name string Image string Ports []PortMapping Network string } // PortMapping represents a port mapping type PortMapping struct { ContainerPort int HostPort int Protocol string IPv6 bool } func main() { if len(os.Args) < 2 { showUsage() os.Exit(1) } arg := os.Args[1] switch arg { case "--help", "-h": showHelp() case "--list", "-l": listDockerServices() default: port, err := strconv.Atoi(arg) if err != nil || port < 1 || port > 65535 { fmt.Printf("%sError:%s Invalid port number. Must be between 1 and 65535.\n", Red, NC) os.Exit(1) } checkPort(port) } } func showUsage() { fmt.Printf("%sUsage:%s inuse \n", Red, NC) fmt.Printf("%s inuse --list%s\n", Yellow, NC) fmt.Printf("%s inuse --help%s\n", Yellow, NC) fmt.Printf("%sExample:%s inuse 80\n", Yellow, NC) fmt.Printf("%s inuse --list%s\n", Yellow, NC) } func showHelp() { fmt.Printf("%s%sinuse - Check if a port is in use%s\n\n", Cyan, Bold, NC) fmt.Printf("%sUSAGE:%s\n", Bold, NC) fmt.Printf(" inuse Check if a specific port is in use\n") fmt.Printf(" inuse --list, -l List all Docker services with listening ports\n") fmt.Printf(" inuse --help, -h Show this help message\n\n") fmt.Printf("%sEXAMPLES:%s\n", Bold, NC) fmt.Printf(" %sinuse 80%s Check if port 80 is in use\n", Green, NC) fmt.Printf(" %sinuse 3000%s Check if port 3000 is in use\n", Green, NC) fmt.Printf(" %sinuse --list%s Show all Docker services with ports\n\n", Green, NC) fmt.Printf("%sDESCRIPTION:%s\n", Bold, NC) fmt.Printf(" The inuse function checks if a specific port is in use and identifies\n") fmt.Printf(" the process using it. It can detect regular processes, Docker containers\n") fmt.Printf(" with published ports, and containers using host networking.\n\n") fmt.Printf("%sOUTPUT:%s\n", Bold, NC) fmt.Printf(" %sāœ“%s Port is in use - shows process name, PID, and Docker info if applicable\n", Green, NC) fmt.Printf(" %sāœ—%s Port is free\n", Red, NC) fmt.Printf(" %s⚠%s Port is in use but process cannot be identified\n", Yellow, NC) } func listDockerServices() { if !isDockerAvailable() { fmt.Printf("%sError:%s Docker is not available\n", Red, NC) os.Exit(1) } fmt.Printf("%s%sDocker Services with Listening Ports:%s\n\n", Cyan, Bold, NC) containers := getRunningContainers() if len(containers) == 0 { fmt.Printf("%sNo running Docker containers found%s\n", Yellow, NC) return } foundServices := false for _, container := range containers { if len(container.Ports) > 0 { cleanImage := cleanImageName(container.Image) fmt.Printf("%sšŸ“¦ %s%s%s %s(%s)%s\n", Green, Bold, container.Name, NC, Cyan, cleanImage, NC) for _, port := range container.Ports { ipv6Marker := "" if port.IPv6 { ipv6Marker = " [IPv6]" } fmt.Printf("%s ā”œā”€ Port %s%d%s%s → %d (%s)%s%s\n", Cyan, Bold, port.HostPort, NC, Cyan, port.ContainerPort, port.Protocol, ipv6Marker, NC) } fmt.Println() foundServices = true } } // Check for host networking containers hostContainers := getHostNetworkingContainers() if len(hostContainers) > 0 { fmt.Printf("%s%sHost Networking Containers:%s\n", Yellow, Bold, NC) for _, container := range hostContainers { cleanImage := cleanImageName(container.Image) fmt.Printf("%s🌐 %s%s%s %s(%s)%s %s- uses host networking%s\n", Yellow, Bold, container.Name, NC, Cyan, cleanImage, NC, Yellow, NC) } fmt.Println() foundServices = true } if !foundServices { fmt.Printf("%sNo Docker services with exposed ports found%s\n", Yellow, NC) } } func checkPort(port int) { // Check if port is in use first if !isPortInUse(port) { fmt.Printf("%sāœ— Port %d is FREE%s\n", Red, port, NC) os.Exit(1) } // Port is in use, now find what's using it process := findProcessUsingPort(port) if process != nil { dockerInfo := "" if process.DockerInfo != "" { dockerInfo = " " + process.DockerInfo } fmt.Printf("%sāœ“ Port %d (%s) in use by %s%s%s %sas PID %s%d%s%s\n", Green, port, process.Protocol, Bold, process.ProcessName, NC, Green, Bold, process.PID, NC, dockerInfo) return } // Check if it's a Docker container containerInfo := findDockerContainerUsingPort(port) if containerInfo != "" { fmt.Printf("%sāœ“ Port %d in use by Docker container %s\n", Green, port, containerInfo) return } // If we still haven't found the process, check for host networking containers more thoroughly hostNetworkProcess := findHostNetworkingProcess(port) if hostNetworkProcess != "" { fmt.Printf("%sāœ“ Port %d likely in use by %s\n", Green, port, hostNetworkProcess) return } // If we still haven't found the process fmt.Printf("%s⚠ Port %d is in use but unable to identify the process%s\n", Yellow, port, NC) if isDockerAvailable() { hostContainers := getHostNetworkingContainers() if len(hostContainers) > 0 { fmt.Printf("%s Note: Found Docker containers using host networking:%s\n", Cyan, NC) for _, container := range hostContainers { cleanImage := cleanImageName(container.Image) fmt.Printf("%s - %s (%s)%s\n", Cyan, container.Name, cleanImage, NC) } fmt.Printf("%s These containers share the host's network, so one of them might be using this port%s\n", Cyan, NC) } else { fmt.Printf("%s This might be due to insufficient permissions or the process being in a different namespace%s\n", Cyan, NC) } } else { fmt.Printf("%s This might be due to insufficient permissions or the process being in a different namespace%s\n", Cyan, NC) } } func isPortInUse(port int) bool { // Try ss first if isCommandAvailable("ss") { cmd := exec.Command("ss", "-tulpn") output, err := cmd.Output() if err == nil { portPattern := fmt.Sprintf(":%d ", port) return strings.Contains(string(output), portPattern) } } // Try netstat as fallback if isCommandAvailable("netstat") { cmd := exec.Command("netstat", "-tulpn") output, err := cmd.Output() if err == nil { portPattern := fmt.Sprintf(":%d ", port) return strings.Contains(string(output), portPattern) } } return false } func findProcessUsingPort(port int) *ProcessInfo { // Method 1: Try netstat if process := tryNetstat(port); process != nil { return process } // Method 2: Try ss if process := trySS(port); process != nil { return process } // Method 3: Try lsof if process := tryLsof(port); process != nil { return process } // Method 4: Try fuser if process := tryFuser(port); process != nil { return process } return nil } func tryNetstat(port int) *ProcessInfo { if !isCommandAvailable("netstat") { return nil } cmd := exec.Command("netstat", "-tulpn") output, err := cmd.Output() if err != nil { // Try with sudo if available if isCommandAvailable("sudo") { cmd = exec.Command("sudo", "netstat", "-tulpn") output, err = cmd.Output() if err != nil { return nil } } else { return nil } } scanner := bufio.NewScanner(strings.NewReader(string(output))) portPattern := fmt.Sprintf(":%d ", port) for scanner.Scan() { line := scanner.Text() if strings.Contains(line, portPattern) { fields := strings.Fields(line) if len(fields) >= 7 { pidProcess := fields[6] parts := strings.Split(pidProcess, "/") if len(parts) >= 2 { if pid, err := strconv.Atoi(parts[0]); err == nil { processName := parts[1] protocol := fields[0] dockerInfo := getDockerInfo(pid, processName, port) return &ProcessInfo{ PID: pid, ProcessName: processName, Protocol: protocol, DockerInfo: dockerInfo, } } } } } } return nil } func trySS(port int) *ProcessInfo { if !isCommandAvailable("ss") { return nil } cmd := exec.Command("ss", "-tulpn") output, err := cmd.Output() if err != nil { // Try with sudo if available if isCommandAvailable("sudo") { cmd = exec.Command("sudo", "ss", "-tulpn") output, err = cmd.Output() if err != nil { return nil } } else { return nil } } scanner := bufio.NewScanner(strings.NewReader(string(output))) portPattern := fmt.Sprintf(":%d ", port) pidRegex := regexp.MustCompile(`pid=(\d+)`) for scanner.Scan() { line := scanner.Text() if strings.Contains(line, portPattern) { matches := pidRegex.FindStringSubmatch(line) if len(matches) >= 2 { if pid, err := strconv.Atoi(matches[1]); err == nil { processName := getProcessName(pid) if processName != "" { fields := strings.Fields(line) protocol := "" if len(fields) > 0 { protocol = fields[0] } dockerInfo := getDockerInfo(pid, processName, port) return &ProcessInfo{ PID: pid, ProcessName: processName, Protocol: protocol, DockerInfo: dockerInfo, } } } } } } return nil } func tryLsof(port int) *ProcessInfo { if !isCommandAvailable("lsof") { return nil } cmd := exec.Command("lsof", "-i", fmt.Sprintf(":%d", port), "-n", "-P") output, err := cmd.Output() if err != nil { // Try with sudo if available if isCommandAvailable("sudo") { cmd = exec.Command("sudo", "lsof", "-i", fmt.Sprintf(":%d", port), "-n", "-P") output, err = cmd.Output() if err != nil { return nil } } else { return nil } } scanner := bufio.NewScanner(strings.NewReader(string(output))) for scanner.Scan() { line := scanner.Text() if strings.Contains(line, "LISTEN") { fields := strings.Fields(line) if len(fields) >= 2 { processName := fields[0] if pid, err := strconv.Atoi(fields[1]); err == nil { dockerInfo := getDockerInfo(pid, processName, port) return &ProcessInfo{ PID: pid, ProcessName: processName, Protocol: "tcp", DockerInfo: dockerInfo, } } } } } return nil } func tryFuser(port int) *ProcessInfo { if !isCommandAvailable("fuser") { return nil } cmd := exec.Command("fuser", fmt.Sprintf("%d/tcp", port)) output, err := cmd.Output() if err != nil { return nil } pids := strings.Fields(string(output)) for _, pidStr := range pids { if pid, err := strconv.Atoi(strings.TrimSpace(pidStr)); err == nil { processName := getProcessName(pid) if processName != "" { return &ProcessInfo{ PID: pid, ProcessName: processName, Protocol: "tcp", DockerInfo: "", } } } } return nil } func getProcessName(pid int) string { cmd := exec.Command("ps", "-p", strconv.Itoa(pid), "-o", "comm=") output, err := cmd.Output() if err != nil { return "" } return strings.TrimSpace(string(output)) } func getDockerInfo(pid int, processName string, port int) string { if !isDockerAvailable() { return "" } // Check if it's docker-proxy (handle truncated names like "docker-pr") if processName == "docker-proxy" || strings.HasPrefix(processName, "docker-pr") { containerName := getContainerByPublishedPort(port) if containerName != "" { image := getContainerImage(containerName) cleanImage := cleanImageName(image) return fmt.Sprintf("%s(Docker: %s, image: %s)%s", Cyan, containerName, cleanImage, NC) } return fmt.Sprintf("%s(Docker proxy)%s", Cyan, NC) } // Check if process is in a Docker container using cgroup containerInfo := getContainerByPID(pid) if containerInfo != "" { return fmt.Sprintf("%s(Docker: %s)%s", Cyan, containerInfo, NC) } // Check if this process might be in a host networking container hostContainer := checkHostNetworkingContainer(pid, processName) if hostContainer != "" { return fmt.Sprintf("%s(Docker host network: %s)%s", Cyan, hostContainer, NC) } return "" } func getContainerByPID(pid int) string { cgroupPath := fmt.Sprintf("/proc/%d/cgroup", pid) file, err := os.Open(cgroupPath) if err != nil { return "" } defer file.Close() scanner := bufio.NewScanner(file) containerIDRegex := regexp.MustCompile(`[a-f0-9]{64}`) for scanner.Scan() { line := scanner.Text() if strings.Contains(line, "docker") { matches := containerIDRegex.FindStringSubmatch(line) if len(matches) > 0 { containerID := matches[0] containerName := getContainerNameByID(containerID) if containerName != "" { return containerName } return containerID[:12] } } } return "" } func findDockerContainerUsingPort(port int) string { if !isDockerAvailable() { return "" } // Check for containers with published ports cmd := exec.Command("docker", "ps", "--format", "{{.Names}}", "--filter", fmt.Sprintf("publish=%d", port)) output, err := cmd.Output() if err != nil { return "" } containerName := strings.TrimSpace(string(output)) if containerName != "" { image := getContainerImage(containerName) cleanImage := cleanImageName(image) return fmt.Sprintf("%s%s%s %s(published port, image: %s)%s", Bold, containerName, NC, Cyan, cleanImage, NC) } return "" } func isDockerAvailable() bool { return isCommandAvailable("docker") } func isCommandAvailable(command string) bool { _, err := exec.LookPath(command) return err == nil } func getRunningContainers() []DockerContainer { if !isDockerAvailable() { return nil } cmd := exec.Command("docker", "ps", "--format", "{{.Names}}") output, err := cmd.Output() if err != nil { return nil } var containers []DockerContainer scanner := bufio.NewScanner(strings.NewReader(string(output))) for scanner.Scan() { containerName := strings.TrimSpace(scanner.Text()) if containerName != "" { container := DockerContainer{ Name: containerName, Image: getContainerImage(containerName), Ports: getContainerPorts(containerName), } containers = append(containers, container) } } return containers } func getHostNetworkingContainers() []DockerContainer { if !isDockerAvailable() { return nil } cmd := exec.Command("docker", "ps", "--format", "{{.Names}}", "--filter", "network=host") output, err := cmd.Output() if err != nil { return nil } var containers []DockerContainer scanner := bufio.NewScanner(strings.NewReader(string(output))) for scanner.Scan() { containerName := strings.TrimSpace(scanner.Text()) if containerName != "" { container := DockerContainer{ Name: containerName, Image: getContainerImage(containerName), Network: "host", } containers = append(containers, container) } } return containers } func getContainerImage(containerName string) string { cmd := exec.Command("docker", "inspect", containerName) output, err := cmd.Output() if err != nil { return "" } var inspectData []map[string]interface{} if err := json.Unmarshal(output, &inspectData); err != nil { return "" } if len(inspectData) > 0 { if image, ok := inspectData[0]["Config"].(map[string]interface{})["Image"].(string); ok { return image } } return "" } func getContainerPorts(containerName string) []PortMapping { cmd := exec.Command("docker", "port", containerName) output, err := cmd.Output() if err != nil { return nil } var ports []PortMapping scanner := bufio.NewScanner(strings.NewReader(string(output))) portRegex := regexp.MustCompile(`(\d+)/(tcp|udp) -> 0\.0\.0\.0:(\d+)`) ipv6PortRegex := regexp.MustCompile(`(\d+)/(tcp|udp) -> \[::\]:(\d+)`) for scanner.Scan() { line := scanner.Text() // Check for IPv4 if matches := portRegex.FindStringSubmatch(line); len(matches) >= 4 { containerPort, _ := strconv.Atoi(matches[1]) protocol := matches[2] hostPort, _ := strconv.Atoi(matches[3]) ports = append(ports, PortMapping{ ContainerPort: containerPort, HostPort: hostPort, Protocol: protocol, IPv6: false, }) } // Check for IPv6 if matches := ipv6PortRegex.FindStringSubmatch(line); len(matches) >= 4 { containerPort, _ := strconv.Atoi(matches[1]) protocol := matches[2] hostPort, _ := strconv.Atoi(matches[3]) ports = append(ports, PortMapping{ ContainerPort: containerPort, HostPort: hostPort, Protocol: protocol, IPv6: true, }) } } return ports } func getContainerByPublishedPort(port int) string { cmd := exec.Command("docker", "ps", "--format", "{{.Names}}", "--filter", fmt.Sprintf("publish=%d", port)) output, err := cmd.Output() if err != nil { return "" } return strings.TrimSpace(string(output)) } func getContainerNameByID(containerID string) string { cmd := exec.Command("docker", "inspect", containerID) output, err := cmd.Output() if err != nil { return "" } var inspectData []map[string]interface{} if err := json.Unmarshal(output, &inspectData); err != nil { return "" } if len(inspectData) > 0 { if name, ok := inspectData[0]["Name"].(string); ok { return strings.TrimPrefix(name, "/") } } return "" } func cleanImageName(image string) string { // Remove SHA256 hashes shaRegex := regexp.MustCompile(`sha256:[a-f0-9]*`) cleaned := shaRegex.ReplaceAllString(image, "[image-hash]") // Remove registry prefixes, keep only the last part parts := strings.Split(cleaned, "/") if len(parts) > 0 { return parts[len(parts)-1] } return cleaned } func findHostNetworkingProcess(port int) string { if !isDockerAvailable() { return "" } // Get all host networking containers hostContainers := getHostNetworkingContainers() for _, container := range hostContainers { // Check if this container might be using the port if isContainerUsingPort(container.Name, port) { cleanImage := cleanImageName(container.Image) return fmt.Sprintf("%s%s%s %s(Docker host network: %s)%s", Bold, container.Name, NC, Cyan, cleanImage, NC) } } return "" } func isContainerUsingPort(containerName string, port int) bool { // Try to execute netstat inside the container to see if it's listening on the port cmd := exec.Command("docker", "exec", containerName, "sh", "-c", fmt.Sprintf("netstat -tlnp 2>/dev/null | grep ':%d ' || ss -tlnp 2>/dev/null | grep ':%d '", port, port)) output, err := cmd.Output() if err != nil { return false } return len(output) > 0 } func checkHostNetworkingContainer(pid int, processName string) string { if !isDockerAvailable() { return "" } // Get all host networking containers and check if any match this process hostContainers := getHostNetworkingContainers() for _, container := range hostContainers { // Try to find this process inside the container cmd := exec.Command("docker", "exec", container.Name, "sh", "-c", fmt.Sprintf("ps -o pid,comm | grep '%s' | grep -q '%d\\|%s'", processName, pid, processName)) err := cmd.Run() if err == nil { cleanImage := cleanImageName(container.Image) return fmt.Sprintf("%s (%s)", container.Name, cleanImage) } } return "" }