Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ $ curl ifconfig.co/port/80
{
"ip": "127.0.0.1",
"port": 80,
"reachable": false
"reachable": false,
"status": "refused"
}
```

Expand Down
36 changes: 33 additions & 3 deletions http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package http

import (
"encoding/json"
"errors"
"fmt"
"html/template"
"io"
"log"
"os"
"path/filepath"
"strings"
"syscall"

"net/http/pprof"

Expand Down Expand Up @@ -57,10 +60,21 @@ type Response struct {
UserAgent *useragent.UserAgent `json:"user_agent,omitempty"`
}

type PortStatus string

const (
PortOpen PortStatus = "open" // TCP handshake completed
PortRefused PortStatus = "refused" // RST (nothing listening)
PortTimeout PortStatus = "timeout" // Packet dropped
PortUnreachable PortStatus = "unreachable" // Host/Network Unreachable
PortUnknown PortStatus = "unknown" // Unhandled error
)

type PortResponse struct {
IP net.IP `json:"ip"`
Port uint64 `json:"port"`
Reachable bool `json:"reachable"`
IP net.IP `json:"ip"`
Port uint64 `json:"port"`
Reachable bool `json:"reachable"`
Status PortStatus `json:"status"` // Strict enum
}

func New(db geo.Reader, cache *Cache, profile bool) *Server {
Expand Down Expand Up @@ -185,10 +199,26 @@ func (s *Server) newPortResponse(r *http.Request) (PortResponse, error) {
return PortResponse{Port: port}, err
}
err = s.LookupPort(ip, port)

status := PortUnknown
if err == nil {
status = PortOpen
} else {
switch {
case os.IsTimeout(err):
status = PortTimeout
case errors.Is(err, syscall.ECONNREFUSED):
status = PortRefused
case errors.Is(err, syscall.EHOSTUNREACH) || errors.Is(err, syscall.ENETUNREACH):
status = PortUnreachable
}
}

return PortResponse{
IP: ip,
Port: port,
Reachable: err == nil,
Status: status,
}, nil
}

Expand Down
6 changes: 3 additions & 3 deletions http/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,9 @@ func TestJSONHandlers(t *testing.T) {
{s.URL + "/port/foo", "{\n \"status\": 400,\n \"error\": \"invalid port: foo\"\n}", 400},
{s.URL + "/port/0", "{\n \"status\": 400,\n \"error\": \"invalid port: 0\"\n}", 400},
{s.URL + "/port/65537", "{\n \"status\": 400,\n \"error\": \"invalid port: 65537\"\n}", 400},
{s.URL + "/port/31337", "{\n \"ip\": \"127.0.0.1\",\n \"port\": 31337,\n \"reachable\": true\n}", 200},
{s.URL + "/port/80", "{\n \"ip\": \"127.0.0.1\",\n \"port\": 80,\n \"reachable\": true\n}", 200}, // checking that our test server is reachable on port 80
{s.URL + "/port/80?ip=1.3.3.7", "{\n \"ip\": \"127.0.0.1\",\n \"port\": 80,\n \"reachable\": true\n}", 200}, // ensuring that the "ip" parameter is not usable to check remote host ports
{s.URL + "/port/31337", "{\n \"ip\": \"127.0.0.1\",\n \"port\": 31337,\n \"reachable\": true,\n \"status\": \"open\"\n}", 200},
{s.URL + "/port/80", "{\n \"ip\": \"127.0.0.1\",\n \"port\": 80,\n \"reachable\": true,\n \"status\": \"open\"\n}", 200}, // checking that our test server is reachable on port 80
{s.URL + "/port/80?ip=1.3.3.7", "{\n \"ip\": \"127.0.0.1\",\n \"port\": 80,\n \"reachable\": true,\n \"status\": \"open\"\n}", 200}, // ensuring that the "ip" parameter is not usable to check remote host ports
{s.URL + "/foo", "{\n \"status\": 404,\n \"error\": \"404 page not found\"\n}", 404},
{s.URL + "/health", `{"status":"OK"}`, 200},
}
Expand Down