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.
- Error Example:
- 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.
- Error Example:
- 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.
- Error Example:
- 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.
- Error Example:
- 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.
- Error Example:
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
- A deep dive into Go's net package
- Golang portping package
- Port (Computer Networking)
- Linux TCP and UDP Port numbers