├── .gitignore ├── go.mod ├── README.md └── portchecker.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor 3 | *~ 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nknorg/portchecker 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # portchecker 2 | 3 | Client side of 4 | [portchecker-server](https://github.com/nknorg/portchecker-server). 5 | -------------------------------------------------------------------------------- /portchecker.go: -------------------------------------------------------------------------------- 1 | package portchecker 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "encoding/base64" 7 | "encoding/json" 8 | "errors" 9 | "fmt" 10 | "net" 11 | "net/http" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | const ( 17 | NonceBytes = 32 18 | DefaultServerAddr = "" 19 | DefaultTimeout = 10 * time.Second 20 | ) 21 | 22 | type Config struct { 23 | ServerAddr string 24 | Timeout time.Duration 25 | } 26 | 27 | type Req struct { 28 | Protocol string `json:"protocol"` 29 | Port uint16 `json:"port"` 30 | Nonce string `json:"nonce"` 31 | } 32 | 33 | type Resp struct { 34 | Success bool `json:"success"` 35 | Error string `json:"error,omitempty"` 36 | } 37 | 38 | type checkError struct { 39 | reason error 40 | error error 41 | } 42 | 43 | func (ce *checkError) isEmpty() bool { 44 | return ce.reason == nil && ce.error == nil 45 | } 46 | 47 | type tcpConn net.TCPConn 48 | 49 | func (conn *tcpConn) ReadFrom(b []byte) (int, net.Addr, error) { 50 | n, err := (*net.TCPConn)(conn).Read(b) 51 | return n, nil, err 52 | } 53 | 54 | func (conn *tcpConn) WriteTo(b []byte, _ net.Addr) (int, error) { 55 | return (*net.TCPConn)(conn).Write(b) 56 | } 57 | 58 | func randomBytes(n int) []byte { 59 | b := make([]byte, n) 60 | rand.Read(b) 61 | return b 62 | } 63 | 64 | func handleConn(conn net.PacketConn, nonceSent string, deadline time.Time) *checkError { 65 | conn.SetDeadline(deadline) 66 | 67 | nonceReceived := make([]byte, len([]byte(nonceSent))) 68 | _, addr, err := conn.ReadFrom(nonceReceived) 69 | if err != nil { 70 | return &checkError{err, nil} 71 | } 72 | 73 | if nonceSent != string(nonceReceived) { 74 | return &checkError{nil, fmt.Errorf("nonce received (%s) is different from nonce sent (%s)", nonceReceived, nonceSent)} 75 | } 76 | 77 | bytesSent := randomBytes(NonceBytes) 78 | _, err = conn.WriteTo(bytesSent, addr) 79 | if err != nil { 80 | return &checkError{err, nil} 81 | } 82 | 83 | bytesReceived := make([]byte, NonceBytes) 84 | _, _, err = conn.ReadFrom(bytesReceived) 85 | if err != nil { 86 | return &checkError{err, nil} 87 | } 88 | 89 | if !bytes.Equal(bytesSent, bytesReceived) { 90 | return &checkError{nil, fmt.Errorf("bytes received (%x) is different from bytes sent (%x)", bytesReceived, bytesSent)} 91 | } 92 | 93 | return &checkError{nil, nil} 94 | } 95 | 96 | func remoteCheck(serverAddr, protocol string, port uint16, nonceSent string, timeout time.Duration) *checkError { 97 | req := &Req{ 98 | Protocol: protocol, 99 | Port: port, 100 | Nonce: nonceSent, 101 | } 102 | 103 | b, err := json.Marshal(req) 104 | if err != nil { 105 | return &checkError{nil, err} 106 | } 107 | 108 | client := &http.Client{ 109 | Timeout: timeout, 110 | } 111 | r, err := client.Post(serverAddr, "application/json", bytes.NewBuffer(b)) 112 | if err != nil { 113 | return &checkError{nil, err} 114 | } 115 | defer r.Body.Close() 116 | 117 | if r.StatusCode == http.StatusInternalServerError { 118 | return &checkError{nil, errors.New("server error")} 119 | } 120 | 121 | resp := &Resp{} 122 | err = json.NewDecoder(r.Body).Decode(resp) 123 | if err != nil { 124 | return &checkError{nil, err} 125 | } 126 | 127 | if r.StatusCode != http.StatusOK { 128 | return &checkError{nil, errors.New(resp.Error)} 129 | } 130 | 131 | if !resp.Success { 132 | return &checkError{errors.New(resp.Error), nil} 133 | } 134 | 135 | return &checkError{nil, nil} 136 | } 137 | 138 | func CheckPort(protocol string, port uint16, configs ...*Config) (bool, error, error) { 139 | serverAddr := DefaultServerAddr 140 | if len(configs) > 0 && len(configs[0].ServerAddr) > 0 { 141 | serverAddr = configs[0].ServerAddr 142 | } 143 | 144 | if len(serverAddr) == 0 { 145 | return false, nil, errors.New("ServerAddr should not be empty") 146 | } 147 | 148 | timeout := DefaultTimeout 149 | if len(configs) > 0 && configs[0].Timeout > 0 { 150 | timeout = configs[0].Timeout 151 | } 152 | 153 | deadline := time.Now().Add(timeout) 154 | nonceSent := base64.StdEncoding.EncodeToString(randomBytes(NonceBytes)) 155 | protocol = strings.ToLower(protocol) 156 | errChan := make(chan *checkError, 2) 157 | 158 | switch protocol { 159 | case "tcp": 160 | listener, err := net.ListenTCP("tcp", &net.TCPAddr{Port: int(port)}) 161 | if err != nil { 162 | return false, err, nil 163 | } 164 | listener.SetDeadline(deadline) 165 | defer listener.Close() 166 | 167 | go func() { 168 | for { 169 | conn, err := listener.AcceptTCP() 170 | if err != nil { 171 | errChan <- &checkError{err, nil} 172 | return 173 | } 174 | 175 | ce := handleConn((*tcpConn)(conn), nonceSent, deadline) 176 | if ce.isEmpty() { 177 | errChan <- ce 178 | } 179 | } 180 | }() 181 | case "udp": 182 | conn, err := net.ListenUDP("udp", &net.UDPAddr{Port: int(port)}) 183 | if err != nil { 184 | return false, err, nil 185 | } 186 | defer conn.Close() 187 | 188 | go func() { 189 | errChan <- handleConn(conn, nonceSent, deadline) 190 | }() 191 | default: 192 | return false, nil, fmt.Errorf("unknown protocol: %s", protocol) 193 | } 194 | 195 | go func() { 196 | errChan <- remoteCheck(serverAddr, protocol, port, nonceSent, timeout) 197 | }() 198 | 199 | for i := 0; i < 2; i++ { 200 | ce := <-errChan 201 | if !ce.isEmpty() { 202 | return false, ce.reason, ce.error 203 | } 204 | } 205 | 206 | return true, nil, nil 207 | } 208 | --------------------------------------------------------------------------------