FinalMaskForm.tsx 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738
  1. import { useMemo } from 'react';
  2. import { Button, Divider, Form, Input, InputNumber, Select, Switch } from 'antd';
  3. import { DeleteOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons';
  4. import { RandomUtil } from '@/utils';
  5. import { Protocols } from '@/models/outbound';
  6. interface StreamShape {
  7. network?: string;
  8. kcp?: { mtu?: number };
  9. finalmask: {
  10. tcp?: MaskRow[];
  11. udp?: MaskRow[];
  12. enableQuicParams?: boolean;
  13. quicParams?: QuicParams;
  14. };
  15. addTcpMask: (type?: string) => void;
  16. delTcpMask: (index: number) => void;
  17. addUdpMask: (type?: string) => void;
  18. delUdpMask: (index: number) => void;
  19. }
  20. interface MaskRow {
  21. type: string;
  22. settings: Record<string, unknown>;
  23. _getDefaultSettings: (type: string, settings: Record<string, unknown>) => Record<string, unknown>;
  24. }
  25. interface ItemRow {
  26. type: string;
  27. packet: string | unknown[];
  28. delay?: number | string;
  29. rand?: number | string;
  30. randRange?: string;
  31. }
  32. interface QuicParams {
  33. congestion: string;
  34. debug?: boolean;
  35. brutalUp?: number | string;
  36. brutalDown?: number | string;
  37. hasUdpHop?: boolean;
  38. udpHop?: { ports: string; interval: string | number };
  39. maxIdleTimeout?: number;
  40. keepAlivePeriod?: number;
  41. disablePathMTUDiscovery?: boolean;
  42. maxIncomingStreams?: number;
  43. initStreamReceiveWindow?: number;
  44. maxStreamReceiveWindow?: number;
  45. initConnectionReceiveWindow?: number;
  46. maxConnectionReceiveWindow?: number;
  47. }
  48. interface FinalMaskFormProps {
  49. stream: StreamShape;
  50. protocol: string;
  51. onChange: () => void;
  52. }
  53. function changeMaskType(mask: MaskRow, type: string) {
  54. mask.type = type;
  55. mask.settings = mask._getDefaultSettings(type, {});
  56. }
  57. function changeItemType(item: ItemRow, type: string) {
  58. item.type = type;
  59. if (type === 'base64') item.packet = RandomUtil.randomBase64();
  60. else if (type === 'array') {
  61. item.rand = 0;
  62. item.packet = [];
  63. } else item.packet = '';
  64. }
  65. function newClientServerItem(): ItemRow {
  66. return { delay: 0, rand: 0, randRange: '0-255', type: 'array', packet: [] };
  67. }
  68. function newUdpClientServerItem(): ItemRow {
  69. return { rand: 0, randRange: '0-255', type: 'array', packet: [] };
  70. }
  71. function newNoiseItem(): ItemRow {
  72. return { rand: '1-8192', randRange: '0-255', type: 'array', packet: [], delay: '10-20' };
  73. }
  74. export default function FinalMaskForm({ stream, protocol, onChange }: FinalMaskFormProps) {
  75. const isHysteria = protocol === Protocols.Hysteria || protocol === 'hysteria';
  76. const network = stream?.network || '';
  77. const showTcp = useMemo(
  78. () => ['raw', 'tcp', 'httpupgrade', 'ws', 'grpc', 'xhttp'].includes(network),
  79. [network],
  80. );
  81. const showUdp = isHysteria || network === 'kcp';
  82. const showQuic = isHysteria || network === 'xhttp';
  83. function notify() {
  84. onChange();
  85. }
  86. function changeUdpMaskType(mask: MaskRow, type: string) {
  87. changeMaskType(mask, type);
  88. if (network === 'kcp' && stream.kcp) {
  89. stream.kcp.mtu = type === 'xdns' ? 900 : 1350;
  90. }
  91. notify();
  92. }
  93. function addUdpMaskWithDefault() {
  94. const def = isHysteria ? 'salamander' : 'mkcp-aes128gcm';
  95. stream.addUdpMask(def);
  96. notify();
  97. }
  98. const tcpMasks = stream.finalmask.tcp || [];
  99. const udpMasks = stream.finalmask.udp || [];
  100. if (!showTcp && !showUdp && !showQuic) return null;
  101. return (
  102. <Form colon={false} labelCol={{ md: { span: 8 } }} wrapperCol={{ md: { span: 14 } }}>
  103. {showTcp && (
  104. <>
  105. <Form.Item label="TCP Masks">
  106. <Button
  107. type="primary"
  108. size="small"
  109. icon={<PlusOutlined />}
  110. onClick={() => {
  111. stream.addTcpMask('fragment');
  112. notify();
  113. }}
  114. />
  115. </Form.Item>
  116. {tcpMasks.map((mask, mIdx) => (
  117. <div key={`tcp-${mIdx}`}>
  118. <Divider style={{ margin: 0 }}>
  119. TCP Mask {mIdx + 1}
  120. <DeleteOutlined
  121. className="danger-icon"
  122. onClick={() => {
  123. stream.delTcpMask(mIdx);
  124. notify();
  125. }}
  126. />
  127. </Divider>
  128. <Form.Item label="Type">
  129. <Select
  130. value={mask.type}
  131. onChange={(v) => {
  132. changeMaskType(mask, v);
  133. notify();
  134. }}
  135. options={[
  136. { value: 'fragment', label: 'Fragment' },
  137. { value: 'header-custom', label: 'Header Custom' },
  138. { value: 'sudoku', label: 'Sudoku' },
  139. ]}
  140. />
  141. </Form.Item>
  142. {mask.type === 'fragment' && (
  143. <>
  144. <Form.Item label="Packets">
  145. <Select
  146. value={mask.settings.packets as string}
  147. onChange={(v) => {
  148. (mask.settings as Record<string, unknown>).packets = v;
  149. notify();
  150. }}
  151. options={[
  152. { value: 'tlshello', label: 'tlshello' },
  153. { value: '1-3', label: '1-3' },
  154. { value: '1-5', label: '1-5' },
  155. ]}
  156. />
  157. </Form.Item>
  158. {(['length', 'delay', 'maxSplit'] as const).map((field) => (
  159. <Form.Item key={field} label={field === 'maxSplit' ? 'Max Split' : field.charAt(0).toUpperCase() + field.slice(1)}>
  160. <Input
  161. value={(mask.settings[field] as string) || ''}
  162. onChange={(e) => {
  163. (mask.settings as Record<string, unknown>)[field] = e.target.value;
  164. notify();
  165. }}
  166. />
  167. </Form.Item>
  168. ))}
  169. </>
  170. )}
  171. {mask.type === 'sudoku' && (
  172. <>
  173. {(['password', 'ascii', 'customTable', 'customTables'] as const).map((field) => (
  174. <Form.Item key={field} label={field === 'customTable' ? 'Custom Table' : field === 'customTables' ? 'Custom Tables' : field.charAt(0).toUpperCase() + field.slice(1)}>
  175. <Input
  176. value={(mask.settings[field] as string) || ''}
  177. onChange={(e) => {
  178. (mask.settings as Record<string, unknown>)[field] = e.target.value;
  179. notify();
  180. }}
  181. />
  182. </Form.Item>
  183. ))}
  184. {(['paddingMin', 'paddingMax'] as const).map((field) => (
  185. <Form.Item key={field} label={field === 'paddingMin' ? 'Padding Min' : 'Padding Max'}>
  186. <InputNumber
  187. value={(mask.settings[field] as number) || 0}
  188. min={0}
  189. onChange={(v) => {
  190. (mask.settings as Record<string, unknown>)[field] = Number(v) || 0;
  191. notify();
  192. }}
  193. />
  194. </Form.Item>
  195. ))}
  196. </>
  197. )}
  198. {mask.type === 'header-custom' && (
  199. <HeaderCustomGroups mask={mask} kind="tcp" onChange={notify} />
  200. )}
  201. </div>
  202. ))}
  203. </>
  204. )}
  205. {showUdp && (
  206. <>
  207. <Form.Item label="UDP Masks">
  208. <Button type="primary" size="small" icon={<PlusOutlined />} onClick={addUdpMaskWithDefault} />
  209. </Form.Item>
  210. {udpMasks.map((mask, mIdx) => (
  211. <div key={`udp-${mIdx}`}>
  212. <Divider style={{ margin: 0 }}>
  213. UDP Mask {mIdx + 1}
  214. <DeleteOutlined
  215. className="danger-icon"
  216. onClick={() => {
  217. stream.delUdpMask(mIdx);
  218. notify();
  219. }}
  220. />
  221. </Divider>
  222. <Form.Item label="Type">
  223. <Select
  224. value={mask.type}
  225. onChange={(v) => changeUdpMaskType(mask, v)}
  226. options={
  227. isHysteria
  228. ? [{ value: 'salamander', label: 'Salamander (Hysteria2)' }]
  229. : [
  230. { value: 'mkcp-aes128gcm', label: 'mKCP AES-128-GCM' },
  231. { value: 'header-dns', label: 'Header DNS' },
  232. { value: 'header-dtls', label: 'Header DTLS 1.2' },
  233. { value: 'header-srtp', label: 'Header SRTP' },
  234. { value: 'header-utp', label: 'Header uTP' },
  235. { value: 'header-wechat', label: 'Header WeChat Video' },
  236. { value: 'header-wireguard', label: 'Header WireGuard' },
  237. { value: 'mkcp-original', label: 'mKCP Original' },
  238. { value: 'xdns', label: 'xDNS' },
  239. { value: 'xicmp', label: 'xICMP' },
  240. { value: 'header-custom', label: 'Header Custom' },
  241. { value: 'noise', label: 'Noise' },
  242. ]
  243. }
  244. />
  245. </Form.Item>
  246. {['mkcp-aes128gcm', 'salamander'].includes(mask.type) && (
  247. <Form.Item label="Password">
  248. <Input
  249. value={(mask.settings.password as string) || ''}
  250. placeholder="Obfuscation password"
  251. onChange={(e) => {
  252. (mask.settings as Record<string, unknown>).password = e.target.value;
  253. notify();
  254. }}
  255. />
  256. </Form.Item>
  257. )}
  258. {mask.type === 'header-dns' && (
  259. <Form.Item label="Domain">
  260. <Input
  261. value={(mask.settings.domain as string) || ''}
  262. placeholder="e.g., www.example.com"
  263. onChange={(e) => {
  264. (mask.settings as Record<string, unknown>).domain = e.target.value;
  265. notify();
  266. }}
  267. />
  268. </Form.Item>
  269. )}
  270. {mask.type === 'xdns' && (
  271. <Form.Item label="Domains">
  272. <Select
  273. mode="tags"
  274. value={(mask.settings.domains as string[]) || []}
  275. style={{ width: '100%' }}
  276. tokenSeparators={[',']}
  277. placeholder="e.g., www.example.com"
  278. onChange={(v) => {
  279. (mask.settings as Record<string, unknown>).domains = v;
  280. notify();
  281. }}
  282. />
  283. </Form.Item>
  284. )}
  285. {mask.type === 'noise' && (
  286. <NoiseItems mask={mask} onChange={notify} />
  287. )}
  288. {mask.type === 'header-custom' && (
  289. <UdpHeaderCustom mask={mask} onChange={notify} />
  290. )}
  291. {mask.type === 'xicmp' && (
  292. <>
  293. <Form.Item label="IP">
  294. <Input
  295. value={(mask.settings.ip as string) || ''}
  296. placeholder="0.0.0.0"
  297. onChange={(e) => {
  298. (mask.settings as Record<string, unknown>).ip = e.target.value;
  299. notify();
  300. }}
  301. />
  302. </Form.Item>
  303. <Form.Item label="ID">
  304. <InputNumber
  305. value={(mask.settings.id as number) || 0}
  306. min={0}
  307. onChange={(v) => {
  308. (mask.settings as Record<string, unknown>).id = Number(v) || 0;
  309. notify();
  310. }}
  311. />
  312. </Form.Item>
  313. </>
  314. )}
  315. </div>
  316. ))}
  317. </>
  318. )}
  319. {showQuic && (
  320. <>
  321. <Form.Item label="QUIC Params">
  322. <Switch
  323. checked={!!stream.finalmask.enableQuicParams}
  324. onChange={(v) => {
  325. stream.finalmask.enableQuicParams = v;
  326. notify();
  327. }}
  328. />
  329. </Form.Item>
  330. {stream.finalmask.enableQuicParams && stream.finalmask.quicParams && (
  331. <QuicParamsForm params={stream.finalmask.quicParams} onChange={notify} />
  332. )}
  333. </>
  334. )}
  335. </Form>
  336. );
  337. }
  338. function HeaderCustomGroups({
  339. mask,
  340. kind: _kind,
  341. onChange,
  342. }: {
  343. mask: MaskRow;
  344. kind: 'tcp';
  345. onChange: () => void;
  346. }) {
  347. const settings = mask.settings as { clients?: ItemRow[][]; servers?: ItemRow[][] };
  348. if (!settings.clients) settings.clients = [];
  349. if (!settings.servers) settings.servers = [];
  350. return (
  351. <>
  352. {(['clients', 'servers'] as const).map((groupKey) => (
  353. <div key={groupKey}>
  354. <Form.Item label={groupKey === 'clients' ? 'Clients' : 'Servers'}>
  355. <Button
  356. type="primary"
  357. size="small"
  358. icon={<PlusOutlined />}
  359. onClick={() => {
  360. (settings[groupKey] as ItemRow[][]).push([newClientServerItem()]);
  361. onChange();
  362. }}
  363. />
  364. </Form.Item>
  365. {(settings[groupKey] as ItemRow[][]).map((group, gi) => (
  366. <div key={`${groupKey}-${gi}`}>
  367. <Divider style={{ margin: 0 }}>
  368. {groupKey === 'clients' ? 'Clients' : 'Servers'} Group {gi + 1}
  369. <DeleteOutlined
  370. className="danger-icon"
  371. onClick={() => {
  372. (settings[groupKey] as ItemRow[][]).splice(gi, 1);
  373. onChange();
  374. }}
  375. />
  376. </Divider>
  377. {group.map((item, _ii) => (
  378. <ItemEditor key={_ii} item={item} onChange={onChange} delayAsNumber />
  379. ))}
  380. </div>
  381. ))}
  382. </div>
  383. ))}
  384. </>
  385. );
  386. }
  387. function UdpHeaderCustom({ mask, onChange }: { mask: MaskRow; onChange: () => void }) {
  388. const settings = mask.settings as { client?: ItemRow[]; server?: ItemRow[] };
  389. if (!settings.client) settings.client = [];
  390. if (!settings.server) settings.server = [];
  391. return (
  392. <>
  393. {(['client', 'server'] as const).map((groupKey) => (
  394. <div key={groupKey}>
  395. <Form.Item label={groupKey === 'client' ? 'Client' : 'Server'}>
  396. <Button
  397. type="primary"
  398. size="small"
  399. icon={<PlusOutlined />}
  400. onClick={() => {
  401. (settings[groupKey] as ItemRow[]).push(newUdpClientServerItem());
  402. onChange();
  403. }}
  404. />
  405. </Form.Item>
  406. {(settings[groupKey] as ItemRow[]).map((item, ci) => (
  407. <div key={ci}>
  408. <Divider style={{ margin: 0 }}>
  409. {groupKey === 'client' ? 'Client' : 'Server'} {ci + 1}
  410. <DeleteOutlined
  411. className="danger-icon"
  412. onClick={() => {
  413. (settings[groupKey] as ItemRow[]).splice(ci, 1);
  414. onChange();
  415. }}
  416. />
  417. </Divider>
  418. <ItemEditor item={item} onChange={onChange} />
  419. </div>
  420. ))}
  421. </div>
  422. ))}
  423. </>
  424. );
  425. }
  426. function NoiseItems({ mask, onChange }: { mask: MaskRow; onChange: () => void }) {
  427. const settings = mask.settings as { reset?: number; noise?: ItemRow[] };
  428. if (!settings.noise) settings.noise = [];
  429. return (
  430. <>
  431. <Form.Item label="Reset">
  432. <InputNumber
  433. value={settings.reset || 0}
  434. min={0}
  435. onChange={(v) => {
  436. settings.reset = Number(v) || 0;
  437. onChange();
  438. }}
  439. />
  440. </Form.Item>
  441. <Form.Item label="Noise">
  442. <Button
  443. type="primary"
  444. size="small"
  445. icon={<PlusOutlined />}
  446. onClick={() => {
  447. (settings.noise as ItemRow[]).push(newNoiseItem());
  448. onChange();
  449. }}
  450. />
  451. </Form.Item>
  452. {(settings.noise as ItemRow[]).map((n, ni) => (
  453. <div key={ni}>
  454. <Divider style={{ margin: 0 }}>
  455. Noise {ni + 1}
  456. <DeleteOutlined
  457. className="danger-icon"
  458. onClick={() => {
  459. (settings.noise as ItemRow[]).splice(ni, 1);
  460. onChange();
  461. }}
  462. />
  463. </Divider>
  464. <ItemEditor item={n} onChange={onChange} delayAsString />
  465. </div>
  466. ))}
  467. </>
  468. );
  469. }
  470. function ItemEditor({
  471. item,
  472. onChange,
  473. delayAsNumber,
  474. delayAsString,
  475. }: {
  476. item: ItemRow;
  477. onChange: () => void;
  478. delayAsNumber?: boolean;
  479. delayAsString?: boolean;
  480. }) {
  481. return (
  482. <>
  483. <Form.Item label="Type">
  484. <Select
  485. value={item.type}
  486. onChange={(v) => {
  487. changeItemType(item, v);
  488. onChange();
  489. }}
  490. options={[
  491. { value: 'array', label: 'Array' },
  492. { value: 'str', label: 'String' },
  493. { value: 'hex', label: 'Hex' },
  494. { value: 'base64', label: 'Base64' },
  495. ]}
  496. />
  497. </Form.Item>
  498. {delayAsNumber && (
  499. <Form.Item label="Delay (ms)">
  500. <InputNumber
  501. value={typeof item.delay === 'number' ? item.delay : 0}
  502. min={0}
  503. onChange={(v) => {
  504. item.delay = Number(v) || 0;
  505. onChange();
  506. }}
  507. />
  508. </Form.Item>
  509. )}
  510. {item.type === 'array' ? (
  511. <>
  512. <Form.Item label="Rand">
  513. {delayAsString ? (
  514. <Input
  515. value={String(item.rand ?? '')}
  516. onChange={(e) => {
  517. item.rand = e.target.value;
  518. onChange();
  519. }}
  520. placeholder="0 or 1-8192"
  521. />
  522. ) : (
  523. <InputNumber
  524. value={typeof item.rand === 'number' ? item.rand : 0}
  525. min={0}
  526. onChange={(v) => {
  527. item.rand = Number(v) || 0;
  528. onChange();
  529. }}
  530. />
  531. )}
  532. </Form.Item>
  533. <Form.Item label="Rand Range">
  534. <Input
  535. value={item.randRange || ''}
  536. placeholder="0-255"
  537. onChange={(e) => {
  538. item.randRange = e.target.value;
  539. onChange();
  540. }}
  541. />
  542. </Form.Item>
  543. </>
  544. ) : (
  545. <Form.Item label="Packet">
  546. {item.type === 'base64' ? (
  547. <Input.Group compact>
  548. <Input
  549. value={String(item.packet ?? '')}
  550. placeholder="binary data"
  551. style={{ width: 'calc(100% - 32px)' }}
  552. onChange={(e) => {
  553. item.packet = e.target.value;
  554. onChange();
  555. }}
  556. />
  557. <Button
  558. icon={<ReloadOutlined />}
  559. onClick={() => {
  560. item.packet = RandomUtil.randomBase64();
  561. onChange();
  562. }}
  563. />
  564. </Input.Group>
  565. ) : (
  566. <Input
  567. value={String(item.packet ?? '')}
  568. placeholder="binary data"
  569. onChange={(e) => {
  570. item.packet = e.target.value;
  571. onChange();
  572. }}
  573. />
  574. )}
  575. </Form.Item>
  576. )}
  577. {delayAsString && (
  578. <Form.Item label="Delay">
  579. <Input
  580. value={typeof item.delay === 'string' ? item.delay : ''}
  581. placeholder="10-20"
  582. onChange={(e) => {
  583. item.delay = e.target.value;
  584. onChange();
  585. }}
  586. />
  587. </Form.Item>
  588. )}
  589. </>
  590. );
  591. }
  592. function QuicParamsForm({ params, onChange }: { params: QuicParams; onChange: () => void }) {
  593. function update<K extends keyof QuicParams>(key: K, value: QuicParams[K]) {
  594. params[key] = value;
  595. onChange();
  596. }
  597. return (
  598. <>
  599. <Form.Item label="Congestion">
  600. <Select
  601. value={params.congestion}
  602. onChange={(v) => update('congestion', v)}
  603. options={[
  604. { value: 'reno', label: 'Reno' },
  605. { value: 'bbr', label: 'BBR' },
  606. { value: 'brutal', label: 'Brutal' },
  607. { value: 'force-brutal', label: 'Force Brutal' },
  608. ]}
  609. />
  610. </Form.Item>
  611. <Form.Item label="Debug">
  612. <Switch checked={!!params.debug} onChange={(v) => update('debug', v)} />
  613. </Form.Item>
  614. {['brutal', 'force-brutal'].includes(params.congestion) && (
  615. <>
  616. <Form.Item label="Brutal Up">
  617. <Input
  618. value={String(params.brutalUp ?? '')}
  619. placeholder="65537"
  620. onChange={(e) => update('brutalUp', e.target.value)}
  621. />
  622. </Form.Item>
  623. <Form.Item label="Brutal Down">
  624. <Input
  625. value={String(params.brutalDown ?? '')}
  626. placeholder="65537"
  627. onChange={(e) => update('brutalDown', e.target.value)}
  628. />
  629. </Form.Item>
  630. </>
  631. )}
  632. <Form.Item label="UDP Hop">
  633. <Switch checked={!!params.hasUdpHop} onChange={(v) => update('hasUdpHop', v)} />
  634. </Form.Item>
  635. {params.hasUdpHop && params.udpHop && (
  636. <>
  637. <Form.Item label="Hop Ports">
  638. <Input
  639. value={params.udpHop.ports || ''}
  640. placeholder="e.g. 20000-50000"
  641. onChange={(e) => {
  642. params.udpHop!.ports = e.target.value;
  643. onChange();
  644. }}
  645. />
  646. </Form.Item>
  647. <Form.Item label="Hop Interval (s)">
  648. <InputNumber
  649. value={Number(params.udpHop.interval) || 5}
  650. min={5}
  651. onChange={(v) => {
  652. params.udpHop!.interval = Number(v) || 5;
  653. onChange();
  654. }}
  655. />
  656. </Form.Item>
  657. </>
  658. )}
  659. {(
  660. [
  661. ['maxIdleTimeout', 'Max Idle Timeout (s)', 4, 120],
  662. ['keepAlivePeriod', 'Keep Alive Period (s)', 2, 60],
  663. ] as const
  664. ).map(([key, label, min, max]) => (
  665. <Form.Item key={key} label={label}>
  666. <InputNumber
  667. value={params[key] as number}
  668. min={min}
  669. max={max}
  670. onChange={(v) => update(key, Number(v) || min)}
  671. />
  672. </Form.Item>
  673. ))}
  674. <Form.Item label="Disable Path MTU Dis">
  675. <Switch checked={!!params.disablePathMTUDiscovery} onChange={(v) => update('disablePathMTUDiscovery', v)} />
  676. </Form.Item>
  677. {(
  678. [
  679. ['maxIncomingStreams', 'Max Incoming Streams', 8, '1024 = default'],
  680. ['initStreamReceiveWindow', 'Init Stream Window', 16384, '8388608 = default'],
  681. ['maxStreamReceiveWindow', 'Max Stream Window', 16384, '8388608 = default'],
  682. ['initConnectionReceiveWindow', 'Init Conn Window', 16384, '20971520 = default'],
  683. ['maxConnectionReceiveWindow', 'Max Conn Window', 16384, '20971520 = default'],
  684. ] as const
  685. ).map(([key, label, min, placeholder]) => (
  686. <Form.Item key={key} label={label}>
  687. <InputNumber
  688. value={params[key] as number}
  689. min={min}
  690. placeholder={placeholder}
  691. onChange={(v) => update(key, Number(v) || 0)}
  692. />
  693. </Form.Item>
  694. ))}
  695. </>
  696. );
  697. }