Просмотр исходного кода

fix(outbound): carry ALPN, fingerprint and UDP mask when importing a Hysteria2 link (#4760)

parseHysteria2Link hardcoded alpn to h3 and never read fp, ech, or the fm (finalmask) param, so importing a Hysteria2 client URL as an outbound dropped the configured ALPN, fingerprint, and salamander UDP mask. Parse alpn (falling back to h3 only when absent), fp, ech, and the pcs pinned-cert key, and restore the UDP mask via applyFinalMaskParam.
MHSanaei 6 часов назад
Родитель
Сommit
803e010921

+ 6 - 4
frontend/src/lib/xray/outbound-link-parser.ts

@@ -404,6 +404,7 @@ export function parseHysteria2Link(link: string): Raw | null {
   const address = url.hostname;
   const port = Number(url.port) || 443;
   const params = url.searchParams;
+  const alpn = params.get('alpn');
   const stream: Raw = {
     network: 'hysteria',
     security: 'tls',
@@ -412,13 +413,14 @@ export function parseHysteria2Link(link: string): Raw | null {
     },
     tlsSettings: {
       serverName: params.get('sni') ?? '',
-      alpn: ['h3'],
-      fingerprint: '',
-      echConfigList: '',
+      alpn: alpn ? alpn.split(',') : ['h3'],
+      fingerprint: params.get('fp') ?? '',
+      echConfigList: params.get('ech') ?? '',
       verifyPeerCertByName: '',
-      pinnedPeerCertSha256: params.get('pinSHA256') ?? '',
+      pinnedPeerCertSha256: params.get('pcs') ?? '',
     },
   };
+  applyFinalMaskParam(stream, params);
   return {
     protocol: 'hysteria',
     tag: decodeRemark(url),

+ 26 - 0
frontend/src/test/outbound-link-parser.test.ts

@@ -267,6 +267,32 @@ describe('parseHysteria2Link', () => {
     const out = parseHysteria2Link('hy2://auth@srv:443?sni=example.com');
     expect(out?.protocol).toBe('hysteria');
   });
+
+  it('parses alpn, fingerprint and the salamander UDP mask (fm) — #4760', () => {
+    const link = 'hysteria2://[email protected]:8443?'
+      + 'alpn=h2%2Chttp%2F1.1&'
+      + 'fm=%7B%22udp%22%3A%5B%7B%22settings%22%3A%7B%22password%22%3A%22ftwfgb9655hh2mgo%22%7D%2C%22type%22%3A%22salamander%22%7D%5D%7D&'
+      + 'fp=chrome&obfs=salamander&obfs-password=655hh2mgo&security=tls&sni=news.domain.org'
+      + '#hy2-ej596ty350qs';
+    const out = parseHysteria2Link(link);
+    expect(out).not.toBeNull();
+    const stream = out!.streamSettings as Record<string, unknown>;
+    const tls = stream.tlsSettings as Record<string, unknown>;
+    expect(tls.alpn).toEqual(['h2', 'http/1.1']);
+    expect(tls.fingerprint).toBe('chrome');
+    expect(tls.serverName).toBe('news.domain.org');
+    const finalmask = stream.finalmask as Record<string, unknown>;
+    expect(finalmask).toBeDefined();
+    const udp = finalmask.udp as Array<Record<string, unknown>>;
+    expect(udp[0].type).toBe('salamander');
+    expect((udp[0].settings as Record<string, unknown>).password).toBe('ftwfgb9655hh2mgo');
+  });
+
+  it('defaults alpn to h3 when the link omits it', () => {
+    const out = parseHysteria2Link('hysteria2://auth@srv:443?sni=example.com');
+    const tls = (out!.streamSettings as Record<string, unknown>).tlsSettings as Record<string, unknown>;
+    expect(tls.alpn).toEqual(['h3']);
+  });
 });
 
 describe('parseVlessLink — extra / fm / x_padding_bytes (B20)', () => {