修复局域网连接出错问题
This commit is contained in:
@@ -2,7 +2,11 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -118,6 +122,14 @@ func (h *HTTPHandler) registerDevice(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
networkGroupKey, publicIPHash := deriveClientNetworkIdentity(c.ClientIP())
|
||||||
|
if networkGroupKey != "" {
|
||||||
|
input.NetworkGroupKey = networkGroupKey
|
||||||
|
}
|
||||||
|
if publicIPHash != "" {
|
||||||
|
input.PublicIPHash = publicIPHash
|
||||||
|
}
|
||||||
|
|
||||||
device, session := h.deps.DeviceService.Register(input, c.Request.UserAgent(), c.GetHeader("X-Device-Token"))
|
device, session := h.deps.DeviceService.Register(input, c.Request.UserAgent(), c.GetHeader("X-Device-Token"))
|
||||||
c.JSON(http.StatusOK, gin.H{"data": gin.H{
|
c.JSON(http.StatusOK, gin.H{"data": gin.H{
|
||||||
"id": device.ID,
|
"id": device.ID,
|
||||||
@@ -611,6 +623,46 @@ func decodeDownloadFilename(filename string) string {
|
|||||||
return strings.TrimSpace(decoded)
|
return strings.TrimSpace(decoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deriveClientNetworkIdentity(clientIP string) (string, string) {
|
||||||
|
addr, err := netip.ParseAddr(strings.TrimSpace(clientIP))
|
||||||
|
if err != nil {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
addr = addr.Unmap()
|
||||||
|
hash := hashIP(addr.String())
|
||||||
|
|
||||||
|
if addr.IsLoopback() {
|
||||||
|
return "loopback", hash
|
||||||
|
}
|
||||||
|
|
||||||
|
if addr.Is4() {
|
||||||
|
bytes := addr.As4()
|
||||||
|
switch {
|
||||||
|
case bytes[0] == 10 || bytes[0] == 127:
|
||||||
|
return fmt.Sprintf("v4:%d.%d.%d", bytes[0], bytes[1], bytes[2]), hash
|
||||||
|
case bytes[0] == 192 && bytes[1] == 168:
|
||||||
|
return fmt.Sprintf("v4:%d.%d.%d", bytes[0], bytes[1], bytes[2]), hash
|
||||||
|
case bytes[0] == 172 && bytes[1] >= 16 && bytes[1] <= 31:
|
||||||
|
return fmt.Sprintf("v4:%d.%d.%d", bytes[0], bytes[1], bytes[2]), hash
|
||||||
|
default:
|
||||||
|
return "pub4:" + hash[:16], hash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := netip.PrefixFrom(addr, 64).Masked()
|
||||||
|
if addr.IsPrivate() {
|
||||||
|
return "v6:" + prefix.Addr().String(), hash
|
||||||
|
}
|
||||||
|
|
||||||
|
return "pub6:" + hash[:16], hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashIP(value string) string {
|
||||||
|
sum := sha256.Sum256([]byte(strings.TrimSpace(value)))
|
||||||
|
return hex.EncodeToString(sum[:])
|
||||||
|
}
|
||||||
|
|
||||||
func (h *HTTPHandler) ensureFallbackBucket(ctx context.Context, transferID string) error {
|
func (h *HTTPHandler) ensureFallbackBucket(ctx context.Context, transferID string) error {
|
||||||
if h.deps.MinIOClient == nil {
|
if h.deps.MinIOClient == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -134,6 +134,46 @@ func TestProtectedRoutesAcceptDeviceCredentialsFromCookies(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRegisterDeviceDerivesNetworkGroupFromClientIP(t *testing.T) {
|
||||||
|
router, _ := newTestRouter()
|
||||||
|
|
||||||
|
alpha := registerDeviceWithRemoteAddr(t, router, map[string]any{
|
||||||
|
"device_id": "alpha",
|
||||||
|
"name": "Alpha",
|
||||||
|
"type": "desktop",
|
||||||
|
}, "192.168.1.10:1234")
|
||||||
|
bravo := registerDeviceWithRemoteAddr(t, router, map[string]any{
|
||||||
|
"device_id": "bravo",
|
||||||
|
"name": "Bravo",
|
||||||
|
"type": "phone",
|
||||||
|
}, "192.168.1.77:4567")
|
||||||
|
_ = registerDeviceWithRemoteAddr(t, router, map[string]any{
|
||||||
|
"device_id": "charlie",
|
||||||
|
"name": "Charlie",
|
||||||
|
"type": "phone",
|
||||||
|
}, "10.0.0.8:9999")
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/devices/candidates?deviceId="+alpha.ID, nil)
|
||||||
|
req.Header.Set("X-Device-ID", alpha.ID)
|
||||||
|
req.Header.Set("X-Device-Token", alpha.AuthToken)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(resp, req)
|
||||||
|
|
||||||
|
if resp.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected candidates request 200, got %d: %s", resp.Code, resp.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload struct {
|
||||||
|
Data []registeredDevice `json:"data"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(resp.Body.Bytes(), &payload); err != nil {
|
||||||
|
t.Fatalf("decode candidates response: %v", err)
|
||||||
|
}
|
||||||
|
if len(payload.Data) != 1 || payload.Data[0].ID != bravo.ID {
|
||||||
|
t.Fatalf("expected only same-network device %q, got %+v", bravo.ID, payload.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newTestRouter() (http.Handler, *store.MemoryStore) {
|
func newTestRouter() (http.Handler, *store.MemoryStore) {
|
||||||
memStore := store.NewMemoryStore(model.RuntimeConfig{})
|
memStore := store.NewMemoryStore(model.RuntimeConfig{})
|
||||||
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||||
@@ -183,6 +223,33 @@ func registerDevice(t *testing.T, router http.Handler, payload map[string]any) r
|
|||||||
return payloadWrapper.Data
|
return payloadWrapper.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func registerDeviceWithRemoteAddr(t *testing.T, router http.Handler, payload map[string]any, remoteAddr string) registeredDevice {
|
||||||
|
t.Helper()
|
||||||
|
body, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("marshal register body: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/devices/register", bytes.NewReader(body))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.RemoteAddr = remoteAddr
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(resp, req)
|
||||||
|
|
||||||
|
if resp.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected register 200, got %d: %s", resp.Code, resp.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var payloadWrapper struct {
|
||||||
|
Data registeredDevice `json:"data"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(resp.Body.Bytes(), &payloadWrapper); err != nil {
|
||||||
|
t.Fatalf("decode register response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return payloadWrapper.Data
|
||||||
|
}
|
||||||
|
|
||||||
func createTransfer(t *testing.T, router http.Handler, device registeredDevice, payload map[string]any) transferRecord {
|
func createTransfer(t *testing.T, router http.Handler, device registeredDevice, payload map[string]any) transferRecord {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
body, err := json.Marshal(payload)
|
body, err := json.Marshal(payload)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
2
frontend/dist/index.html
vendored
2
frontend/dist/index.html
vendored
@@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<title>AirShare Pro</title>
|
<title>AirShare Pro</title>
|
||||||
<script type="module" crossorigin src="/assets/index-BjlDNx75.js"></script>
|
<script type="module" crossorigin src="/assets/index-Dvss27fc.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-qzWUgf-t.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-qzWUgf-t.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ const localDevice = ref({
|
|||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
type: '',
|
type: '',
|
||||||
|
networkGroupKey: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
const persistedDeviceId = localStorage.getItem(DEVICE_ID_KEY) || ''
|
const persistedDeviceId = localStorage.getItem(DEVICE_ID_KEY) || ''
|
||||||
@@ -233,6 +234,7 @@ async function registerCurrentDevice() {
|
|||||||
id: device.id,
|
id: device.id,
|
||||||
name: device.name,
|
name: device.name,
|
||||||
type: device.type,
|
type: device.type,
|
||||||
|
networkGroupKey: device.network_group_key || '',
|
||||||
}
|
}
|
||||||
|
|
||||||
await loadPendingDownloads()
|
await loadPendingDownloads()
|
||||||
@@ -1284,7 +1286,7 @@ function deriveNetworkGroupKey() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isSameLocalNetwork(networkGroupKey) {
|
function isSameLocalNetwork(networkGroupKey) {
|
||||||
const localNetworkGroupKey = deriveNetworkGroupKey()
|
const localNetworkGroupKey = localDevice.value.networkGroupKey || deriveNetworkGroupKey()
|
||||||
return !!localNetworkGroupKey && networkGroupKey === localNetworkGroupKey
|
return !!localNetworkGroupKey && networkGroupKey === localNetworkGroupKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user