Golang Portping Magic Revealed: Beyond Basic Port Checks


GO

Brief Overview of portping

portping, developed by janosgyerik, is a specialized Golang library aimed at facilitating port accessibility checks on hosts. At its essence, it offers developers a straightforward way to 'ping' specific ports, allowing them to determine whether those ports are open and accepting connections. This package bridges the gap between complex network diagnostic tools and the simplicity desired by many developers.

In traditional network testing, while we have tools to ping IP addresses to verify if a host machine is up, portping takes it a step further. It delves into the specific 'doors' or 'entry points' (the ports) of that host, ensuring they are responsive. Whether you're setting up a new server, monitoring existing infrastructure, or troubleshooting network issues, portping provides invaluable insights, all while harnessing the power and simplicity of Golang.

 

Set up Environment

We need to create our project before we can use portping. I have created a directory ~/projects/portping as my project directory.

mkdir -p ~/projects/portping
cd ~/projects/portping

Next create your main.go file inside ~/projects/portping

package main

import "github.com/janosgyerik/portping"

func main() {}

Next initialize your directory

go mod init portping
go mod tidy

This will install the portping module

go: finding module for package github.com/janosgyerik/portping
go: found github.com/janosgyerik/portping in github.com/janosgyerik/portping v1.0.1

 

Syntax and Parameters of portping

The portping package offers a simplified approach to ping TCP ports. Let's break down its primary functions and the parameters they support:

 

Ping Function

func Ping(network, address string, timeout time.Duration) error
  • network: Specifies the type of network to use for the ping operation. For most applications, this will be "tcp", representing the TCP protocol.
  • address: A string that combines the hostname and port number in the format "hostname:port". For instance, "www.google.com:443".
  • timeout: The maximum amount of time the function will wait for a response. If there's no response within this period, the function will return an error.

The function returns an error if the ping operation is unsuccessful or nil if it is successful.

 

PingN Function

func PingN(network, address string, timeout time.Duration, count int, c chan<- error)
  • network: Just like the Ping function, this specifies the network type.
  • address: The combined hostname and port string.
  • timeout: Maximum waiting time for a response.
  • count: Number of times the ping operation should be repeated.
  • c: A Go channel that the function sends errors to, for each ping attempt. This allows for concurrent operations and capturing results asynchronously.

This function doesn't return a value directly. Instead, it sends the results of each ping attempt to the provided channel.

 

Basic Usage of portping

Now that we have setup our environment, so now we can start using portping in our examples.

Let's jump right in with a basic example. Say you want to check if port 80 (commonly used for HTTP) is open on a website:

package main

import (
	"fmt"
	"github.com/janosgyerik/portping"
	"time"
)

func main() {
	// Basic Ping Example
	host := "www.google.com"
	port := "443"
	address := host + ":" + port
	timeout := 10 * time.Second

	err := portping.Ping("tcp", address, timeout)
	if err == nil {
		fmt.Printf("Port %s on host %s is accessible!\n", port, host)
	} else {
		fmt.Printf("Port %s on host %s is not accessible. Error: %s\n", port, host, err)
	}

	// PingN Example
	count := 5
	errChan := make(chan error, count)

	portping.PingN("tcp", address, timeout, count, errChan)
	for i := 0; i < count; i++ {
		err := <-errChan
		if err == nil {
			fmt.Printf("[Attempt %d] Port %s on host %s is accessible!\n", i+1, port, host)
		} else {
			fmt.Printf("[Attempt %d] Port %s on host %s is not accessible. Error: %s\n", i+1, port, host, err)
		}
	}
}

This code provides a clear demonstration of how to use both the Ping and PingN functions from the portping package. It attempts a single connection with Ping and then multiple connections with PingN, displaying the results for each. Adjust the values as needed based on your requirements.

Output:

Port 443 on host www.google.com is accessible!
[Attempt 1] Port 443 on host www.google.com is accessible!
[Attempt 2] Port 443 on host www.google.com is accessible!
[Attempt 3] Port 443 on host www.google.com is accessible!
[Attempt 4] Port 443 on host www.google.com is accessible!
[Attempt 5] Port 443 on host www.google.com is accessible!

 

Advanced Usage of portping

1. Checking connectivity of Multiple Ports Sequentially

You might want to check multiple ports on a host in a sequence. To do this, you can loop through a list of ports and ping each one.

package main

import (
	"fmt"
	"github.com/janosgyerik/portping"
	"time"
)

func main() {
	host := "www.google.com"
	ports := []string{"80", "443", "8080", "3306"}
	timeout := 10 * time.Second

	for _, port := range ports {
		address := host + ":" + port
		err := portping.Ping("tcp", address, timeout)
		if err == nil {
			fmt.Printf("Port %s on host %s is accessible!\n", port, host)
		} else {
			fmt.Printf("Port %s on host %s is not accessible. Error: %s\n", port, host, err)
		}
	}
}

Output

Port 80 on host www.google.com is accessible!
Port 443 on host www.google.com is accessible!
Port 8080 on host www.google.com is not accessible. Error: dial tcp 216.58.211.228:8080: i/o timeout
Port 3306 on host www.google.com is not accessible. Error: dial tcp 216.58.211.228:3306: i/o timeout

 

2. Checking Connectivity of Multiple Ports in Parallel

To ping multiple ports in parallel, you can utilize Goroutines in Go. Here's an example of how you might do this for ports 80, 443, and 8080:

package main

import (
	"fmt"
	"github.com/janosgyerik/portping"
	"sync"
	"time"
)

func main() {
	timeout := 5 * time.Second
	ports := []int{80, 443, 8080}
	var wg sync.WaitGroup

	for _, port := range ports {
		wg.Add(1)
		go func(p int) {
			defer wg.Done()
			address := fmt.Sprintf("www.google.com:%d", p)
			err := portping.Ping("tcp", address, timeout)
			if err == nil {
				fmt.Printf("Port %d is accessible!\n", p)
			} else {
				fmt.Printf("Error with port %d: %s\n", p, err)
			}
		}(port)
	}

	wg.Wait()  // Wait for all Goroutines to finish
}

In this example, the sync.WaitGroup is used to ensure the main function doesn't exit until all Goroutines (parallel tasks) are complete. Each port is pinged concurrently, saving time compared to a sequential approach.

Output:

Port 80 is accessible!
Port 443 is accessible!
Error with port 8080: dial tcp 216.58.211.228:8080: i/o timeout

 

3. Checking the connectivity of range of Port

Here's how you can ping and check the connectivity of a range of ports on www.google.com:

// Pinging ports 80 and 443
for _, port := range []int{80, 443, 3306} {
    go func(p int) {
        address := fmt.Sprintf("www.google.com:%d", p)
        err := portping.Ping("tcp", address, 1*time.Second)
        if err == nil {
            fmt.Printf("Port %d is accessible!\n", p)
        } else {
            fmt.Printf("Port %d is not accessible. Error: %s\n", p, err)
        }
    }(port)
}
time.Sleep(5 * time.Second) // Give it some time for goroutines to complete. Better to use WaitGroup.

4. Handling Results from PingN

Pinging port 80 multiple times and analyzing the results:

count := 5  // number of pings
ch := make(chan error, count)
portping.PingN("tcp", "www.google.com:80", 1*time.Second, count, ch)

successfulPings := 0
for i := 0; i < count; i++ {
    if err := <-ch; err == nil {
        successfulPings++
    }
}
fmt.Printf("%d out of %d pings to port 80 were successful.\n", successfulPings, count)

5. Retrying Failed Pings

Retrying pings to port 3306 (which is not open on www.google.com):

retries := 3
var err error
address := "www.google.com:3306"
for i := 0; i < retries; i++ {
    err = portping.Ping("tcp", address, 1*time.Second)
    if err == nil {
        break
    }
}
if err != nil {
    fmt.Printf("Failed to ping %s after %d retries. Error: %s\n", address, retries, err)
}

6. Logging Time Taken for Each Ping

Logging how long it takes to get a response from port 80:

startTime := time.Now()
err := portping.Ping("tcp", "www.google.com:80", 5*time.Second)
elapsed := time.Since(startTime)
if err == nil {
    fmt.Printf("Port 80 is accessible! Time taken: %s\n", elapsed)
} else {
    fmt.Printf("Error pinging port 80: %s. Time taken: %s\n", err, elapsed)
}

 

Common Errors

Here are a list of possible common errors for which you can add additional handling:

  • Connection Refused:
    • Error Example: connect: connection refused
    • Meaning: The target host is reachable, but it actively refused the connection on the specified port. This often means that there's no application currently accepting connections on that port.
    • Potential Solutions: Check if the intended service is running and listening on the port.
  • No Route to Host:
    • Error Example: connect: no route to host
    • Meaning: Your system doesn't know how to route data to the specified address, or the host is down.
    • Potential Solutions: Ensure network connectivity and that the host IP is correct. If on a cloud service, ensure correct network configurations.
  • DNS Errors:
    • Error Example: dial tcp: lookup www.example.com on 169.254.169.254:53: no such host
    • Meaning: There was an issue resolving the domain name to an IP address.
    • Potential Solutions: Check DNS settings or use an IP address directly.
  • Timeout:
    • Error Example: dial tcp 93.184.216.34:80: i/o timeout
    • Meaning: A connection attempt was made, but there was no response within the set timeout duration.
    • Potential Solutions: Increase timeout duration or check if the remote host is overloaded or having issues.
  • Address Unreachable:
    • Error Example: dial tcp: address 93.184.216.340:80: unreachable
    • Meaning: The given IP address is not valid or not in the expected format.
    • Potential Solutions: Ensure the address you're trying to ping is correct.

 

Conclusion

Port scanning is essential for understanding network security and vulnerabilities. Using libraries like portping in Golang can make the process efficient and programmable. Whether you're developing security tools or just ensuring that your services are running correctly, understanding the intricacies of ports and how to check their accessibility is crucial.

 

Further Reading

 

Deepak Prasad

Deepak Prasad

He is the founder of GoLinuxCloud and brings over a decade of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels in various domains, from development to DevOps, Networking, and Security, ensuring robust and efficient solutions for diverse projects. You can connect with him on his LinkedIn profile.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to admin@golinuxcloud.com

Thank You for your support!!

Leave a Comment