commit b8f8e5607c7129fc7e49f352bbe2cf782b8e8e17 Author: elisaveta9 Date: Tue Dec 23 16:38:32 2025 +0300 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/admin.log b/admin.log new file mode 100644 index 0000000..0ef0eea --- /dev/null +++ b/admin.log @@ -0,0 +1,22 @@ +2025/12/22 13:39:35 ADMIN AUTH_MISSING ip=192.168.31.108:63121 +2025/12/22 13:39:36 ADMIN AUTH_MISSING ip=192.168.31.108:63121 +2025/12/22 13:41:40 ADMIN AUTH_MISSING ip=192.168.31.108:63121 +2025/12/22 13:41:40 ADMIN AUTH_MISSING ip=192.168.31.108:63121 +2025/12/22 13:41:48 ADMIN AUTH_MISSING ip=192.168.31.108:63121 +2025/12/22 13:41:48 ADMIN AUTH_MISSING ip=192.168.31.108:63121 +2025/12/22 13:42:36 ADMIN AUTH_MISSING ip=192.168.31.108:63121 +2025/12/22 13:42:36 ADMIN AUTH_MISSING ip=192.168.31.108:63121 +2025/12/22 13:43:14 ADMIN AUTH_MISSING ip=192.168.31.108:63121 +2025/12/22 13:43:14 ADMIN AUTH_MISSING ip=192.168.31.108:63121 +2025/12/22 13:43:38 ADMIN AUTH_MISSING ip=192.168.31.108:63121 +2025/12/22 13:43:38 ADMIN AUTH_MISSING ip=192.168.31.108:63121 +2025/12/22 13:45:28 ADMIN AUTH_MISSING ip=192.168.31.108:63121 +2025/12/22 13:45:28 ADMIN AUTH_MISSING ip=192.168.31.108:63121 +2025/12/22 13:50:59 ADMIN ADD domain=api.phone.local ip=192.168.31.108:63731 +2025/12/22 20:26:42 ADMIN ADD domain=photo.phone.local ip=192.168.31.108:57258 +2025/12/22 20:34:20 ADMIN ADD domain=api.phone.key ip=192.168.31.108:57431 +2025/12/22 20:34:36 ADMIN DELETE domain=api.phone.key ip=192.168.31.108:57431 +2025/12/22 20:34:42 ADMIN ADD domain=api.phone.local ip=192.168.31.108:57431 +2025/12/22 20:35:10 ADMIN ADD domain=photo.phone.local ip=192.168.31.108:57431 +2025/12/23 13:11:21 ADMIN ADD domain=api.phone.local ip=192.168.31.108:51617 +2025/12/23 13:16:27 ADMIN ADD domain=api.phone.key ip=192.168.31.108:51942 diff --git a/admin/admin.html b/admin/admin.html new file mode 100644 index 0000000..ffa5edd --- /dev/null +++ b/admin/admin.html @@ -0,0 +1,127 @@ + + + + + Relay Admin Panel + + + + +

Relay Admin Panel

+ + + +
+ +

Registered domains

+ + + +
+ +

Add domain

+ + + +

+ + + + + \ No newline at end of file diff --git a/admin/admin_log.go b/admin/admin_log.go new file mode 100644 index 0000000..aab084f --- /dev/null +++ b/admin/admin_log.go @@ -0,0 +1,21 @@ +package admin + +import ( + "log" + "os" +) + +var adminLogger *log.Logger + +func InitLogger() { + f, err := os.OpenFile( + "admin.log", + os.O_CREATE|os.O_APPEND|os.O_WRONLY, + 0600, + ) + if err != nil { + log.Fatal("cannot open admin.log:", err) + } + + adminLogger = log.New(f, "", log.LstdFlags|log.LUTC) +} diff --git a/admin/config.go b/admin/config.go new file mode 100644 index 0000000..093f6e7 --- /dev/null +++ b/admin/config.go @@ -0,0 +1,3 @@ +package admin + +var ApiKey string diff --git a/admin/handlers.go b/admin/handlers.go new file mode 100644 index 0000000..ed63a98 --- /dev/null +++ b/admin/handlers.go @@ -0,0 +1,58 @@ +package admin + +import ( + "net/http" + "strings" + + "relay/registry" +) + +func domainsHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + + case "GET": + registry.Global.Mu.Lock() + defer registry.Global.Mu.Unlock() + for d := range registry.Global.Domains { + w.Write([]byte(d + "\n")) + } + + case "POST": + domain := strings.ToLower(r.URL.Query().Get("domain")) + if domain == "" { + http.Error(w, "domain required", http.StatusBadRequest) + return + } + + registry.Global.Mu.Lock() + if _, exists := registry.Global.Domains[domain]; exists { + w.Write([]byte("already registered\n")) + } else { + registry.Global.Domains[domain] = nil + adminLogger.Printf( + "ADMIN ADD domain=%s ip=%s", + domain, r.RemoteAddr, + ) + w.Write([]byte("registered\n")) + } + registry.Global.Mu.Unlock() + + case "DELETE": + domain := strings.ToLower(r.URL.Query().Get("domain")) + + registry.Global.Mu.Lock() + if _, exists := registry.Global.Domains[domain]; exists { + delete(registry.Global.Domains, domain) + adminLogger.Printf( + "ADMIN DELETE domain=%s ip=%s", + domain, r.RemoteAddr, + ) + } + registry.Global.Mu.Unlock() + + w.Write([]byte("deleted\n")) + + default: + http.Error(w, "method not allowed", 405) + } +} diff --git a/admin/middleware.go b/admin/middleware.go new file mode 100644 index 0000000..5d96bb6 --- /dev/null +++ b/admin/middleware.go @@ -0,0 +1,26 @@ +package admin + +import ( + "net/http" +) + +func requireAPIKey(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + key := r.Header.Get("apikey") + ip := r.RemoteAddr + + if key == "" { + adminLogger.Printf("ADMIN AUTH_MISSING ip=%s", ip) + http.Error(w, "missing api key", http.StatusUnauthorized) + return + } + + if key != ApiKey { + adminLogger.Printf("ADMIN AUTH_FAIL ip=%s", ip) + http.Error(w, "invalid api key", http.StatusForbidden) + return + } + + next.ServeHTTP(w, r) + }) +} diff --git a/admin/server.go b/admin/server.go new file mode 100644 index 0000000..2739412 --- /dev/null +++ b/admin/server.go @@ -0,0 +1,24 @@ +package admin + +import ( + "log" + "net/http" +) + +func Serve(addr string) { + mux := http.NewServeMux() + + mux.Handle("/domains", requireAPIKey(http.HandlerFunc(domainsHandler))) + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, "admin.html") + }) + + log.Println("Admin API listening on", addr) + log.Fatal(http.ListenAndServeTLS( + addr, + "certs/admin.crt", + "certs/admin.key", + mux, + )) +} diff --git a/certs/admin.crt b/certs/admin.crt new file mode 100644 index 0000000..59da0fb --- /dev/null +++ b/certs/admin.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCTCCAfGgAwIBAgIUe6pJSs6b4SAGPq+6Y2cHcD8RPSIwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MTIyMjEzMzgzMloXDTI2MTIy +MjEzMzgzMlowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAuMh21DmeKPkG3W/yYCQTi2CCd01JpqOhDl5dXTFU9pBE +5FMyx9bNKcazJhMALCrqXJU/8YKTvigbQYDYQWLsuURDhItrdaPUtDKQ2h9kaJEq +/hYUzu/nUopkdb2FyKlvsJo5newM7GZLadHIERng+QXXjJhlDsYcZVWMSLRTZuoV +awZSxKA3N9SiXyiFuXZ3yXSq6KTwWrVvMx+QO4zXg8RXdKXG8aWHY0aT2i2fe53D +/4YHsZHMg2ilBnh18GTIOnArCwqXE8cuVHtmgKNQyXmmW48tj+3UnYRtdc1HtMGj +Ecm11nnbjKt0lIwdx1DWDDclU4j6Vbpp+J6VpKaKUQIDAQABo1MwUTAdBgNVHQ4E +FgQUS5pECl+57NwHASDX2zoHe5TXQTswHwYDVR0jBBgwFoAUS5pECl+57NwHASDX +2zoHe5TXQTswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAdiOu +MabNSO2Ymtead5ZTNR5tNVRCd+xUIod7BmKA9Qo1/W8PlKVyYCly5e9NgtEqBaBA +f1NURkl70a6lDcSQRyeAPEvpkqYsANedkq74RGJ6TFbj1kBbXSySXgrzF9f/q6x6 +oZ/Age7aItfly/lHFIRZocrmi5xZxCjl39n4qonniAsT4zw1vC8jFqYsk0eVvtSe +H1O9K091L7sMxt4wijh6YtFZWSbezZs2OudUrc5kB2DeU9IUuIP83uh585ThTBMn +SDKlxw1Jtz7SMuP4rs7fCzIslcSNq7q+KCvtcaLOvhlQRmXQ45fd+ESASXY5BcF+ +0neKGaxItwVUvBu4DQ== +-----END CERTIFICATE----- diff --git a/certs/admin.key b/certs/admin.key new file mode 100644 index 0000000..30f05a9 --- /dev/null +++ b/certs/admin.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC4yHbUOZ4o+Qbd +b/JgJBOLYIJ3TUmmo6EOXl1dMVT2kETkUzLH1s0pxrMmEwAsKupclT/xgpO+KBtB +gNhBYuy5REOEi2t1o9S0MpDaH2RokSr+FhTO7+dSimR1vYXIqW+wmjmd7AzsZktp +0cgRGeD5BdeMmGUOxhxlVYxItFNm6hVrBlLEoDc31KJfKIW5dnfJdKropPBatW8z +H5A7jNeDxFd0pcbxpYdjRpPaLZ97ncP/hgexkcyDaKUGeHXwZMg6cCsLCpcTxy5U +e2aAo1DJeaZbjy2P7dSdhG11zUe0waMRybXWeduMq3SUjB3HUNYMNyVTiPpVumn4 +npWkpopRAgMBAAECggEADG+MqbyP85og12XEO3//YxBVb3HU4gFd99wrbzfAS/tf +xjhVhk6jOOtzB+ixngdXBZs+IK2PmQr/cewvLFAGoPL3vUjvFuLLUEOdQoS9f+5n +JORNCwk+ZEHFQDRHTOl0R2p1tAuxv0t6H07GPA+7DN9TB0FZVGzKS+/oNJtoMoMz +BREkBzhA+BZirITicHqv6Egcobh+2Cdd6Nh6Mhdj8280RG8SmrJZvSBdMvmR4Xqm +c+J4gh6wNCGOewKQM6JBAIVS5bd6c5flaKvgY3jTRXzX/Gmsv4422rgUaMlwcOgO +ZjkqRDDVcfc3VNMelVVxKuBTLWYURMhTcRIm0vBhbQKBgQD9zr4mNLcvlWZVloSz +2basjZuHo/Ywb1t5z+U11coaQIWBYmOPw/dB3CpfTp46iUHBZQE4/bG+RVt/MGKF +Q6ST9h2oMEz9pH5zeSYcMi3MUdttb6hgJDd/Amf6Gk71JKvCqLb0Vu8NoaOtrCsm +zoF3RdsoIWpgSoDBlZQIb3ca7QKBgQC6YRWFmiciOh95HnimBDsmW2XaA6TOYe3W +NOSEFlC1Rc9FsCq8OX14tTRIlre3xSXHTUEpHhJFu0bef5/cPf/JTuQpxFkvrXm6 +IdVCmvcy2fbB8br2JyvhQ6BBzA5F94p/3/bS2f2GzchBuQEyEj7dSJZPBbZHzlpR +UyCdHaCsdQKBgHxIrNxQnBN0+TOYDUt0pPtCLJLzOy7kmMrBfuAp3FmWlsmQwGg5 +8e4SPb2F5f2MEOL+7uZVdKBTnkZeDyBqy0CZGFSvskPSNQmenYbZG4wd2XFxZ+YM +VhCfwQK7t0ZburALpetoVo86Q8hbspXCMauSTYsNMeYNFZe2A1NOIejRAoGAPIPP +OBzwPeW/WFUzeTwAdJjSfjIWrcgQMC/mTpjsRZ9QCGGFzq2f9rRnMHZ3WlzRwl9s +G8yexDNldFLd9eXPim5qGMGe76MU1gGsO78TKlipDRnOyaO4VKDfhN2beM5CEvkG +LDoJXl6seeJ25+oSrUinPSsunyv6GVOzUDBRfg0CgYAk6qV1fb/9sZY+twfdlkEg +0h9e2b2xFInxLZKem9Ogd85fHjdjGmykIBpW2crG3DAX1vVDm8Lp2Z/IvwquO5/N +NfY+tMhS3UgFECu7lDcwJBxe7DuYN6TWz+RB8rVnEGhuAlfyPwf5nggRt8Y+rLcz +KpAh+zwvI7U80qQ2oiK8eA== +-----END PRIVATE KEY----- diff --git a/certs/ca.bks b/certs/ca.bks new file mode 100644 index 0000000..8083cb3 Binary files /dev/null and b/certs/ca.bks differ diff --git a/certs/ca.crt b/certs/ca.crt new file mode 100644 index 0000000..e6e1c6a --- /dev/null +++ b/certs/ca.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFDTCCAvWgAwIBAgIUcGCTDSHXhNdepDL+88XFkMrKiKIwDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UEAwwLUGhvbmVSb290Q0EwHhcNMjUxMTE3MjIwMzMwWhcNMzUx +MTE1MjIwMzMwWjAWMRQwEgYDVQQDDAtQaG9uZVJvb3RDQTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAOEqJqNS07VhtV8B17cm/wBo9XeDcdRTWiUJV5aw +dmlyBjPP/eU3pngUHi5ZBy8OwSswkjGhOVXJ1pwjoi2mQ1SFTSs+Z7MMtpvGsZm/ +0WEiF15NAEgEygD0bFRIzth7QqC/qLNHSJIhhpTNQ+cxWs2ndNjaSVuf4WRDLE30 +J49MiaGeaZVnSdGAcH6lmK7Y42jZwxY1a9viTGaPz2aI4zU2bKXG+fOEmi3nWNlR +01s/MpK2WEkEqFBDwjurzivbeHkoJ0M2TxD/x48V4YXfN/j/ykxonmKF9kXikRqW +pJISBdlBf9i7l/vz9ApnHkKA8ZabfKm5XZHD04aJ8N6oO5KlsPjRA9Xuym/UhIUg +dkxuu3zOpsGEe3PNMAptdLbC4oN0wfJKpghIO9MxUChD3DVNBpt/XDlwI3vWEMLy +xnvxrJJnNy6I2jPcQ78Nv8bW767YHwEZ1NpEDbpyKrWo2cBZjRMpOnWBG2zJhcqY +4y38CwdRnk1+AYPLY72gmsDvFN0U6aMM9OpQv64DuqOjS6ZG3vXQq3kwwdWwQa9X +reCaVpIimzPAIKclEDIVXbd+ORM/NBqXI6O49fPgSS3hA8IdA8fuahjVnBGRXdO2 +H0+YvU9+/b8UnAE76Kv6HTVa+oOcJhFhU9QKPI0GHd+DCbCjTuxLyT6SPylKP9HQ +c+fjAgMBAAGjUzBRMB0GA1UdDgQWBBTlFiHfyw33ardXiUf2D3YfyDmEHzAfBgNV +HSMEGDAWgBTlFiHfyw33ardXiUf2D3YfyDmEHzAPBgNVHRMBAf8EBTADAQH/MA0G +CSqGSIb3DQEBCwUAA4ICAQBIT2KcTDnlsFrs7T8oBp6UXz7WNxQsbd5QINGJbNcD +bekXOFyh9nH8VZTAm4cRhIf9DAf9V3eifWk2ZAOiAU0+Tx+Fstj2qsD/QtYEWqoC +vlvMEKaqKmdvQsIBqs0tqZW1fpBoCJFNZuROZs8OsQU7kfdPrO7p2wKXkphDXqC3 +izXb4dyunJOJoAk9wVF3sKbnO4tzy1Vr6sBy5jTsTqK9BYCQX+0zkV1tRc9M9R4k +V9hgsdidzFDnXWdG6KG2ZU7I7Q4YursOT5yXZJSrCce1gcADQfEiedvcSkzmaBUm +C1kJUWymV1x08MbsxzbO2tR7tsMi/URjzi/OgjFQdblM+Z1Lfd76CsMliV3l3mhT +XKU8fTYvyAhJYKDKI7r5bzP5gobHkSf/fvr1YkHNzMd270ympn8fLJtunAfrxz2Z +N44pL/J44OEpu1DT+LhqnnHq4KnjFEd3DKryUGs2/C8i5GN41DF0krwEMQQLFYvL +PmW+QhNVxHpmsclTjbXsp9is/2iSoNrIRbEGSKWo8sOyX+eZGUwdEXmEdqZRh3Sy +QMcbvC7KkU5dkwwOoJqz+XFjpden4RllldwM1vchP8qRJ6gCu5aLH55fNPc4TReb +wxvUtntYebOC8p/VUUvWZiy481ql3khsv2Akwj/wecmKDatFVVRG5BVjiPJkLZ1O +Nw== +-----END CERTIFICATE----- diff --git a/certs/ca.der b/certs/ca.der new file mode 100644 index 0000000..59814c2 Binary files /dev/null and b/certs/ca.der differ diff --git a/certs/ca.key b/certs/ca.key new file mode 100644 index 0000000..5a46bd4 --- /dev/null +++ b/certs/ca.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDhKiajUtO1YbVf +Ade3Jv8AaPV3g3HUU1olCVeWsHZpcgYzz/3lN6Z4FB4uWQcvDsErMJIxoTlVydac +I6ItpkNUhU0rPmezDLabxrGZv9FhIhdeTQBIBMoA9GxUSM7Ye0Kgv6izR0iSIYaU +zUPnMVrNp3TY2klbn+FkQyxN9CePTImhnmmVZ0nRgHB+pZiu2ONo2cMWNWvb4kxm +j89miOM1NmylxvnzhJot51jZUdNbPzKStlhJBKhQQ8I7q84r23h5KCdDNk8Q/8eP +FeGF3zf4/8pMaJ5ihfZF4pEalqSSEgXZQX/Yu5f78/QKZx5CgPGWm3ypuV2Rw9OG +ifDeqDuSpbD40QPV7spv1ISFIHZMbrt8zqbBhHtzzTAKbXS2wuKDdMHySqYISDvT +MVAoQ9w1TQabf1w5cCN71hDC8sZ78aySZzcuiNoz3EO/Db/G1u+u2B8BGdTaRA26 +ciq1qNnAWY0TKTp1gRtsyYXKmOMt/AsHUZ5NfgGDy2O9oJrA7xTdFOmjDPTqUL+u +A7qjo0umRt710Kt5MMHVsEGvV63gmlaSIpszwCCnJRAyFV23fjkTPzQalyOjuPXz +4Ekt4QPCHQPH7moY1ZwRkV3Tth9PmL1Pfv2/FJwBO+ir+h01WvqDnCYRYVPUCjyN +Bh3fgwmwo07sS8k+kj8pSj/R0HPn4wIDAQABAoICAAcQxAqe+D66VK8pByRbqGfS +Y1V/PeysOulrnCCB5A1WAyTQE4cEUoh1FSdeKtAKtfwNd6DwDrUG8uGOarHlBDrc +Pfor6KQhM3+64erRrpfOMo6GewpHgDE8ekPAzyDS6VnS+c+6XmgbfjYgJM/V6CQI +6608WE30O+XiB7tgfHdOLwlvx59lENduoNkQiDSAYM5y+uCxB3Psa3pSa+8rP3n1 +fb5L+p7uG8px9oNHQosb9hHNgAK2jzOJlMg7kWol5mdi36yHwT5m0FPIm32Qbhi1 +qhrws3ui+YHi5KvBa2OcBiv1lrXt2QyScKd86eGrWylfMq++vdVroDEaFgFepRPb +YE3wwXldwag6hDckhYO14BRZ75TKI/+bis5G5SFZZgi6q4dsqc0OE2zp7fHbdXK0 +UvYB1uYFUxXo59JgqpPL5oN5pAA8qjTX77CgCegVNvUQChPIEuS0eloGQ4kEb8fw +VIaDvhL2LD8wfOwRYTQaWKm5tB0zbJBo2CX4UL1kUM+7Gva9JjQY9RUkcdAbqyvt +HT8cA1MCHBVcs24lDXiq2k7mEaCthCwMjDrRw5dd1ReXGIUIIFL9V/Pj79pxABvV +3V7zFK3aDAmt9UbRUBDZml0NaOTRw20mu1q/8GO+IiptENjwJN+ntv7vlUDtHYTO +GTvT9cd7pWiXXleHPGENAoIBAQD+diSnX/xsVWXBozFWX74/nT4/GzJy7fOlF4Ky +QJl9CHbfwJeROwPf/zHkAAkGmUaLdxei2+hAryJv3g9boSfTLROHIcMIuylpT+Wg +QrgLJixm5c2ljgTXWAaItK36C1JffIWIOlsS8bBDpNljiR6FolaW+u7GG7X4raZP +34TIsBOF/T3tKsvtIHhdf0sRMAAhhG3Th5wpFwKtQio+H2XQNA9TPSTTI3Q4uMlf +FTLOLq4I22RmPbexhO7s8tXlgas3whwRwien8GIH9vyCGhn3zMX0+GlfulKdVmmY +GFCSddeLZNOStmTMQW2a3amaWObyA0xoQigEYTqWR+seHgGHAoIBAQDihql1Fmvf +kVx1ygdznWd7w4exWcy48mz7Kwnahc6odQPXwirWJUeE3tVKt2hSsmMdONVcS423 +/92p803jlY4RozzhROzxSJ4BmzWkMeq+NqWb908x97CX9e+WXGnnWM7vfO9wgMsq +evqcZXKiLb8aMihfyQnA87RgrWZ9xKt+UN08rYzh7bCzBRY2wzaG3qPujzjY7Hc1 +9rNLmQbwJsH0JqZdhixpDi7Js0tVsgBvANHc5D3cFKarxEUCzhyfQn+2vH9Ni+En +ddGFbHhl4/PBhxSdglmLxX9Lyge29i6ZWbfM4Jxabucbyp4iF+B3qqLG43DvNz03 +LYKOAl7gKC3FAoIBAGRpMFuPx+bj5+x6PizMSSGHJhn23Hh3p049McivLyIkpevz +bpvTgtrwM7q5vNYKw8ii7QPJ7SMhOOCV6s+Ij1OIlQ0c6H5YhKXW7lDIIyHbJWhR +RE4Q9Z4YUTO+wGxL7WxakcLI7ElUb+6xbUsWv6GSBsM7L9+i7bK8q0UXealLudiE +rtwYqDHhPz39zhxxKYwQWto+yAN63gaKdmrfLujysh1xokdi1SjQ2j2X+t1blDdQ +g5TIw+TcBQAhbwBKo8qz4SmsyMM/Wx+zeb7JF/gtqOva3pH0z0bg43guGBVDRRPv +bIGXUb08EEktMREEBdX4MT1u5aWczGai6IeQ5Z8CggEAXZAbXAZHxg7nLqukikdg +X8kyObmKinVvOArdxglwgznZvXswcETzlljnpjW8/DM8Juy7i6SVQ4OGTdT1veOe +y4At/CmY6EwDH0y5Sjln6OrvWk06w7u3j/x/2g4PnAOdbWOgDkrUH5at/n0UbRIS +FcLT1ERYJ/9A6JGWfKoH32ONDavtPcAMNNUMCNB2rBw3f6heZZK95mcgRUBOklCf +2bVBD+4EV9wIzTymCB4+eZrQE7meJ1HMSbQGgRMa1VkFITgYKLJ26/KI+OAFlqgh +nTSq+ifFzqpjWq12pEZoluRsu02mpt+Zy2vUBubvyt2K4DU/Bz88pJbCjt9Byrot +tQKCAQEAw69rwrsZhvqBOi3enAHlvMKSoS435tUtiRPnlAGyWwo7Mag2Irb+8327 +blPJrbEIZ+nJ48JWg3P6X8Uq1hGrT2mO4DX84Sy14rVAESTI6iNqYWG0chJopBRV +FEbhkxix2e7N1OZigHUDm3EFOpupUHJMDuWmBiW+CBM4LW1aT6Bn4wXbDJtN4P9F +tX3yFowD9u24N+gRzTpabfkMKgiiRsj1uHVwXhw39WVPI3Ch6kwKIVe+HN74fhHo +gMqEUX7ouVbokXC4e18j7wOAAOWPWjEjQUjcHKXLuUtbRoKxgF7RN1CVnugLomU8 +2XZAcee+tCeqgwJwg3YFbc03POFXAw== +-----END PRIVATE KEY----- diff --git a/certs/ca.srl b/certs/ca.srl new file mode 100644 index 0000000..61a27fd --- /dev/null +++ b/certs/ca.srl @@ -0,0 +1 @@ +5166E809D8A7B37861DD999BE40296DD06CD077E diff --git a/certs/extfile.cnf b/certs/extfile.cnf new file mode 100644 index 0000000..bdeb5cb --- /dev/null +++ b/certs/extfile.cnf @@ -0,0 +1,9 @@ +authorityKeyIdentifier=keyid,issuer +basicConstraints=CA:FALSE +keyUsage = digitalSignature, keyEncipherment +extendedKeyUsage = serverAuth +subjectAltName=@alt_names + +[alt_names] +DNS.1=api.phone.local +DNS.2=photo.phone.local diff --git a/certs/g.bat b/certs/g.bat new file mode 100644 index 0000000..f506e1a --- /dev/null +++ b/certs/g.bat @@ -0,0 +1,58 @@ +@echo off +setlocal enabledelayedexpansion + +set OPENSSL="C:\Program Files (x86)\OpenSSL-Win32\bin\openssl.exe" + +%OPENSSL% x509 -in ca.crt -out ca.der -outform DER + + +if not exist ca.crt ( + echo [ERROR] File ca.crt not found in this folder. + pause + exit /b 1 +) + +echo [OK] Found ca.crt + +set BC_FILE=bcprov-jdk15on-1.70.jar + +if not exist %BC_FILE% ( + echo [INFO] Downloading %BC_FILE% ... + powershell -Command "(New-Object Net.WebClient).DownloadFile('https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.70/bcprov-jdk15on-1.70.jar', '%BC_FILE%')" + if not exist %BC_FILE% ( + echo [ERROR] Failed to download %BC_FILE% + pause + exit /b 1 + ) +) + +echo [OK] BouncyCastle is ready: %BC_FILE% + +echo [INFO] Converting ca.crt to ca.der ... +%OPENSSL% x509 -in ca.crt -out ca.der -outform DER + +if not exist ca.der ( + echo [ERROR] Failed to create ca.der + pause + exit /b 1 +) + +echo [OK] ca.der created + +echo [INFO] Creating BKS TrustStore ca.bks ... + +keytool -importcert -alias relayCA -file ca.der -keystore ca.bks -storetype BKS -storepass password -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath %BC_FILE% -noprompt + +if not exist ca.bks ( + echo [ERROR] Failed to create ca.bks + pause + exit /b 1 +) + +echo [OK] ca.bks successfully created + +echo [INFO] Verifying ca.bks ... +keytool -list -v -keystore ca.bks -storepass password -storetype BKS + +echo ca.bks is ready for Android. +pause \ No newline at end of file diff --git a/certs/generate_admin_cert.bat b/certs/generate_admin_cert.bat new file mode 100644 index 0000000..067f374 --- /dev/null +++ b/certs/generate_admin_cert.bat @@ -0,0 +1,30 @@ +@echo off + +cd /d "%~dp0" + +set OPENSSL="C:\Program Files (x86)\OpenSSL-Win32\bin\openssl.exe" + +if not exist %OPENSSL% ( + echo [ERROR] OpenSSL not found at: + echo %OPENSSL% + pause + exit /b 1 +) + +REM генерация сертификата +%OPENSSL% req -x509 -newkey rsa:2048 ^ + -keyout admin.key ^ + -out admin.crt ^ + -days 365 ^ + -nodes ^ + -subj "/CN=localhost" + +if errorlevel 1 ( + echo [ERROR] OpenSSL failed + pause + exit /b 1 +) + +echo. +echo [SUCCESS] admin.crt and admin.key generated in certs\ +pause diff --git a/certs/phone.crt b/certs/phone.crt new file mode 100644 index 0000000..68b288d --- /dev/null +++ b/certs/phone.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEaDCCAlCgAwIBAgIUUWboCdins3hh3Zmb5AKW3QbNB34wDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UEAwwLUGhvbmVSb290Q0EwHhcNMjUxMTE3MjIwMzMyWhcNMjYx +MTE3MjIwMzMyWjAXMRUwEwYDVQQDDAxwaG9uZS5kZXZpY2UwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC5mV3ppEknCziHHpO9WzDqYA5O3WyNsVFec0fi +zGNMzIZWjt9eKBzPUJH3L51oC+NtN1Qe/Qbq+/z/SzKtPz58o981bIoGNTZN0oh/ +28/IwZwimjyw8K2HB0vtTV16AIvtE72C4jOAuazsnjk8PUuJ9t7hH1afyiqSWdMa +K5FG7oWJGMXQQQXxmgsCQADP96H+810TT02em7AWZMyD9H7sIeRJ5CNuXuUDNJZZ +3XJgGStqfOFrk3S9dZxUsfI5jYl5MwDvt5HmR1nhwepxoT9IPPWEdm5oPPMhYymu +f581jZAmY98v+VHY76+J6bpLDjNUSDRntDqu9LFt1aRqMe8xAgMBAAGjgawwgakw +HwYDVR0jBBgwFoAU5RYh38sN92q3V4lH9g92H8g5hB8wCQYDVR0TBAIwADALBgNV +HQ8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwOgYDVR0RBDMwMYILcGhvbmUu +bG9jYWyCD2FwaS5waG9uZS5sb2NhbIIRcGhvdG8ucGhvbmUubG9jYWwwHQYDVR0O +BBYEFBFz4PmZFnooU2PAygJXD3LOK1mzMA0GCSqGSIb3DQEBCwUAA4ICAQC+jYsM +xjEfEbP1fgNR2zjBnfNLz+1GkXfMpuLbo+rcy6pcsqUPiK7EMoMjF12vY2zYvyD+ +8w6Pf3QCWQ/RKpN6yXHsp6ePUk/STAArZN4E3jI2+Pzp9a6PDehQDJXkjqpRBp0O +NROjcD0PrTzDB/eHD5SrGexLpVLhxoabUdKiEh+JylBhVUki4H8iFVvQvakZkb5o +x3F3G3nl+vmcTvbWe5dpVgY7rqjlsbg5OSQcte6B1mDvVwt7meM5ymjDFmxQdOfX +CoKQ8woPm9TmUiJf/Ljh+/RK8VIdkuynXvjkHvX/jAXWWQD6mRJq+HvaGNOFDGox +Oodi56Rq9H7LzY1pb7nHhzunxncR/443JjnrzAPyReuYBo2ON3RyF5g5JXgfmJNK +0u9pb9rEYY5W6jC6Hu6Ttby/BV+NO3xDofXHRo1ujK+WHVd490+U2vo1gfiwLc5P +wbHmmvNxXsF2xI+rrylho6OgXCF/lVcfMOl0klq0TzTwFL90+qLTz0b5AYN2VqnW +IIDCEwtbS4imX/OXLP6wPDiHxsy0PHKJfk1nr5cWu/IFG1myfz3EYZn4W8t3VSQS +e1h1lErCx5kMKBmTdyOukeEEu0+wCrjs/PpU8pQcFKlLWU4KR6/r7ZX/go0t6eCD +U5EVKKdr60PwkCfzb+ld0k7A+E61czW2XgNVQg== +-----END CERTIFICATE----- diff --git a/certs/phone.csr b/certs/phone.csr new file mode 100644 index 0000000..a70c180 --- /dev/null +++ b/certs/phone.csr @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICXDCCAUQCAQAwFzEVMBMGA1UEAwwMcGhvbmUuZGV2aWNlMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuZld6aRJJws4hx6TvVsw6mAOTt1sjbFRXnNH +4sxjTMyGVo7fXigcz1CR9y+daAvjbTdUHv0G6vv8/0syrT8+fKPfNWyKBjU2TdKI +f9vPyMGcIpo8sPCthwdL7U1degCL7RO9guIzgLms7J45PD1Lifbe4R9Wn8oqklnT +GiuRRu6FiRjF0EEF8ZoLAkAAz/eh/vNdE09NnpuwFmTMg/R+7CHkSeQjbl7lAzSW +Wd1yYBkranzha5N0vXWcVLHyOY2JeTMA77eR5kdZ4cHqcaE/SDz1hHZuaDzzIWMp +rn+fNY2QJmPfL/lR2O+viem6Sw4zVEg0Z7Q6rvSxbdWkajHvMQIDAQABoAAwDQYJ +KoZIhvcNAQELBQADggEBAHfVrvpmjQtuuDoi05i6yMSW4TnVuKPbOWTuJrKq5HPR +ob5KMf/hZMYpMdKN3OUjJ+Ze5BfI6X1MWUPX8Nx5kDVdSa1b0gEjMzgOlKbM0w+7 +Dv0jJsKWjmNGwD4IslSmzIrdZrD8kOZZmifBsc66unjgnhi+K8/EEgMz7AQCfS3m +UjwAclRvqI/abvgnOcDQ+dtLF+yjEhbJltP4kQKraIkR2bxGcP53W8n+xeeyEqlK +3NdUk9iwhAO7ovXSW7Zt7ohx3ASQTaUWCS9vYGihY9RhdRcyk1Q47vAbwOe0kmMC +OJcA9qOZcx1FOPKkYJ4LRyj/uDj4G8zoz75BpHKzYW4= +-----END CERTIFICATE REQUEST----- diff --git a/certs/phone.key b/certs/phone.key new file mode 100644 index 0000000..d7be3a7 --- /dev/null +++ b/certs/phone.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC5mV3ppEknCziH +HpO9WzDqYA5O3WyNsVFec0fizGNMzIZWjt9eKBzPUJH3L51oC+NtN1Qe/Qbq+/z/ +SzKtPz58o981bIoGNTZN0oh/28/IwZwimjyw8K2HB0vtTV16AIvtE72C4jOAuazs +njk8PUuJ9t7hH1afyiqSWdMaK5FG7oWJGMXQQQXxmgsCQADP96H+810TT02em7AW +ZMyD9H7sIeRJ5CNuXuUDNJZZ3XJgGStqfOFrk3S9dZxUsfI5jYl5MwDvt5HmR1nh +wepxoT9IPPWEdm5oPPMhYymuf581jZAmY98v+VHY76+J6bpLDjNUSDRntDqu9LFt +1aRqMe8xAgMBAAECggEAEFR7ssNKaNdWZzq1m6tERjyJL1kEV5uhG3ly+w2qGqEP +z6bgS8vYuRVcOnUKJi3Bmj1ZHYuvltZXTNKxFOHXKo1NzHwON6/P5UOfnSxaGTTC +LmzTgTgAwW9onh3SZzOMHDuotYIXyP0AesAMxicyGDBl+aw4HKgWwp8i18JUlODg +sP4BsjMRMVvL3n45PsORENrxORx7n73243k3yhR4GgNiH9f6HkDUi75uHFOguzjH +kkhy7eKMwLdNOsyTQgWXDRkf9p1q43Ij2pA0k+E0o+8C0oteG4IVNPTir9Df0FsR +weV6NuvHQcM437iqeBK3SS6IVRq+ONaRn3xkWYJklwKBgQDoY3k3hNDRVZ2tVjpD +HVAxHMhYIKm5J4lP6MZfIxFrfla+RPPNqbtaJetDI+FOQ9I1ww7kB+USKb13cqIM +PzYD8KNLNmiZGXNafZGN1KlcrGyVepdSZBDhvOCr0fsuPwrvE07wEP619K9UVIMP +TYD1wWRv8bOlGe6/IWdGf+VxFwKBgQDMdOD+8u/N1zAt2ocGSkY9QSg5LV50x1cO +BZ0KEKFF682VjKgvPXJfwLEtjB2c7DtB02FzrSBRCai0oejqjiR9X76ustvN6lGs +a3QyZdRQduswEh7nbjHDZWuRcJ6LcrwyXteAxCd2jYeGlNm2H4mViRPRA7Oj/5Cc +IM6bT2v+9wKBgQDTqL5gJdU3xN4fYXkhZdKDWO7U7bMIVA1Jvf7n6f3UxTZmiFDy +0hQ1cHIOLeDpMvaERwwJ/3LRjzjlUYBs3SnqfsOMHps4Tqj1E4d+AeLb6KPhpc2r +mj6SDEEp3dAEeCESTajJGQGVYq5Khcr1Jic+Lw22tBNmXe9JIDh7SsQEswKBgQDK +yM3z8C4qgdj2Ub0EXxylWn+zni0GNERC0wx71Bywxf0E7BpPqGlNUUgp/cPNRXxM +gIQygC8CUUszxTbHlS3Z7+GnUhyyckqJcZYw+lCHcjs4VXCzZ9cRjwEOop2Nf8Hr +T6f8vhDKA/u8XtxER6llWC0AbOR6r1rVj7B72F0r5wKBgQCe/tdr/Oe17NXCkrRP +1iwKHqtueiwWdgKmGWiTYgZssbZDtjb+yuCt0sKFZ3fPgJWG0rdyQIilNUXeM2Yq +TVWWNBbsncOOb2hRJ1E0CY5DlsRVRixK5hezfRnM+4Ewecr3Q9LJywJRh7RH6uXr +plqC/CygDzL1lLEMkHRu1TRRTQ== +-----END PRIVATE KEY----- diff --git a/certs/phone.p12 b/certs/phone.p12 new file mode 100644 index 0000000..db8b676 Binary files /dev/null and b/certs/phone.p12 differ diff --git a/certs/phone_ext.cnf b/certs/phone_ext.cnf new file mode 100644 index 0000000..f2823b8 --- /dev/null +++ b/certs/phone_ext.cnf @@ -0,0 +1,9 @@ +authorityKeyIdentifier=keyid,issuer +basicConstraints=CA:FALSE +keyUsage=digitalSignature,keyEncipherment +extendedKeyUsage=clientAuth +subjectAltName=@alt_names +[alt_names] +DNS.1=phone.local +DNS.2=api.phone.local +DNS.3=photo.phone.local diff --git a/certs/relay.crt b/certs/relay.crt new file mode 100644 index 0000000..676becd --- /dev/null +++ b/certs/relay.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEdjCCAl6gAwIBAgIUUWboCdins3hh3Zmb5AKW3QbNB3kwDQYJKoZIhvcNAQEL +BQAwGDEWMBQGA1UEAwwNUGhvbmUgUm9vdCBDQTAeFw0yNTExMTcyMDE3MjlaFw0y +NjExMTcyMDE3MjlaMBAxDjAMBgNVBAMMBXJlbGF5MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAnqXbO1kzgmhp+kqvoq2lXAbTPN3yJLr+e5bKNoP5mhYh +CGLQ4boeS3a/1LPjLLJq9vE+eXywgG/Lk+KvHysQg7imOKtEVQmkT1cXpkBSMKYZ +TlMLttVhlj3IhrEk0N3b5k2QcnhV+CZxIFiZrrbFC5ZDlr1QgsW0bqF6r1VyGIxx +Aivtx5EOUNLf32hyH0wnf74Abi4/CBvo5YDEM+0F5XMV7SkZTTfNyspBPweDT9hy +evkJysMBqMRBelxenRFoHH5iHWds9lH0CejVTsVXT817Z6+mUsk3qiV4tpCTanz9 +IgaZ/37J1VtWvMZOdlUJXa1YoEJoOYSS5Mz+vQeUVQIDAQABo4G/MIG8MB8GA1Ud +IwQYMBaAFHxW0HxPLVn3/hGAqanNf1E/5+eXMAkGA1UdEwQCMAAwCwYDVR0PBAQD +AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBME0GA1UdEQRGMESCBXJlbGF5ggtyZWxh +eS5sb2NhbIIPYXBpLnBob25lLmxvY2FsghFwaG90by5waG9uZS5sb2NhbIcEfwAA +AYcEwKgfbDAdBgNVHQ4EFgQUBQUunM/zKMrW9//gKQWX1Ze9laUwDQYJKoZIhvcN +AQELBQADggIBAFpialKbFXFeSTiwijhKogB0LQ9uqIjw4Mzv7/RNU/xH9fIK7fAv +7a4MMlSDbEphhcv/6mf3pmjzZ6yqrvXmXN9RnmdUjnjwdhW+Q0TghCWabCtdCtCm +MmPDQ08kZY0hIsJGuAaEuubW/dzoFfZ5heGMv2R2fSp8YO/OHI0+8M+9dgcQJiFz +L4e7Dr1Lt5rzfEhJXtxGBA/Fk5R7dUEbVThLsoPFSuwk8DjJ4lz8iRtDNDD3TB2W +vC+L9U7veRkaci6lzFRpAPGCEnA3RKJFfGNylBda2y0dnuHfHZtf9MAmrUFUL476 +OQ8YxD+FoCQn4qNjGzzdHT1t4WIrWoKjqI8NRxLiJjDq+6E3H/SiL25gjPxYtUIJ +tLSrq9lnFqJ5zgAXee3K5/XHS5wToBGjnM4QMQru54COM9hHzTk3hSLZFTrFNNqN +mwhaY+piTMYDJVcPvxzSetXug4hCEZBkZg4GqvX/6N4ic+YXwu1kaLvPrqpo1BEC +SCjbJVXCorDrbNqmXFq74GHvi+W6l5RX7GIy+oiSxAQCcLMJ1IY8iA1B+PsjvpBq +1ni9rWY0U6i67LT0Y8UutNLSCfuHQI0KIJ3Xqt7ZbRkE/VwjcoMA++lh+ML12MM+ +WgPItN/3E0oZhAOF0MaYNijGRnkAs16/o7CiiQ6Bf0bHQdmKrNhdB0aD +-----END CERTIFICATE----- diff --git a/certs/relay.csr b/certs/relay.csr new file mode 100644 index 0000000..cdb4ba0 --- /dev/null +++ b/certs/relay.csr @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICVTCCAT0CAQAwEDEOMAwGA1UEAwwFcmVsYXkwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCepds7WTOCaGn6Sq+iraVcBtM83fIkuv57lso2g/maFiEI +YtDhuh5Ldr/Us+Mssmr28T55fLCAb8uT4q8fKxCDuKY4q0RVCaRPVxemQFIwphlO +Uwu21WGWPciGsSTQ3dvmTZByeFX4JnEgWJmutsULlkOWvVCCxbRuoXqvVXIYjHEC +K+3HkQ5Q0t/faHIfTCd/vgBuLj8IG+jlgMQz7QXlcxXtKRlNN83KykE/B4NP2HJ6 ++QnKwwGoxEF6XF6dEWgcfmIdZ2z2UfQJ6NVOxVdPzXtnr6ZSyTeqJXi2kJNqfP0i +Bpn/fsnVW1a8xk52VQldrVigQmg5hJLkzP69B5RVAgMBAAGgADANBgkqhkiG9w0B +AQsFAAOCAQEALWFuDYi3xyis834DJ+B9+snhFq9cdOZmeoR3yDoJT03NN7yf/sy/ +4pCoD+y5xfPFsi5JP/ev+ki1ZPCEpW+C8ptNtYurWo3QN3B/RmY3xsbE9C1XcTwm +comDNUUEfOaHi64bWIJ+ee4mLzD0IdDYWTVoNzuGkDxWfPlcibvV1jrDRb1zFWEU +UTrXXsa0LktGpGUX7QW+NIUGWuvBi7zZt/iyV2Hq9zRBbjacuo+2niyM+Igyygb9 +WB0JdxH/iChvPd498hyHQnt1zq/UEgT+pX5qQ5blTzoKDr5y+WVYPw81ump8W81v +hqq2sDyyv0hIoKyEnZlNc6JlQA/d1DfliA== +-----END CERTIFICATE REQUEST----- diff --git a/certs/relay.key b/certs/relay.key new file mode 100644 index 0000000..e51c8e5 --- /dev/null +++ b/certs/relay.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCepds7WTOCaGn6 +Sq+iraVcBtM83fIkuv57lso2g/maFiEIYtDhuh5Ldr/Us+Mssmr28T55fLCAb8uT +4q8fKxCDuKY4q0RVCaRPVxemQFIwphlOUwu21WGWPciGsSTQ3dvmTZByeFX4JnEg +WJmutsULlkOWvVCCxbRuoXqvVXIYjHECK+3HkQ5Q0t/faHIfTCd/vgBuLj8IG+jl +gMQz7QXlcxXtKRlNN83KykE/B4NP2HJ6+QnKwwGoxEF6XF6dEWgcfmIdZ2z2UfQJ +6NVOxVdPzXtnr6ZSyTeqJXi2kJNqfP0iBpn/fsnVW1a8xk52VQldrVigQmg5hJLk +zP69B5RVAgMBAAECggEADOrEJtjkcMG/nzllg4FP7WZA3LvVc1gmUDm+zSGBuSIE +HoKtLzPQM6S/ibn7zS/ZtbF+vLWCSXLQyIG0cH05IwBtyD9pmOLoFe2pZ0flaUCc +3tu0IHbPM0Rkh1L1BGyL8vxtvmQ9266Fl3SfTrJi6MabSjKv03X1cuyC6LO0Tns/ +6QXA+BeTzUeOGQPNYQNhEfmAX9prFwVka0/ZrNK8O/uCIWo+S8sDvk+XuV001F/G +hRw9V21gyu8o2vE7u923hNlzN3oD+cwxeW7vdTf/V+Kvb/0WkhQfMcbiLkhUvJp8 +Ln3+vDFpT9YwvsLOrPgBfkpC9egSmW6iNDjRmUQQIQKBgQDbTMFM2J+V/NAvBtMo +tRvb4oL+g07vE6Bc2iIT1v6UxWFIkEb33Y3AbFPgeSXyqghx8A57BUkNMHAKDcwT +UDivnWL7yOpWIjkqT0Rx9r64xQJhdwkptYZGkor0ViO3Ib3bPeHo5hmkHPeXWBDG +RQ/Llx08LS8ePTW0JbBZFnJMtwKBgQC5MqZoO627pTd+ijH1ITqO6FVgpzAnlgrt +HTQ7E8vZMuNzswfB9lFqL9yQs/1JLv6wF6Q0BEpaKgjO7WxHmewh662gn9kJmJk0 +7Oa+fapJV6ZJyaspXjAS99b4CfKqJwJC2g9U4Voi+UbvIcGNk5ausmy9pA6otseY +X2YPGRHzUwKBgFnh2rRRcQ87gGRV6sQbfuCftUr1rKuGosqLyT3Sd6OoRphEMiIz +V38Pp00wumH8WNvCJV08rFVQjgtA3mzoC2PHISPLlGhbPIzf0lDXbhIzBSfHbyT+ +NpvEQlmk2JydosAyW95mOQovqTcJrE5nTfMG67+XUWGWeuVsMRxCe/R/AoGBAKAu +9Anh3IIaBCbaBx3b8Ndck0Q3N4p119uBUATtOXsS7fLL3iDhGUg8P42VcLWVBUtw +F2G6eEkGZHn+l5JSyxAH1jTBLauEJrfFVEtdTCWFv1nBKd9tDS3K2k/N4utTNDx4 +eZxUEm9/pt/9FXmMN1/BChb5SIibF4ZutUrHx2PrAoGAVBam777gpXuVnkjgj2hu +VM+Q3XZzBk3OPmN6LhWW2MaXL1PM7KEWFFa3Lg9n1xOvmLncPa/7pwwNsvvwt/Er +Dfn/TV4MDfb0DXjEu9W51f6ZHgXmlJl1XfXMTUO2tFCyKnCqU53LfDWLAXFi2HIw +3gd80FG9XMJxd+AlvwjzhGs= +-----END PRIVATE KEY----- diff --git a/certs/relay_ext.cnf b/certs/relay_ext.cnf new file mode 100644 index 0000000..2ba97bf --- /dev/null +++ b/certs/relay_ext.cnf @@ -0,0 +1,13 @@ +authorityKeyIdentifier=keyid,issuer +basicConstraints=CA:FALSE +keyUsage = digitalSignature, keyEncipherment +extendedKeyUsage = serverAuth +subjectAltName=@alt_names + +[alt_names] +DNS.1=relay +DNS.2=relay.local +DNS.3=api.phone.local +DNS.4=photo.phone.local +IP.1=127.0.0.1 +IP.2=192.168.31.108 diff --git a/certs/server.crt b/certs/server.crt new file mode 100644 index 0000000..75c8509 --- /dev/null +++ b/certs/server.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEZjCCAk6gAwIBAgIUUWboCdins3hh3Zmb5AKW3QbNB30wDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UEAwwLUGhvbmVSb290Q0EwHhcNMjUxMTE3MjIwMzMxWhcNMjYx +MTE3MjIwMzMxWjAWMRQwEgYDVQQDDAtwaG9uZS5sb2NhbDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBALeTnJjjhfrPPaGPWhlxpJwtWOXkJyVPDK7lHM/d +cW5bQvExpMHvDVIg04d6rlnxO+ZxMAfz29KhIIsbuTI7jW55+oS0ceUIsLZ729xi +eMZ31WfCZ/OoPgHj+BeQWEeeMQvvYfamW/lLZNUdRBQAdEEOM3v0ygqlMJdsjxOr +e8mTIjXWobWwGUGl0mqLH8f7Co0ef5N5A6n50furIODPeKUvVVItXz6oyuMnGwKm +iPEVa06LATbe8zQt1UdXP7OG/cPY4OCMQhwZZaYmYLkeFDqmHfj7nqQo1e2Xrdlm +zbdcBmcNgnwvuSJ+Fq5z0rzVQjpWjJ1nS2d+IotyuKN9dKcCAwEAAaOBqzCBqDAf +BgNVHSMEGDAWgBTlFiHfyw33ardXiUf2D3YfyDmEHzAJBgNVHRMEAjAAMAsGA1Ud +DwQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATA5BgNVHREEMjAwgg9hcGkucGhv +bmUubG9jYWyCEXBob3RvLnBob25lLmxvY2FshwTAqB9shwR/AAABMB0GA1UdDgQW +BBQIOZl1Y4we6IphJqvqR7kfWg8JIDANBgkqhkiG9w0BAQsFAAOCAgEA1E8ebgK8 +Q13ne2dAZsJ8071TgZPO8gZA31duAIN/PIwO3eExVzevzn2sxtnq2tPuivnkTajN +9dMO/EBmDZghXB41ZbEr8Bi+5u61xShHq/SSrpqEVE5nlbWnsOVv4B61WM5sA/sU +UVMf7B9YHUhXL4Gkrm6yWMlAhsIkLPwHhlESzvyMnVaM9h3B+LdqOyp1Ws6u3noH +IGstIRGNJY77vI0L1slfCU1IGIPrzjTc6QGGNA8Jm4bvnhjei8L4SawNKBH9+Ffl +gH6esjM9iQL3IJRw/B7BO0/GnMzGmpLWvV01y9G6Rm4NbX4i6RParbHrMptfSMh4 +wZ5T7tawm3P4BdujHtxALr0TRiXDlg/HluF8gaHLQ7J+xJKoE6eJynCDLdUIExLd +jrk6hNknrcguASf2Iq5uJgkavUrdoyGHMqhrEBL+2Z62RHmrzP8+dTAXuy9ckgEx +oW9tg+rAmjOtJzGjhRLS3XaTEdKBh5xynUV82l+S8j89FVTva7wpEB9FutLDziBW +Xh5NanQ7w+373ZPcKF7D1Xyh5DmifIN0ppMCowx2EziKQdlE3KDwtuOYxU4lCYTA +9i40J/KtU3wSxZyTSza6H866PDo8IRN6Yy/QYDePOSmH2SWx8gHkeqzatD6yE5CB +o0Iw73cyFDBT/5OOeUqgfFR2WKEvWt+6aTI= +-----END CERTIFICATE----- diff --git a/certs/server.csr b/certs/server.csr new file mode 100644 index 0000000..e93b948 --- /dev/null +++ b/certs/server.csr @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICWzCCAUMCAQAwFjEUMBIGA1UEAwwLcGhvbmUubG9jYWwwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC3k5yY44X6zz2hj1oZcaScLVjl5CclTwyu5RzP +3XFuW0LxMaTB7w1SINOHeq5Z8TvmcTAH89vSoSCLG7kyO41uefqEtHHlCLC2e9vc +YnjGd9VnwmfzqD4B4/gXkFhHnjEL72H2plv5S2TVHUQUAHRBDjN79MoKpTCXbI8T +q3vJkyI11qG1sBlBpdJqix/H+wqNHn+TeQOp+dH7qyDgz3ilL1VSLV8+qMrjJxsC +pojxFWtOiwE23vM0LdVHVz+zhv3D2ODgjEIcGWWmJmC5HhQ6ph34+56kKNXtl63Z +Zs23XAZnDYJ8L7kifhauc9K81UI6VoydZ0tnfiKLcrijfXSnAgMBAAGgADANBgkq +hkiG9w0BAQsFAAOCAQEAaFZNpp7JKVJAhD0yjVccyE9q9qepwFO4dCAb0U+Lf930 +sIBcALa8XYMbdcKB614Tcku4k/153TYAhY91XfbRk2CY/yrxYNZfj6nWhR//DnMX +CvWLOcziFoS2QY8PZzu/XcSVVekuE24zX5vJnLgFmCNesTfTRZGfilhCIcnT542l +g1CuacY1HZ/HOigFWQ3eLByxOwd2Ty7z6iOUrDofTJL914dIkBvtyDIiQyGdx57P +a40itGI8Xpt4LKpMoNC5wdr4r+91mXga27y8FHUvlcypXYqAyaatyvc1ie141R1+ +RPzljUx3ZE/FvkbcQ2VJSYgmCEDkCogJtNyZDcbFSw== +-----END CERTIFICATE REQUEST----- diff --git a/certs/server.key b/certs/server.key new file mode 100644 index 0000000..ef25e6f --- /dev/null +++ b/certs/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC3k5yY44X6zz2h +j1oZcaScLVjl5CclTwyu5RzP3XFuW0LxMaTB7w1SINOHeq5Z8TvmcTAH89vSoSCL +G7kyO41uefqEtHHlCLC2e9vcYnjGd9VnwmfzqD4B4/gXkFhHnjEL72H2plv5S2TV +HUQUAHRBDjN79MoKpTCXbI8Tq3vJkyI11qG1sBlBpdJqix/H+wqNHn+TeQOp+dH7 +qyDgz3ilL1VSLV8+qMrjJxsCpojxFWtOiwE23vM0LdVHVz+zhv3D2ODgjEIcGWWm +JmC5HhQ6ph34+56kKNXtl63ZZs23XAZnDYJ8L7kifhauc9K81UI6VoydZ0tnfiKL +crijfXSnAgMBAAECggEAFNlD2K1GYxS0SCvyrJ8WddJEnV9K/ta6dwZ4PjdFEaZ1 +tNTRhGD031XxEBrjwD1Ac0x5iv2sAoDgJExSvRy8VEawJ/AiW0OgZiks8BuS4d9s +MiuHCMobuLU+UNbI3+FcaqGWO5/rJCXm3rlDKQ5gJZ9KQ5Kqe4kFOr1OhopcNak5 +yAyhlm/uo/au9Y+H/ZhwDAsL0HgMRmeBTp4o4pmvtNOUXr+vPypJwGteISeHkH4K +DZ5XlAzK2b6lttU2wazb82jlRKQs044vt3H5hZFw8ihKgfwnWvJE2CVfsT7suVPj +IGJ+vl/NxgwWAs+g5tgLHmbVvwdE2svPafAYwwRWkQKBgQDn6DlV6XGDh0mEvvFb +lobbHQg9XC1Z4VJQpnpnFwdYi8ivRJraZzaAZ5HpItj5035dx0rDL63QgcrNtG+k +4qUZxvg9Qwan9qHAP6EF4Nmc/DxY/C3bKVau4FOuAKP3QIjnLMPIbgpm46xKL0Lm ++QvCRXt5gmSpb2f2Y532o5jfqQKBgQDKpf6lSmLv+7vayJQbT4jVndz2QTS7EZz0 +uQ58VURiDPmB5JJqd0t2T8tQMULsbYCtcPMWkFhq8RJBRY2tOI+zNPI51UKwM+DZ +qKNh8iIw4s/UhkG7Hk/fW+JtVpcWtHBkuuclpX+ImBzzBcF1HwD61WLcJb2s3rb+ +upNrC7ijzwKBgCyp7xD6yt/4GFK2q6lmDkb5CnM2440h9kaOKZjdOI73LQmEfwZ9 +RmPojpOGIJ/M+dN55I4/CozT0olXrQDmncGfLoQt+oQy3eg22P5W1dKESeMhfntt +NMKtdbBXczzVcwiC+JoLh3lVdAM9ovb1FCywUR2P3W88g5tSiEPOMk0xAoGBAI+U +YQH33HWwZH81EkrvZUIFIaOQu623Yv+5hlvthg/6pWW071BjyaHAE4tz+7jMNRmu +J23bQ+oxd/+rKiCLLLWKU/1J7oAtoahV5bzl8/ezHBG2Nig/59OXpKxXZ6F8ow2b +p15zDhNXw1skvtce6nDOc8cLLoKnIf0FcKGtolrdAoGAOy1ruHSstC3Ln+MNrnCN ++D8WRjUN/5Chh69R4IjER2l2xGZUmjnFHk398l7TrS1YRVn0iuGb0KfBoUjC4iH3 +lTP/pdxUOhwvLR3Q7OztZrQGHZ9+FH6OE5U724i6fJ6PhHAAp1agFGlRSQ+IP2PC +vvCI/m3H+XLbZkvtFJ97WFE= +-----END PRIVATE KEY----- diff --git a/certs/server_ext.cnf b/certs/server_ext.cnf new file mode 100644 index 0000000..467aad1 --- /dev/null +++ b/certs/server_ext.cnf @@ -0,0 +1,10 @@ +authorityKeyIdentifier=keyid,issuer +basicConstraints=CA:FALSE +keyUsage=digitalSignature,keyEncipherment +extendedKeyUsage=serverAuth +subjectAltName=@alt_names +[alt_names] +DNS.1=api.phone.local +DNS.2=photo.phone.local +IP.1=192.168.31.108 +IP.2=127.0.0.1 diff --git a/certs/win-trace.txt b/certs/win-trace.txt new file mode 100644 index 0000000..24ccc64 --- /dev/null +++ b/certs/win-trace.txt @@ -0,0 +1,6 @@ +== Info: Added api.phone.local:4433:192.168.31.108 to DNS cache +== Info: Hostname api.phone.local was found in DNS cache +== Info: Trying 192.168.31.108:4433... +== Info: schannel: disabled automatic use of client certificate +== Info: schannel: AcquireCredentialsHandle failed: SEC_E_ALGORITHM_MISMATCH (0x80090331) - , .. . +== Info: closing connection #0 diff --git a/device/device.go b/device/device.go new file mode 100644 index 0000000..239d92e --- /dev/null +++ b/device/device.go @@ -0,0 +1,65 @@ +package device + +import ( + "log" + "net" + "sync" + + tunnelpb "relay/proto/tunnel" +) + +type Device struct { + stream tunnelpb.TunnelService_TunnelServer + sendCh chan *tunnelpb.Frame + done chan struct{} + closeOnce sync.Once + + streamsMu sync.Mutex + streams map[uint32]net.Conn + streamDone map[uint32]chan struct{} + nextID uint32 +} + +func NewDevice(stream tunnelpb.TunnelService_TunnelServer) *Device { + d := &Device{ + stream: stream, + sendCh: make(chan *tunnelpb.Frame, 128), // backpressure here + done: make(chan struct{}), + streams: make(map[uint32]net.Conn), + streamDone: make(map[uint32]chan struct{}), + nextID: 1, + } + + go d.writer() + return d +} + +func (d *Device) Close() { + d.closeOnce.Do(func() { + close(d.done) + }) +} + +func (d *Device) SendFrame(f *tunnelpb.Frame) { + select { + case d.sendCh <- f: + case <-d.done: + default: + log.Println("device backpressure: drop frame") + } +} + +func (d *Device) writer() { + for { + select { + case f := <-d.sendCh: + if err := d.stream.Send(f); err != nil { + log.Println("device send error:", err) + d.Close() + return + } + case <-d.done: + return + } + } +} diff --git a/device/streams.go b/device/streams.go new file mode 100644 index 0000000..77fab88 --- /dev/null +++ b/device/streams.go @@ -0,0 +1,49 @@ +package device + +import ( + "net" +) + +func (d *Device) AllocateStreamID() uint32 { + d.streamsMu.Lock() + defer d.streamsMu.Unlock() + + id := d.nextID + d.nextID++ + return id +} + +func (d *Device) AddStream(id uint32, conn net.Conn) chan struct{} { + d.streamsMu.Lock() + defer d.streamsMu.Unlock() + + ch := make(chan struct{}) + d.streams[id] = conn + d.streamDone[id] = ch + return ch +} + +func (d *Device) RemoveStream(id uint32) { + d.streamsMu.Lock() + defer d.streamsMu.Unlock() + + if c, ok := d.streams[id]; ok { + _ = c.Close() + delete(d.streams, id) + } + if ch, ok := d.streamDone[id]; ok { + close(ch) + delete(d.streamDone, id) + } +} + +func (d *Device) GetClient(id uint32) (net.Conn, bool) { + d.streamsMu.Lock() + defer d.streamsMu.Unlock() + c, ok := d.streams[id] + return c, ok +} + +func (d *Device) Done() <-chan struct{} { + return d.done +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..74f20c6 --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module relay + +go 1.24.0 + +require ( + google.golang.org/grpc v1.77.0 + google.golang.org/protobuf v1.36.11 +) + +require ( + golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.30.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a237160 --- /dev/null +++ b/go.sum @@ -0,0 +1,36 @@ +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= diff --git a/grpcserver/control.go b/grpcserver/control.go new file mode 100644 index 0000000..84087f0 --- /dev/null +++ b/grpcserver/control.go @@ -0,0 +1,18 @@ +package grpcserver + +import ( + "context" + + controlpb "relay/proto/control" +) + +type ControlServiceImpl struct { + controlpb.UnimplementedControlServiceServer +} + +func (s *ControlServiceImpl) RegisterDevice( + ctx context.Context, + req *controlpb.RegisterRequest, +) (*controlpb.RegisterResponse, error) { + return &controlpb.RegisterResponse{Success: true}, nil +} diff --git a/grpcserver/server.go b/grpcserver/server.go new file mode 100644 index 0000000..b6017da --- /dev/null +++ b/grpcserver/server.go @@ -0,0 +1,36 @@ +package grpcserver + +import ( + "crypto/tls" + "log" + "net" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/keepalive" + + controlpb "relay/proto/control" + tunnelpb "relay/proto/tunnel" +) + +func Serve(addr string, tlsCfg *tls.Config) { + grpcServer := grpc.NewServer( + grpc.Creds(credentials.NewTLS(tlsCfg)), + grpc.KeepaliveParams(keepalive.ServerParameters{ + Time: 30 * time.Second, + Timeout: 10 * time.Second, + }), + ) + + tunnelpb.RegisterTunnelServiceServer(grpcServer, &TunnelServiceImpl{}) + controlpb.RegisterControlServiceServer(grpcServer, &ControlServiceImpl{}) + + ln, err := net.Listen("tcp", addr) + if err != nil { + log.Fatal(err) + } + + log.Println("gRPC listening on", addr) + log.Fatal(grpcServer.Serve(ln)) +} diff --git a/grpcserver/tunnel.go b/grpcserver/tunnel.go new file mode 100644 index 0000000..5873fc5 --- /dev/null +++ b/grpcserver/tunnel.go @@ -0,0 +1,94 @@ +package grpcserver + +import ( + "log" + "strings" + + "relay/device" + "relay/registry" + + tunnelpb "relay/proto/tunnel" +) + +type TunnelServiceImpl struct { + tunnelpb.UnimplementedTunnelServiceServer +} + +func (s *TunnelServiceImpl) Tunnel(stream tunnelpb.TunnelService_TunnelServer) error { + dev := device.NewDevice(stream) + log.Println("Device connected") + + defer func() { + log.Println("Device disconnected") + dev.Close() + registry.Global.Mu.Lock() + for d, v := range registry.Global.Domains { + if v == dev { + registry.Global.Domains[d] = nil + log.Println("Domain unbound from device:", d) + } + } + registry.Global.Mu.Unlock() + }() + + for { + frame, err := stream.Recv() + if err != nil { + return err + } + + switch frame.Type { + + // case tunnelpb.FrameType_FRAME_OPEN: + // domain := strings.ToLower(string(frame.Payload)) + // log.Println("BIND request from device for domain:", domain) + // registry.Global.Mu.Lock() + // if _, ok := registry.Global.Domains[domain]; ok { + // registry.Global.Domains[domain] = dev + // log.Println("Domain bound to device:", domain) + // } else { + // log.Println("Bind rejected, domain not registered:", domain) + // } + // registry.Global.Mu.Unlock() + + case tunnelpb.FrameType_FRAME_BIND_REQUEST: + domain := strings.ToLower(string(frame.Payload)) + log.Println("BIND request from device for domain:", domain) + + registry.Global.Mu.Lock() + if _, ok := registry.Global.Domains[domain]; ok { + registry.Global.Domains[domain] = dev + registry.Global.Mu.Unlock() + + log.Println("Domain bound to device:", domain) + dev.SendFrame(&tunnelpb.Frame{ + Type: tunnelpb.FrameType_FRAME_BIND_OK, + Payload: []byte(domain), + }) + } else { + registry.Global.Mu.Unlock() + + log.Println("Bind rejected, domain not registered:", domain) + dev.SendFrame(&tunnelpb.Frame{ + Type: tunnelpb.FrameType_FRAME_BIND_REJECTED, + Payload: []byte(domain), + }) + } + + case tunnelpb.FrameType_FRAME_DATA: + if c, ok := dev.GetClient(frame.StreamId); ok { + if _, err := c.Write(frame.Payload); err != nil { + dev.RemoveStream(frame.StreamId) + } + } + + case tunnelpb.FrameType_FRAME_CLOSE: + dev.RemoveStream(frame.StreamId) + + case tunnelpb.FrameType_FRAME_PING: + dev.SendFrame(&tunnelpb.Frame{ + Type: tunnelpb.FrameType_FRAME_PONG, + }) + } + } +} diff --git a/ingress/handler.go b/ingress/handler.go new file mode 100644 index 0000000..065b868 --- /dev/null +++ b/ingress/handler.go @@ -0,0 +1,82 @@ +package ingress + +import ( + "log" + "net" + + "relay/registry" + "relay/util" + + tunnelpb "relay/proto/tunnel" +) + +func handleClientTCP(conn net.Conn) { + defer conn.Close() + + sni, hello, err := peekClientHello(conn) + if err != nil { + log.Println("peek failed:", err) + return + } + + log.Println("Client SNI:", sni) + + registry.Global.Mu.Lock() + dev := registry.Global.Domains[sni] + log.Println("Known domains:", util.Keys(registry.Global.Domains)) + registry.Global.Mu.Unlock() + + if dev == nil { + log.Println("No device bound for domain:", sni) + conn.Write([]byte("HTTP/1.1 503 No device\r\n\r\n")) + return + } + + streamID := dev.AllocateStreamID() + done := dev.AddStream(streamID, conn) + + log.Printf("Client [%s] → device (stream %d)\n", sni, streamID) + + dev.SendFrame(&tunnelpb.Frame{ + Type: tunnelpb.FrameType_FRAME_OPEN, + StreamId: streamID, + Payload: []byte(sni), + }) + + dev.SendFrame(&tunnelpb.Frame{ + Type: tunnelpb.FrameType_FRAME_DATA, + StreamId: streamID, + Payload: hello, + }) + + go func() { + buf := util.BufPool.Get().([]byte) + defer util.BufPool.Put(buf) + + for { + n, err := conn.Read(buf) + if err != nil { + dev.SendFrame(&tunnelpb.Frame{ + Type: tunnelpb.FrameType_FRAME_CLOSE, + StreamId: streamID, + }) + dev.RemoveStream(streamID) + return + } + + dev.SendFrame(&tunnelpb.Frame{ + Type: tunnelpb.FrameType_FRAME_DATA, + StreamId: streamID, + Payload: append([]byte(nil), buf[:n]...), + }) + } + }() + + select { + case <-done: + case <-dev.Done(): + } + + dev.RemoveStream(streamID) + +} diff --git a/ingress/listener.go b/ingress/listener.go new file mode 100644 index 0000000..ec0c634 --- /dev/null +++ b/ingress/listener.go @@ -0,0 +1,23 @@ +package ingress + +import ( + "log" + "net" +) + +func Listen(addr string) { + ln, err := net.Listen("tcp", addr) + if err != nil { + log.Fatal(err) + } + log.Println("HTTPS passthrough listening on", addr) + + for { + c, err := ln.Accept() + if err != nil { + log.Println("accept error:", err) + continue + } + go handleClientTCP(c) + } +} diff --git a/ingress/sni.go b/ingress/sni.go new file mode 100644 index 0000000..a4769dd --- /dev/null +++ b/ingress/sni.go @@ -0,0 +1,73 @@ +package ingress + +import ( + "bufio" + "encoding/binary" + "errors" + "io" + "strings" +) + +func peekClientHello(r io.Reader) (sni string, hello []byte, err error) { + br := bufio.NewReader(r) + + hdr, err := br.Peek(5) + if err != nil { + return "", nil, err + } + if hdr[0] != 0x16 { + return "", nil, errors.New("not TLS handshake") + } + + recLen := int(binary.BigEndian.Uint16(hdr[3:5])) + total := 5 + recLen + + hello, err = br.Peek(total) + if err != nil { + return "", nil, err + } + + data := hello[5:] + if data[0] != 0x01 { + return "", hello, errors.New("not ClientHello") + } + + i := 4 + i += 2 + 32 + + sidLen := int(data[i]) + i += 1 + sidLen + + csLen := int(binary.BigEndian.Uint16(data[i:])) + i += 2 + csLen + + compLen := int(data[i]) + i += 1 + compLen + + extLen := int(binary.BigEndian.Uint16(data[i:])) + i += 2 + end := i + extLen + + for i+4 <= end { + typ := binary.BigEndian.Uint16(data[i:]) + sz := int(binary.BigEndian.Uint16(data[i+2:])) + i += 4 + + if typ == 0x00 { + l := int(binary.BigEndian.Uint16(data[i:])) + j := i + 2 + for j+3 < i+l { + nameType := data[j] + nameLen := int(binary.BigEndian.Uint16(data[j+1:])) + if nameType == 0 { + sni = strings.ToLower(string(data[j+3 : j+3+nameLen])) + return sni, hello, nil + } + j += 3 + nameLen + } + } + i += sz + } + + return "", hello, nil +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..6313889 --- /dev/null +++ b/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "log" + "os" + + "relay/admin" + "relay/grpcserver" + "relay/ingress" + "relay/tlsutil" +) + +func main() { + admin.ApiKey = os.Getenv("SECRET_API_KEY") + if admin.ApiKey == "" { + log.Fatal("SECRET_API_KEY is not set") + } + + tlsCfg, err := tlsutil.GRPCTLSConfig() + if err != nil { + log.Fatal(err) + } + + admin.InitLogger() + + go admin.Serve(":8443") + go grpcserver.Serve(":50051", tlsCfg) + + ingress.Listen(":443") +} diff --git a/proto/control.proto b/proto/control.proto new file mode 100644 index 0000000..e8d6183 --- /dev/null +++ b/proto/control.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; + +package control; + +option go_package = "relay/proto/control;controlpb"; +option java_package = "com.projects.httpsserverapp.control.v1"; +option java_multiple_files = true; + +service ControlService { + + // Регистрация устройства и доменов + rpc RegisterDevice(RegisterRequest) + returns (RegisterResponse); + + // Получить список доменов + rpc ListDomains(ListDomainsRequest) + returns (ListDomainsResponse); + + // Отвязать домен + rpc UnregisterDomain(UnregisterDomainRequest) + returns (UnregisterDomainResponse); +} + +message RegisterRequest { + string device_id = 1; + repeated string domains = 2; +} + +message RegisterResponse { + bool success = 1; + string message = 2; +} + +message ListDomainsRequest {} + +message ListDomainsResponse { + repeated string domains = 1; +} + +message UnregisterDomainRequest { + string domain = 1; +} + +message UnregisterDomainResponse { + bool success = 1; +} diff --git a/proto/control/control.pb.go b/proto/control/control.pb.go new file mode 100644 index 0000000..37b3740 --- /dev/null +++ b/proto/control/control.pb.go @@ -0,0 +1,492 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc v6.33.1 +// source: control.proto + +package controlpb + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type RegisterRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DeviceId string `protobuf:"bytes,1,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"` + Domains []string `protobuf:"bytes,2,rep,name=domains,proto3" json:"domains,omitempty"` +} + +func (x *RegisterRequest) Reset() { + *x = RegisterRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_control_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RegisterRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterRequest) ProtoMessage() {} + +func (x *RegisterRequest) ProtoReflect() protoreflect.Message { + mi := &file_control_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterRequest.ProtoReflect.Descriptor instead. +func (*RegisterRequest) Descriptor() ([]byte, []int) { + return file_control_proto_rawDescGZIP(), []int{0} +} + +func (x *RegisterRequest) GetDeviceId() string { + if x != nil { + return x.DeviceId + } + return "" +} + +func (x *RegisterRequest) GetDomains() []string { + if x != nil { + return x.Domains + } + return nil +} + +type RegisterResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *RegisterResponse) Reset() { + *x = RegisterResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_control_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RegisterResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterResponse) ProtoMessage() {} + +func (x *RegisterResponse) ProtoReflect() protoreflect.Message { + mi := &file_control_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterResponse.ProtoReflect.Descriptor instead. +func (*RegisterResponse) Descriptor() ([]byte, []int) { + return file_control_proto_rawDescGZIP(), []int{1} +} + +func (x *RegisterResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *RegisterResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type ListDomainsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ListDomainsRequest) Reset() { + *x = ListDomainsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_control_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListDomainsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListDomainsRequest) ProtoMessage() {} + +func (x *ListDomainsRequest) ProtoReflect() protoreflect.Message { + mi := &file_control_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListDomainsRequest.ProtoReflect.Descriptor instead. +func (*ListDomainsRequest) Descriptor() ([]byte, []int) { + return file_control_proto_rawDescGZIP(), []int{2} +} + +type ListDomainsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Domains []string `protobuf:"bytes,1,rep,name=domains,proto3" json:"domains,omitempty"` +} + +func (x *ListDomainsResponse) Reset() { + *x = ListDomainsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_control_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListDomainsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListDomainsResponse) ProtoMessage() {} + +func (x *ListDomainsResponse) ProtoReflect() protoreflect.Message { + mi := &file_control_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListDomainsResponse.ProtoReflect.Descriptor instead. +func (*ListDomainsResponse) Descriptor() ([]byte, []int) { + return file_control_proto_rawDescGZIP(), []int{3} +} + +func (x *ListDomainsResponse) GetDomains() []string { + if x != nil { + return x.Domains + } + return nil +} + +type UnregisterDomainRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Domain string `protobuf:"bytes,1,opt,name=domain,proto3" json:"domain,omitempty"` +} + +func (x *UnregisterDomainRequest) Reset() { + *x = UnregisterDomainRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_control_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UnregisterDomainRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UnregisterDomainRequest) ProtoMessage() {} + +func (x *UnregisterDomainRequest) ProtoReflect() protoreflect.Message { + mi := &file_control_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UnregisterDomainRequest.ProtoReflect.Descriptor instead. +func (*UnregisterDomainRequest) Descriptor() ([]byte, []int) { + return file_control_proto_rawDescGZIP(), []int{4} +} + +func (x *UnregisterDomainRequest) GetDomain() string { + if x != nil { + return x.Domain + } + return "" +} + +type UnregisterDomainResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +} + +func (x *UnregisterDomainResponse) Reset() { + *x = UnregisterDomainResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_control_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UnregisterDomainResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UnregisterDomainResponse) ProtoMessage() {} + +func (x *UnregisterDomainResponse) ProtoReflect() protoreflect.Message { + mi := &file_control_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UnregisterDomainResponse.ProtoReflect.Descriptor instead. +func (*UnregisterDomainResponse) Descriptor() ([]byte, []int) { + return file_control_proto_rawDescGZIP(), []int{5} +} + +func (x *UnregisterDomainResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +var File_control_proto protoreflect.FileDescriptor + +var file_control_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x22, 0x48, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x73, 0x22, 0x46, 0x0a, 0x10, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x14, 0x0a, 0x12, 0x4c, 0x69, + 0x73, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x22, 0x2f, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x73, 0x22, 0x31, 0x0a, 0x17, 0x55, 0x6e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x44, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x34, 0x0a, 0x18, 0x55, 0x6e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x32, 0xfa, 0x01, 0x0a, 0x0e, 0x43, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, + 0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, + 0x18, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x73, 0x12, 0x1b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1c, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, + 0x0a, 0x10, 0x55, 0x6e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x12, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x2e, 0x55, 0x6e, 0x72, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x2e, 0x55, + 0x6e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x49, 0x0a, 0x26, 0x63, 0x6f, 0x6d, 0x2e, 0x70, + 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x73, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x61, 0x70, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x2e, 0x76, + 0x31, 0x50, 0x01, 0x5a, 0x1d, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x3b, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_control_proto_rawDescOnce sync.Once + file_control_proto_rawDescData = file_control_proto_rawDesc +) + +func file_control_proto_rawDescGZIP() []byte { + file_control_proto_rawDescOnce.Do(func() { + file_control_proto_rawDescData = protoimpl.X.CompressGZIP(file_control_proto_rawDescData) + }) + return file_control_proto_rawDescData +} + +var file_control_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_control_proto_goTypes = []interface{}{ + (*RegisterRequest)(nil), // 0: control.RegisterRequest + (*RegisterResponse)(nil), // 1: control.RegisterResponse + (*ListDomainsRequest)(nil), // 2: control.ListDomainsRequest + (*ListDomainsResponse)(nil), // 3: control.ListDomainsResponse + (*UnregisterDomainRequest)(nil), // 4: control.UnregisterDomainRequest + (*UnregisterDomainResponse)(nil), // 5: control.UnregisterDomainResponse +} +var file_control_proto_depIdxs = []int32{ + 0, // 0: control.ControlService.RegisterDevice:input_type -> control.RegisterRequest + 2, // 1: control.ControlService.ListDomains:input_type -> control.ListDomainsRequest + 4, // 2: control.ControlService.UnregisterDomain:input_type -> control.UnregisterDomainRequest + 1, // 3: control.ControlService.RegisterDevice:output_type -> control.RegisterResponse + 3, // 4: control.ControlService.ListDomains:output_type -> control.ListDomainsResponse + 5, // 5: control.ControlService.UnregisterDomain:output_type -> control.UnregisterDomainResponse + 3, // [3:6] is the sub-list for method output_type + 0, // [0:3] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_control_proto_init() } +func file_control_proto_init() { + if File_control_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_control_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RegisterRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_control_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RegisterResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_control_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListDomainsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_control_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListDomainsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_control_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UnregisterDomainRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_control_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UnregisterDomainResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_control_proto_rawDesc, + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_control_proto_goTypes, + DependencyIndexes: file_control_proto_depIdxs, + MessageInfos: file_control_proto_msgTypes, + }.Build() + File_control_proto = out.File + file_control_proto_rawDesc = nil + file_control_proto_goTypes = nil + file_control_proto_depIdxs = nil +} diff --git a/proto/control/control_grpc.pb.go b/proto/control/control_grpc.pb.go new file mode 100644 index 0000000..3e66efa --- /dev/null +++ b/proto/control/control_grpc.pb.go @@ -0,0 +1,189 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v6.33.1 +// source: control.proto + +package controlpb + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + ControlService_RegisterDevice_FullMethodName = "/control.ControlService/RegisterDevice" + ControlService_ListDomains_FullMethodName = "/control.ControlService/ListDomains" + ControlService_UnregisterDomain_FullMethodName = "/control.ControlService/UnregisterDomain" +) + +// ControlServiceClient is the client API for ControlService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ControlServiceClient interface { + // Регистрация устройства и доменов + RegisterDevice(ctx context.Context, in *RegisterRequest, opts ...grpc.CallOption) (*RegisterResponse, error) + // Получить список доменов + ListDomains(ctx context.Context, in *ListDomainsRequest, opts ...grpc.CallOption) (*ListDomainsResponse, error) + // Отвязать домен + UnregisterDomain(ctx context.Context, in *UnregisterDomainRequest, opts ...grpc.CallOption) (*UnregisterDomainResponse, error) +} + +type controlServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewControlServiceClient(cc grpc.ClientConnInterface) ControlServiceClient { + return &controlServiceClient{cc} +} + +func (c *controlServiceClient) RegisterDevice(ctx context.Context, in *RegisterRequest, opts ...grpc.CallOption) (*RegisterResponse, error) { + out := new(RegisterResponse) + err := c.cc.Invoke(ctx, ControlService_RegisterDevice_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *controlServiceClient) ListDomains(ctx context.Context, in *ListDomainsRequest, opts ...grpc.CallOption) (*ListDomainsResponse, error) { + out := new(ListDomainsResponse) + err := c.cc.Invoke(ctx, ControlService_ListDomains_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *controlServiceClient) UnregisterDomain(ctx context.Context, in *UnregisterDomainRequest, opts ...grpc.CallOption) (*UnregisterDomainResponse, error) { + out := new(UnregisterDomainResponse) + err := c.cc.Invoke(ctx, ControlService_UnregisterDomain_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ControlServiceServer is the server API for ControlService service. +// All implementations must embed UnimplementedControlServiceServer +// for forward compatibility +type ControlServiceServer interface { + // Регистрация устройства и доменов + RegisterDevice(context.Context, *RegisterRequest) (*RegisterResponse, error) + // Получить список доменов + ListDomains(context.Context, *ListDomainsRequest) (*ListDomainsResponse, error) + // Отвязать домен + UnregisterDomain(context.Context, *UnregisterDomainRequest) (*UnregisterDomainResponse, error) + mustEmbedUnimplementedControlServiceServer() +} + +// UnimplementedControlServiceServer must be embedded to have forward compatible implementations. +type UnimplementedControlServiceServer struct { +} + +func (UnimplementedControlServiceServer) RegisterDevice(context.Context, *RegisterRequest) (*RegisterResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RegisterDevice not implemented") +} +func (UnimplementedControlServiceServer) ListDomains(context.Context, *ListDomainsRequest) (*ListDomainsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListDomains not implemented") +} +func (UnimplementedControlServiceServer) UnregisterDomain(context.Context, *UnregisterDomainRequest) (*UnregisterDomainResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UnregisterDomain not implemented") +} +func (UnimplementedControlServiceServer) mustEmbedUnimplementedControlServiceServer() {} + +// UnsafeControlServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ControlServiceServer will +// result in compilation errors. +type UnsafeControlServiceServer interface { + mustEmbedUnimplementedControlServiceServer() +} + +func RegisterControlServiceServer(s grpc.ServiceRegistrar, srv ControlServiceServer) { + s.RegisterService(&ControlService_ServiceDesc, srv) +} + +func _ControlService_RegisterDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RegisterRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ControlServiceServer).RegisterDevice(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ControlService_RegisterDevice_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ControlServiceServer).RegisterDevice(ctx, req.(*RegisterRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ControlService_ListDomains_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListDomainsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ControlServiceServer).ListDomains(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ControlService_ListDomains_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ControlServiceServer).ListDomains(ctx, req.(*ListDomainsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ControlService_UnregisterDomain_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UnregisterDomainRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ControlServiceServer).UnregisterDomain(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ControlService_UnregisterDomain_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ControlServiceServer).UnregisterDomain(ctx, req.(*UnregisterDomainRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// ControlService_ServiceDesc is the grpc.ServiceDesc for ControlService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ControlService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "control.ControlService", + HandlerType: (*ControlServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "RegisterDevice", + Handler: _ControlService_RegisterDevice_Handler, + }, + { + MethodName: "ListDomains", + Handler: _ControlService_ListDomains_Handler, + }, + { + MethodName: "UnregisterDomain", + Handler: _ControlService_UnregisterDomain_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "control.proto", +} diff --git a/proto/tunnel.proto b/proto/tunnel.proto new file mode 100644 index 0000000..dcff8c3 --- /dev/null +++ b/proto/tunnel.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +package tunnel; + +option go_package = "relay/proto/tunnel;tunnelpb"; +option java_package = "com.projects.httpsserverapp.tunnel.v1"; +option java_multiple_files = true; + +service TunnelService { + rpc Tunnel(stream Frame) returns (stream Frame); +} + +enum FrameType { + FRAME_UNSPECIFIED = 0; + + FRAME_OPEN = 1; // открыть TCP stream + FRAME_DATA = 2; + FRAME_CLOSE = 3; + + FRAME_PING = 4; + FRAME_PONG = 5; + + FRAME_BIND_REQUEST = 10; // device → relay + FRAME_BIND_OK = 11; // relay → device + FRAME_BIND_REJECTED = 12; // relay → device +} + +message Frame { + FrameType type = 1; + uint32 stream_id = 2; + bytes payload = 3; +} \ No newline at end of file diff --git a/proto/tunnel/tunnel.pb.go b/proto/tunnel/tunnel.pb.go new file mode 100644 index 0000000..8f6f747 --- /dev/null +++ b/proto/tunnel/tunnel.pb.go @@ -0,0 +1,254 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc v6.33.1 +// source: tunnel.proto + +package tunnelpb + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type FrameType int32 + +const ( + FrameType_FRAME_UNSPECIFIED FrameType = 0 + FrameType_FRAME_OPEN FrameType = 1 // открыть TCP stream + FrameType_FRAME_DATA FrameType = 2 + FrameType_FRAME_CLOSE FrameType = 3 + FrameType_FRAME_PING FrameType = 4 + FrameType_FRAME_PONG FrameType = 5 + FrameType_FRAME_BIND_REQUEST FrameType = 10 // device → relay + FrameType_FRAME_BIND_OK FrameType = 11 // relay → device + FrameType_FRAME_BIND_REJECTED FrameType = 12 // relay → device +) + +// Enum value maps for FrameType. +var ( + FrameType_name = map[int32]string{ + 0: "FRAME_UNSPECIFIED", + 1: "FRAME_OPEN", + 2: "FRAME_DATA", + 3: "FRAME_CLOSE", + 4: "FRAME_PING", + 5: "FRAME_PONG", + 10: "FRAME_BIND_REQUEST", + 11: "FRAME_BIND_OK", + 12: "FRAME_BIND_REJECTED", + } + FrameType_value = map[string]int32{ + "FRAME_UNSPECIFIED": 0, + "FRAME_OPEN": 1, + "FRAME_DATA": 2, + "FRAME_CLOSE": 3, + "FRAME_PING": 4, + "FRAME_PONG": 5, + "FRAME_BIND_REQUEST": 10, + "FRAME_BIND_OK": 11, + "FRAME_BIND_REJECTED": 12, + } +) + +func (x FrameType) Enum() *FrameType { + p := new(FrameType) + *p = x + return p +} + +func (x FrameType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (FrameType) Descriptor() protoreflect.EnumDescriptor { + return file_tunnel_proto_enumTypes[0].Descriptor() +} + +func (FrameType) Type() protoreflect.EnumType { + return &file_tunnel_proto_enumTypes[0] +} + +func (x FrameType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use FrameType.Descriptor instead. +func (FrameType) EnumDescriptor() ([]byte, []int) { + return file_tunnel_proto_rawDescGZIP(), []int{0} +} + +type Frame struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type FrameType `protobuf:"varint,1,opt,name=type,proto3,enum=tunnel.FrameType" json:"type,omitempty"` + StreamId uint32 `protobuf:"varint,2,opt,name=stream_id,json=streamId,proto3" json:"stream_id,omitempty"` + Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"` +} + +func (x *Frame) Reset() { + *x = Frame{} + if protoimpl.UnsafeEnabled { + mi := &file_tunnel_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Frame) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Frame) ProtoMessage() {} + +func (x *Frame) ProtoReflect() protoreflect.Message { + mi := &file_tunnel_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Frame.ProtoReflect.Descriptor instead. +func (*Frame) Descriptor() ([]byte, []int) { + return file_tunnel_proto_rawDescGZIP(), []int{0} +} + +func (x *Frame) GetType() FrameType { + if x != nil { + return x.Type + } + return FrameType_FRAME_UNSPECIFIED +} + +func (x *Frame) GetStreamId() uint32 { + if x != nil { + return x.StreamId + } + return 0 +} + +func (x *Frame) GetPayload() []byte { + if x != nil { + return x.Payload + } + return nil +} + +var File_tunnel_proto protoreflect.FileDescriptor + +var file_tunnel_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, + 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x22, 0x65, 0x0a, 0x05, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, + 0x25, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, + 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x2a, 0xb7, 0x01, + 0x0a, 0x09, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x46, + 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, + 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x4f, 0x50, 0x45, 0x4e, + 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, + 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x43, 0x4c, 0x4f, 0x53, + 0x45, 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x50, 0x49, 0x4e, + 0x47, 0x10, 0x04, 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x50, 0x4f, 0x4e, + 0x47, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x42, 0x49, 0x4e, + 0x44, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x10, 0x0a, 0x12, 0x11, 0x0a, 0x0d, 0x46, + 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x42, 0x49, 0x4e, 0x44, 0x5f, 0x4f, 0x4b, 0x10, 0x0b, 0x12, 0x17, + 0x0a, 0x13, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x42, 0x49, 0x4e, 0x44, 0x5f, 0x52, 0x45, 0x4a, + 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x0c, 0x32, 0x3b, 0x0a, 0x0d, 0x54, 0x75, 0x6e, 0x6e, 0x65, + 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x54, 0x75, 0x6e, 0x6e, + 0x65, 0x6c, 0x12, 0x0d, 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x46, 0x72, 0x61, 0x6d, + 0x65, 0x1a, 0x0d, 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65, + 0x28, 0x01, 0x30, 0x01, 0x42, 0x46, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x6a, + 0x65, 0x63, 0x74, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x73, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x61, 0x70, 0x70, 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x50, 0x01, 0x5a, + 0x1b, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x75, 0x6e, + 0x6e, 0x65, 0x6c, 0x3b, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_tunnel_proto_rawDescOnce sync.Once + file_tunnel_proto_rawDescData = file_tunnel_proto_rawDesc +) + +func file_tunnel_proto_rawDescGZIP() []byte { + file_tunnel_proto_rawDescOnce.Do(func() { + file_tunnel_proto_rawDescData = protoimpl.X.CompressGZIP(file_tunnel_proto_rawDescData) + }) + return file_tunnel_proto_rawDescData +} + +var file_tunnel_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_tunnel_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_tunnel_proto_goTypes = []interface{}{ + (FrameType)(0), // 0: tunnel.FrameType + (*Frame)(nil), // 1: tunnel.Frame +} +var file_tunnel_proto_depIdxs = []int32{ + 0, // 0: tunnel.Frame.type:type_name -> tunnel.FrameType + 1, // 1: tunnel.TunnelService.Tunnel:input_type -> tunnel.Frame + 1, // 2: tunnel.TunnelService.Tunnel:output_type -> tunnel.Frame + 2, // [2:3] is the sub-list for method output_type + 1, // [1:2] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_tunnel_proto_init() } +func file_tunnel_proto_init() { + if File_tunnel_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_tunnel_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Frame); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_tunnel_proto_rawDesc, + NumEnums: 1, + NumMessages: 1, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_tunnel_proto_goTypes, + DependencyIndexes: file_tunnel_proto_depIdxs, + EnumInfos: file_tunnel_proto_enumTypes, + MessageInfos: file_tunnel_proto_msgTypes, + }.Build() + File_tunnel_proto = out.File + file_tunnel_proto_rawDesc = nil + file_tunnel_proto_goTypes = nil + file_tunnel_proto_depIdxs = nil +} diff --git a/proto/tunnel/tunnel_grpc.pb.go b/proto/tunnel/tunnel_grpc.pb.go new file mode 100644 index 0000000..184844c --- /dev/null +++ b/proto/tunnel/tunnel_grpc.pb.go @@ -0,0 +1,141 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v6.33.1 +// source: tunnel.proto + +package tunnelpb + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + TunnelService_Tunnel_FullMethodName = "/tunnel.TunnelService/Tunnel" +) + +// TunnelServiceClient is the client API for TunnelService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type TunnelServiceClient interface { + Tunnel(ctx context.Context, opts ...grpc.CallOption) (TunnelService_TunnelClient, error) +} + +type tunnelServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewTunnelServiceClient(cc grpc.ClientConnInterface) TunnelServiceClient { + return &tunnelServiceClient{cc} +} + +func (c *tunnelServiceClient) Tunnel(ctx context.Context, opts ...grpc.CallOption) (TunnelService_TunnelClient, error) { + stream, err := c.cc.NewStream(ctx, &TunnelService_ServiceDesc.Streams[0], TunnelService_Tunnel_FullMethodName, opts...) + if err != nil { + return nil, err + } + x := &tunnelServiceTunnelClient{stream} + return x, nil +} + +type TunnelService_TunnelClient interface { + Send(*Frame) error + Recv() (*Frame, error) + grpc.ClientStream +} + +type tunnelServiceTunnelClient struct { + grpc.ClientStream +} + +func (x *tunnelServiceTunnelClient) Send(m *Frame) error { + return x.ClientStream.SendMsg(m) +} + +func (x *tunnelServiceTunnelClient) Recv() (*Frame, error) { + m := new(Frame) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// TunnelServiceServer is the server API for TunnelService service. +// All implementations must embed UnimplementedTunnelServiceServer +// for forward compatibility +type TunnelServiceServer interface { + Tunnel(TunnelService_TunnelServer) error + mustEmbedUnimplementedTunnelServiceServer() +} + +// UnimplementedTunnelServiceServer must be embedded to have forward compatible implementations. +type UnimplementedTunnelServiceServer struct { +} + +func (UnimplementedTunnelServiceServer) Tunnel(TunnelService_TunnelServer) error { + return status.Errorf(codes.Unimplemented, "method Tunnel not implemented") +} +func (UnimplementedTunnelServiceServer) mustEmbedUnimplementedTunnelServiceServer() {} + +// UnsafeTunnelServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to TunnelServiceServer will +// result in compilation errors. +type UnsafeTunnelServiceServer interface { + mustEmbedUnimplementedTunnelServiceServer() +} + +func RegisterTunnelServiceServer(s grpc.ServiceRegistrar, srv TunnelServiceServer) { + s.RegisterService(&TunnelService_ServiceDesc, srv) +} + +func _TunnelService_Tunnel_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(TunnelServiceServer).Tunnel(&tunnelServiceTunnelServer{stream}) +} + +type TunnelService_TunnelServer interface { + Send(*Frame) error + Recv() (*Frame, error) + grpc.ServerStream +} + +type tunnelServiceTunnelServer struct { + grpc.ServerStream +} + +func (x *tunnelServiceTunnelServer) Send(m *Frame) error { + return x.ServerStream.SendMsg(m) +} + +func (x *tunnelServiceTunnelServer) Recv() (*Frame, error) { + m := new(Frame) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// TunnelService_ServiceDesc is the grpc.ServiceDesc for TunnelService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var TunnelService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "tunnel.TunnelService", + HandlerType: (*TunnelServiceServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "Tunnel", + Handler: _TunnelService_Tunnel_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "tunnel.proto", +} diff --git a/registry/registry.go b/registry/registry.go new file mode 100644 index 0000000..81ce9e5 --- /dev/null +++ b/registry/registry.go @@ -0,0 +1,15 @@ +package registry + +import ( + "relay/device" + "sync" +) + +type Registry struct { + Mu sync.Mutex + Domains map[string]*device.Device +} + +var Global = &Registry{ + Domains: make(map[string]*device.Device), +} diff --git a/run_relay.bat b/run_relay.bat new file mode 100644 index 0000000..8777bc3 --- /dev/null +++ b/run_relay.bat @@ -0,0 +1,33 @@ +@echo off + +cd /d "%~dp0" + + +set SECRET_API_KEY=VNJ9L-BBBIM-6CJGZ-MO5AZ +set CERT_DIR=certs +set ADMIN_CERT=%CERT_DIR%\admin.crt +set ADMIN_KEY=%CERT_DIR%\admin.key + +if not exist "%ADMIN_CERT%" ( + echo [ERROR] admin.crt not found in %CERT_DIR% + goto :error +) + +if not exist "%ADMIN_KEY%" ( + echo [ERROR] admin.key not found in %CERT_DIR% + goto :error +) + +echo [OK] Certificates found +echo [INFO] Starting relay server... +echo. + +go run . + +goto :eof + +:error +echo. +echo Fix the error and try again. +pause +exit /b 1 diff --git a/tlsutil/config.go b/tlsutil/config.go new file mode 100644 index 0000000..2ca4202 --- /dev/null +++ b/tlsutil/config.go @@ -0,0 +1,29 @@ +package tlsutil + +import ( + "crypto/tls" + "crypto/x509" + "io/ioutil" +) + +func GRPCTLSConfig() (*tls.Config, error) { + cert, err := tls.LoadX509KeyPair("certs/server.crt", "certs/server.key") + if err != nil { + return nil, err + } + + caPEM, err := ioutil.ReadFile("certs/ca.crt") + if err != nil { + return nil, err + } + + caPool := x509.NewCertPool() + caPool.AppendCertsFromPEM(caPEM) + + return &tls.Config{ + Certificates: []tls.Certificate{cert}, + ClientCAs: caPool, + ClientAuth: tls.RequireAndVerifyClientCert, + MinVersion: tls.VersionTLS12, + }, nil +} diff --git a/util/bufpool.go b/util/bufpool.go new file mode 100644 index 0000000..5f17495 --- /dev/null +++ b/util/bufpool.go @@ -0,0 +1,9 @@ +package util + +import "sync" + +var BufPool = sync.Pool{ + New: func() any { + return make([]byte, 4096) + }, +} diff --git a/util/mapkeys.go b/util/mapkeys.go new file mode 100644 index 0000000..d24b851 --- /dev/null +++ b/util/mapkeys.go @@ -0,0 +1,11 @@ +package util + +import "relay/device" + +func Keys(m map[string]*device.Device) []string { + out := make([]string, 0, len(m)) + for k := range m { + out = append(out, k) + } + return out +}