Initial commit

This commit is contained in:
elisaveta9 2025-12-23 16:38:32 +03:00
commit b8f8e5607c
54 changed files with 2525 additions and 0 deletions

0
README.md Normal file
View File

22
admin.log Normal file
View File

@ -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

127
admin/admin.html Normal file
View File

@ -0,0 +1,127 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Relay Admin Panel</title>
<style>
body {
font-family: sans-serif;
max-width: 600px;
margin: 40px auto;
}
h1 {
margin-bottom: 10px;
}
input, button {
padding: 6px;
font-size: 14px;
}
ul {
padding-left: 20px;
}
li {
margin: 6px 0;
}
.error {
color: red;
}
</style>
</head>
<body>
<h1>Relay Admin Panel</h1>
<label>
API Key:
<input id="apiKey" type="password" style="width: 100%" />
</label>
<hr>
<h3>Registered domains</h3>
<button onclick="loadDomains()">Refresh</button>
<ul id="domains"></ul>
<hr>
<h3>Add domain</h3>
<input id="newDomain" placeholder="example.phone.local" />
<button onclick="addDomain()">Add</button>
<p id="status" class="error"></p>
<script>
const apiBase = "/domains";
function headers() {
return {
"apikey": document.getElementById("apiKey").value
};
}
async function loadDomains() {
const res = await fetch(apiBase, { headers: headers() });
if (!res.ok) {
showError(res);
return;
}
const text = await res.text();
const list = document.getElementById("domains");
list.innerHTML = "";
text.trim().split("\n").forEach(domain => {
if (!domain) return;
const li = document.createElement("li");
li.textContent = domain + " ";
const btn = document.createElement("button");
btn.textContent = "Delete";
btn.onclick = () => deleteDomain(domain);
li.appendChild(btn);
list.appendChild(li);
});
}
async function addDomain() {
const domain = document.getElementById("newDomain").value;
if (!domain) return;
const res = await fetch(`${apiBase}?domain=${encodeURIComponent(domain)}`, {
method: "POST",
headers: headers()
});
if (!res.ok) {
showError(res);
return;
}
document.getElementById("newDomain").value = "";
loadDomains();
}
async function deleteDomain(domain) {
const res = await fetch(`${apiBase}?domain=${encodeURIComponent(domain)}`, {
method: "DELETE",
headers: headers()
});
if (!res.ok) {
showError(res);
return;
}
loadDomains();
}
function showError(res) {
document.getElementById("status").textContent =
`Error: ${res.status} ${res.statusText}`;
}
</script>
</body>
</html>

21
admin/admin_log.go Normal file
View File

@ -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)
}

3
admin/config.go Normal file
View File

@ -0,0 +1,3 @@
package admin
var ApiKey string

58
admin/handlers.go Normal file
View File

@ -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)
}
}

26
admin/middleware.go Normal file
View File

@ -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)
})
}

24
admin/server.go Normal file
View File

@ -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,
))
}

19
certs/admin.crt Normal file
View File

@ -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-----

28
certs/admin.key Normal file
View File

@ -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-----

BIN
certs/ca.bks Normal file

Binary file not shown.

30
certs/ca.crt Normal file
View File

@ -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-----

BIN
certs/ca.der Normal file

Binary file not shown.

52
certs/ca.key Normal file
View File

@ -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-----

1
certs/ca.srl Normal file
View File

@ -0,0 +1 @@
5166E809D8A7B37861DD999BE40296DD06CD077E

9
certs/extfile.cnf Normal file
View File

@ -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

58
certs/g.bat Normal file
View File

@ -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

View File

@ -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

26
certs/phone.crt Normal file
View File

@ -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-----

15
certs/phone.csr Normal file
View File

@ -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-----

28
certs/phone.key Normal file
View File

@ -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-----

BIN
certs/phone.p12 Normal file

Binary file not shown.

9
certs/phone_ext.cnf Normal file
View File

@ -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

26
certs/relay.crt Normal file
View File

@ -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-----

15
certs/relay.csr Normal file
View File

@ -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-----

28
certs/relay.key Normal file
View File

@ -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-----

13
certs/relay_ext.cnf Normal file
View File

@ -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

26
certs/server.crt Normal file
View File

@ -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-----

15
certs/server.csr Normal file
View File

@ -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-----

28
certs/server.key Normal file
View File

@ -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-----

10
certs/server_ext.cnf Normal file
View File

@ -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

6
certs/win-trace.txt Normal file
View File

@ -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

65
device/device.go Normal file
View File

@ -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
}
}
}

49
device/streams.go Normal file
View File

@ -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
}

15
go.mod Normal file
View File

@ -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
)

36
go.sum Normal file
View File

@ -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=

18
grpcserver/control.go Normal file
View File

@ -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
}

36
grpcserver/server.go Normal file
View File

@ -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))
}

94
grpcserver/tunnel.go Normal file
View File

@ -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,
})
}
}
}

82
ingress/handler.go Normal file
View File

@ -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)
}

23
ingress/listener.go Normal file
View File

@ -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)
}
}

73
ingress/sni.go Normal file
View File

@ -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
}

30
main.go Normal file
View File

@ -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")
}

46
proto/control.proto Normal file
View File

@ -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;
}

492
proto/control/control.pb.go Normal file
View File

@ -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
}

View File

@ -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",
}

32
proto/tunnel.proto Normal file
View File

@ -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;
}

254
proto/tunnel/tunnel.pb.go Normal file
View File

@ -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
}

View File

@ -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",
}

15
registry/registry.go Normal file
View File

@ -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),
}

33
run_relay.bat Normal file
View File

@ -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

29
tlsutil/config.go Normal file
View File

@ -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
}

9
util/bufpool.go Normal file
View File

@ -0,0 +1,9 @@
package util
import "sync"
var BufPool = sync.Pool{
New: func() any {
return make([]byte, 4096)
},
}

11
util/mapkeys.go Normal file
View File

@ -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
}