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
This commit is contained in:
Sanaei
2026-09-09 00:47:18 +02:00
parent 705b291d34
commit c392f367e1
2 changed files with 63 additions and 8 deletions
+16 -8
View File
@@ -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>
@@ -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();
});
});