├── go.mod ├── .gitignore ├── README.md └── main.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nknorg/portchecker-server 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor 3 | *~ 4 | .DS_Store 5 | build 6 | portchecker-server 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # portchecker-server 2 | 3 | Server side of [portchecker](https://github.com/nknorg/portchecker). 4 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "log" 9 | "net" 10 | "net/http" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | var listenAddr = flag.String("listen-addr", ":80", "http server listen address") 16 | var httpReadTimeout = flag.Int("http-read-timeout", 10, "http read timeout in seconds") 17 | var httpWriteTimeout = flag.Int("http-write-timeout", 30, "http write timeout in seconds") 18 | var portDialTimeout = flag.Int("port-dial-timeout", 10, "check port dial timeout in seconds") 19 | var portConnTimeout = flag.Int("port-conn-timeout", 30, "check port conn timeout in seconds") 20 | 21 | type Req struct { 22 | Protocol string `json:"protocol"` 23 | Port uint16 `json:"port"` 24 | Nonce string `json:"nonce"` 25 | } 26 | 27 | type Resp struct { 28 | Success bool `json:"success"` 29 | Error string `json:"error,omitempty"` 30 | } 31 | 32 | type ipRange struct { 33 | start net.IP 34 | end net.IP 35 | } 36 | 37 | var privateRanges = []ipRange{ 38 | ipRange{ 39 | start: net.ParseIP("10.0.0.0"), 40 | end: net.ParseIP("10.255.255.255"), 41 | }, 42 | ipRange{ 43 | start: net.ParseIP("100.64.0.0"), 44 | end: net.ParseIP("100.127.255.255"), 45 | }, 46 | ipRange{ 47 | start: net.ParseIP("172.16.0.0"), 48 | end: net.ParseIP("172.31.255.255"), 49 | }, 50 | ipRange{ 51 | start: net.ParseIP("192.0.0.0"), 52 | end: net.ParseIP("192.0.0.255"), 53 | }, 54 | ipRange{ 55 | start: net.ParseIP("192.168.0.0"), 56 | end: net.ParseIP("192.168.255.255"), 57 | }, 58 | ipRange{ 59 | start: net.ParseIP("198.18.0.0"), 60 | end: net.ParseIP("198.19.255.255"), 61 | }, 62 | } 63 | 64 | func inRange(r ipRange, ipAddress net.IP) bool { 65 | if bytes.Compare(ipAddress, r.start) >= 0 && bytes.Compare(ipAddress, r.end) < 0 { 66 | return true 67 | } 68 | return false 69 | } 70 | 71 | func isPrivateSubnet(ipAddress net.IP) bool { 72 | if ipCheck := ipAddress.To4(); ipCheck != nil { 73 | for _, r := range privateRanges { 74 | if inRange(r, ipAddress) { 75 | return true 76 | } 77 | } 78 | } 79 | return false 80 | } 81 | 82 | func getClientIP(r *http.Request) net.IP { 83 | for _, h := range []string{"X-Real-Ip", "X-Client-Ip", "X-Forwarded-For"} { 84 | for _, ipStr := range strings.Split(r.Header.Get(h), ",") { 85 | ipStr = strings.TrimSpace(ipStr) 86 | ip := net.ParseIP(ipStr) 87 | if ip.IsGlobalUnicast() && !isPrivateSubnet(ip) { 88 | return ip 89 | } 90 | } 91 | } 92 | return net.ParseIP(strings.Split(r.RemoteAddr, ":")[0]) 93 | } 94 | 95 | func checkPort(r *http.Request) (int, *Resp, error) { 96 | resp := &Resp{Success: false} 97 | 98 | clientIP := getClientIP(r) 99 | if clientIP == nil { 100 | resp.Error = fmt.Sprintf("failed to parse client IP address") 101 | return http.StatusBadRequest, resp, fmt.Errorf("failed to parse client IP address from %s", r.RemoteAddr) 102 | } 103 | 104 | req := &Req{} 105 | decoder := json.NewDecoder(r.Body) 106 | err := decoder.Decode(req) 107 | if err != nil { 108 | resp.Error = fmt.Sprintf("request body is not a valid json") 109 | return http.StatusBadRequest, resp, fmt.Errorf("decode request error: %v", err) 110 | } 111 | 112 | protocol := strings.ToLower(req.Protocol) 113 | addr := fmt.Sprintf("%s:%v", clientIP.String(), req.Port) 114 | 115 | var conn net.Conn 116 | switch protocol { 117 | case "tcp", "udp": 118 | conn, err = net.DialTimeout(protocol, addr, time.Duration(*portDialTimeout)*time.Second) 119 | default: 120 | resp.Error = fmt.Sprintf("unknown protocol: %s", protocol) 121 | return http.StatusBadRequest, resp, fmt.Errorf(resp.Error) 122 | } 123 | if err != nil { 124 | resp.Error = fmt.Sprintf("dial error: %v", err) 125 | return http.StatusOK, resp, nil 126 | } 127 | 128 | conn.SetDeadline(time.Now().Add(time.Duration(*portConnTimeout) * time.Second)) 129 | 130 | _, err = conn.Write([]byte(req.Nonce)) 131 | if err != nil { 132 | resp.Error = fmt.Sprintf("write error: %v", err) 133 | return http.StatusOK, resp, nil 134 | } 135 | 136 | b := make([]byte, 64) 137 | n, err := conn.Read(b) 138 | if err != nil { 139 | resp.Error = fmt.Sprintf("read error: %v", err) 140 | return http.StatusOK, resp, nil 141 | } 142 | 143 | _, err = conn.Write(b[:n]) 144 | if err != nil { 145 | resp.Error = fmt.Sprintf("write error: %v", err) 146 | return http.StatusOK, resp, nil 147 | } 148 | 149 | resp.Success = true 150 | return http.StatusOK, resp, nil 151 | } 152 | 153 | func handler(w http.ResponseWriter, r *http.Request) { 154 | if r.Method != http.MethodPost { 155 | w.WriteHeader(http.StatusMethodNotAllowed) 156 | return 157 | } 158 | 159 | statusCode, resp, err := checkPort(r) 160 | if err != nil { 161 | log.Printf("Check port error: %v", err) 162 | } 163 | if resp != nil { 164 | b, err := json.Marshal(resp) 165 | if err != nil { 166 | w.WriteHeader(http.StatusInternalServerError) 167 | return 168 | } 169 | w.WriteHeader(statusCode) 170 | w.Header().Set("Content-Type", "application/json") 171 | _, err = w.Write(b) 172 | if err != nil { 173 | log.Printf("Write response error: %v", err) 174 | } 175 | } else { 176 | w.WriteHeader(statusCode) 177 | } 178 | } 179 | 180 | func main() { 181 | flag.Parse() 182 | http.HandleFunc("/", handler) 183 | s := &http.Server{ 184 | Addr: *listenAddr, 185 | ReadTimeout: time.Duration(*httpReadTimeout) * time.Second, 186 | WriteTimeout: time.Duration(*httpWriteTimeout) * time.Second, 187 | } 188 | log.Fatal(s.ListenAndServe()) 189 | } 190 | --------------------------------------------------------------------------------