wireguard.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import { useTranslation } from 'react-i18next';
  2. import { Button, Divider, Form, Input, InputNumber, Select, Space, Switch } from 'antd';
  3. import { MinusOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons';
  4. import { Wireguard } from '@/utils';
  5. interface WireguardFieldsProps {
  6. wgPubKey: string;
  7. regenInboundWg: () => void;
  8. regenWgPeerKeypair: (name: number) => void;
  9. }
  10. function nextWgPeerAllowedIP(peers: Array<{ allowedIPs?: string[] }> | undefined): string {
  11. const fallback = '10.0.0.2/32';
  12. let maxInt = -1;
  13. let prefix = 32;
  14. for (const peer of peers ?? []) {
  15. for (const ip of peer?.allowedIPs ?? []) {
  16. const m = /^\s*(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(?:\/(\d{1,2}))?\s*$/.exec(String(ip));
  17. if (!m) continue;
  18. const octets = [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
  19. if (octets.some((o) => o > 255)) continue;
  20. const asInt = octets[0] * 16777216 + octets[1] * 65536 + octets[2] * 256 + octets[3];
  21. if (asInt > maxInt) {
  22. maxInt = asInt;
  23. prefix = m[5] !== undefined ? Math.min(Number(m[5]), 32) : 32;
  24. }
  25. }
  26. }
  27. if (maxInt < 0) return fallback;
  28. const next = maxInt + 1;
  29. const a = Math.floor(next / 16777216) % 256;
  30. const b = Math.floor(next / 65536) % 256;
  31. const c = Math.floor(next / 256) % 256;
  32. const d = next % 256;
  33. return `${a}.${b}.${c}.${d}/${prefix}`;
  34. }
  35. export default function WireguardFields({ wgPubKey, regenInboundWg, regenWgPeerKeypair }: WireguardFieldsProps) {
  36. const { t } = useTranslation();
  37. const form = Form.useFormInstance();
  38. return (
  39. <>
  40. <Form.Item label={t('pages.xray.wireguard.secretKey')}>
  41. <Space.Compact block>
  42. <Form.Item name={['settings', 'secretKey']} noStyle>
  43. <Input style={{ width: 'calc(100% - 32px)' }} />
  44. </Form.Item>
  45. <Button icon={<ReloadOutlined />} onClick={regenInboundWg} />
  46. </Space.Compact>
  47. </Form.Item>
  48. <Form.Item label={t('pages.xray.wireguard.publicKey')}>
  49. <Input value={wgPubKey} disabled />
  50. </Form.Item>
  51. <Form.Item name={['settings', 'mtu']} label="MTU">
  52. <InputNumber />
  53. </Form.Item>
  54. <Form.Item
  55. name={['settings', 'noKernelTun']}
  56. label={t('pages.inbounds.info.noKernelTun')}
  57. valuePropName="checked"
  58. >
  59. <Switch />
  60. </Form.Item>
  61. <Form.Item name={['settings', 'workers']} label='Workers'>
  62. <InputNumber min={1} />
  63. </Form.Item>
  64. <Form.Item name={['settings', 'domainStrategy']} label={t('pages.xray.wireguard.domainStrategy')}>
  65. <Select
  66. allowClear
  67. options={[
  68. { value: 'ForceIP', label: 'ForceIP' },
  69. { value: 'ForceIPv4', label: 'ForceIPv4' },
  70. { value: 'ForceIPv4v6', label: 'ForceIPv4v6' },
  71. { value: 'ForceIPv6', label: 'ForceIPv6' },
  72. { value: 'ForceIPv6v4', label: 'ForceIPv6v4' },
  73. ]}
  74. />
  75. </Form.Item>
  76. <Form.List name={['settings', 'peers']}>
  77. {(fields, { add, remove }) => (
  78. <>
  79. <Form.Item label={t('pages.inbounds.form.peers')}>
  80. <Button
  81. size="small"
  82. onClick={() => {
  83. const kp = Wireguard.generateKeypair();
  84. const peers = form.getFieldValue(['settings', 'peers']) as Array<{ allowedIPs?: string[] }> | undefined;
  85. add({
  86. privateKey: kp.privateKey,
  87. publicKey: kp.publicKey,
  88. allowedIPs: [nextWgPeerAllowedIP(peers)],
  89. keepAlive: 0,
  90. });
  91. }}
  92. >
  93. <PlusOutlined /> {t('pages.inbounds.form.addPeer')}
  94. </Button>
  95. </Form.Item>
  96. {fields.map((field, idx) => (
  97. <div key={field.key} className="wg-peer">
  98. <Divider titlePlacement="center">
  99. <Space>
  100. <span>{t('pages.inbounds.info.peerNumber', { n: idx + 1 })}</span>
  101. {fields.length > 1 && (
  102. <Button
  103. size="small"
  104. danger
  105. icon={<MinusOutlined />}
  106. onClick={() => remove(field.name)}
  107. />
  108. )}
  109. </Space>
  110. </Divider>
  111. <Form.Item label={t('pages.xray.wireguard.secretKey')}>
  112. <Space.Compact block>
  113. <Form.Item name={[field.name, 'privateKey']} noStyle>
  114. <Input style={{ width: 'calc(100% - 32px)' }} />
  115. </Form.Item>
  116. <Button
  117. icon={<ReloadOutlined />}
  118. onClick={() => regenWgPeerKeypair(field.name)}
  119. />
  120. </Space.Compact>
  121. </Form.Item>
  122. <Form.Item name={[field.name, 'publicKey']} label={t('pages.xray.wireguard.publicKey')}>
  123. <Input />
  124. </Form.Item>
  125. <Form.Item name={[field.name, 'preSharedKey']} label="PSK">
  126. <Input />
  127. </Form.Item>
  128. <Form.List name={[field.name, 'allowedIPs']}>
  129. {(ipFields, { add: addIp, remove: removeIp }) => (
  130. <Form.Item label={t('pages.xray.wireguard.allowedIPs')}>
  131. <Button size="small" onClick={() => addIp('')}>
  132. <PlusOutlined />
  133. </Button>
  134. {ipFields.map((ipField) => (
  135. <Space.Compact key={ipField.key} block className="mt-4">
  136. <Form.Item name={ipField.name} noStyle>
  137. <Input />
  138. </Form.Item>
  139. {ipFields.length > 1 && (
  140. <Button size="small" onClick={() => removeIp(ipField.name)}>
  141. <MinusOutlined />
  142. </Button>
  143. )}
  144. </Space.Compact>
  145. ))}
  146. </Form.Item>
  147. )}
  148. </Form.List>
  149. <Form.Item name={[field.name, 'keepAlive']} label={t('pages.inbounds.form.keepAlive')}>
  150. <InputNumber min={0} />
  151. </Form.Item>
  152. </div>
  153. ))}
  154. </>
  155. )}
  156. </Form.List>
  157. </>
  158. );
  159. }