Przeglądaj źródła

fix(dns): stop offering a port field that DoH entries discard

Xray ignores port for DoH/DoHL/DoQL, so valuesToWire deliberately stores none
for an encrypted address and a non-standard port has to go inside the URL. The
form kept offering the field anyway, pre-filled with the 53 from its own
defaults: a port typed there was dropped on save and redrawn as 53 on reopen,
which reads as the panel losing the value.

Render the port field only where it is actually stored. DoT keeps it, since
tls:// is not an encrypted-address scheme for this purpose.

Closes #6403
Sanaei 8 godzin temu
rodzic
commit
c392f367e1

+ 16 - 8
frontend/src/pages/xray/dns/DnsServerModal.tsx

@@ -137,7 +137,13 @@ export default function DnsServerModal({
   onConfirm,
 }: DnsServerModalProps) {
   const { t } = useTranslation();
-  const methods = useForm<DnsServerForm>({ defaultValues: defaultFormValues() });
+  const methods = useForm<DnsServerForm>({
+    defaultValues: defaultFormValues(),
+  });
+  const address = useWatch({ control: methods.control, name: 'address' }) ?? '';
+  // Xray ignores port for DoH/DoHL/DoQL, so valuesToWire never stores one:
+  // offering the field there discards whatever is typed into it.
+  const portApplies = !isEncryptedDnsAddress(address);
   const domains = useWatch({ control: methods.control, name: 'domains' }) ?? [];
   const expectedIPs = useWatch({ control: methods.control, name: 'expectedIPs' }) ?? [];
   const unexpectedIPs = useWatch({ control: methods.control, name: 'unexpectedIPs' }) ?? [];
@@ -168,13 +174,15 @@ export default function DnsServerModal({
           >
             <Input />
           </FormField>
-          <FormField
-            label={t('pages.inbounds.port')}
-            name="port"
-            rules={{ validate: rhfZodValidate(shape.port) }}
-          >
-            <InputNumber min={1} max={65535} />
-          </FormField>
+          {portApplies && (
+            <FormField
+              label={t('pages.inbounds.port')}
+              name="port"
+              rules={{ validate: rhfZodValidate(shape.port) }}
+            >
+              <InputNumber min={1} max={65535} />
+            </FormField>
+          )}
           <FormField label={t('pages.xray.dns.tag')} name="tag">
             <Input />
           </FormField>

+ 47 - 0
frontend/src/test/dns-server-port-field.test.tsx

@@ -0,0 +1,47 @@
+import { describe, expect, it } from 'vitest';
+import { fireEvent, screen } from '@testing-library/react';
+
+import DnsServerModal from '@/pages/xray/dns/DnsServerModal';
+import { renderWithProviders } from './test-utils';
+
+describe('DnsServerModal port field', () => {
+  it('hides the port for an encrypted address, whose port lives in the URL', () => {
+    renderWithProviders(
+      <DnsServerModal
+        open
+        server="https://dns.example.com/dns-query"
+        isEdit
+        onClose={() => {}}
+        onConfirm={() => {}}
+      />,
+    );
+
+    expect(screen.queryByLabelText('Port')).toBeNull();
+  });
+
+  it('offers the port for a plain address and for DoT', () => {
+    renderWithProviders(
+      <DnsServerModal
+        open
+        server="tls://dns.example.com"
+        isEdit
+        onClose={() => {}}
+        onConfirm={() => {}}
+      />,
+    );
+
+    expect(screen.getByLabelText('Port')).toBeTruthy();
+  });
+
+  it('drops the port field as soon as the address becomes a DoH URL', () => {
+    renderWithProviders(
+      <DnsServerModal open server="1.1.1.1" isEdit onClose={() => {}} onConfirm={() => {}} />,
+    );
+
+    expect(screen.getByLabelText('Port')).toBeTruthy();
+    fireEvent.change(screen.getByLabelText('Address'), {
+      target: { value: 'https://dns.example.com/dns-query' },
+    });
+    expect(screen.queryByLabelText('Port')).toBeNull();
+  });
+});