Jelajahi Sumber

fix(hysteria2): restore missing masquerade config in inbound form (#4316)

* fix(hysteria2): restore missing masquerade config in inbound form

Fixes #4303

The Hysteria2 Masquerade option was missing from the Stream settings
tab after the v3.0.0 form rewrite. Added the UI form and ensured the
masquerade block is passed through in subscription JSON generation.
Abdalrahman 12 jam lalu
induk
melakukan
60e6b12f4c

+ 2 - 1
frontend/src/models/inbound.js

@@ -687,8 +687,9 @@ export class HysteriaMasquerade extends XrayCommonClass {
     }
 
     static fromJson(json = {}) {
+        const type = ['proxy', 'file', 'string'].includes(json.type) ? json.type : 'proxy';
         return new HysteriaMasquerade(
-            json.type,
+            type,
             json.dir,
             json.url,
             json.rewriteHost,

+ 68 - 0
frontend/src/pages/inbounds/InboundFormModal.vue

@@ -1671,6 +1671,74 @@ watch(
               </a-select>
             </a-form-item>
           </template>
+
+          <!-- ====== Hysteria Masquerade ====== -->
+          <!-- Per https://xtls.github.io/config/transports/hysteria.html#masqobject -->
+          <template v-if="protocol === Protocols.HYSTERIA">
+            <a-form-item label="Masquerade">
+              <a-switch v-model:checked="inbound.stream.hysteria.masqueradeSwitch" />
+            </a-form-item>
+            <template v-if="inbound.stream.hysteria.masqueradeSwitch">
+              <a-form-item label="Type">
+                <a-select v-model:value="inbound.stream.hysteria.masquerade.type" :style="{ width: '50%' }">
+                  <a-select-option value="proxy">Proxy</a-select-option>
+                  <a-select-option value="file">File</a-select-option>
+                  <a-select-option value="string">String</a-select-option>
+                </a-select>
+              </a-form-item>
+
+              <!-- Proxy type: url / rewriteHost / insecure -->
+              <template v-if="inbound.stream.hysteria.masquerade.type === 'proxy'">
+                <a-form-item label="URL">
+                  <a-input v-model:value="inbound.stream.hysteria.masquerade.url" placeholder="https://example.com" />
+                </a-form-item>
+                <a-form-item label="Rewrite Host">
+                  <a-switch v-model:checked="inbound.stream.hysteria.masquerade.rewriteHost" />
+                </a-form-item>
+                <a-form-item label="Insecure">
+                  <a-switch v-model:checked="inbound.stream.hysteria.masquerade.insecure" />
+                </a-form-item>
+              </template>
+
+              <!-- File type: dir -->
+              <a-form-item v-if="inbound.stream.hysteria.masquerade.type === 'file'" label="Directory">
+                <a-input v-model:value="inbound.stream.hysteria.masquerade.dir" placeholder="/path/to/www" />
+              </a-form-item>
+
+              <!-- String type: content / statusCode / headers -->
+              <template v-if="inbound.stream.hysteria.masquerade.type === 'string'">
+                <a-form-item label="Content">
+                  <a-textarea v-model:value="inbound.stream.hysteria.masquerade.content"
+                    :auto-size="{ minRows: 2, maxRows: 6 }" />
+                </a-form-item>
+                <a-form-item label="Status Code">
+                  <a-input-number v-model:value="inbound.stream.hysteria.masquerade.statusCode" :min="100" :max="599"
+                    placeholder="200" />
+                </a-form-item>
+                <a-form-item label="Headers">
+                  <a-button size="small" @click="inbound.stream.hysteria.masquerade.addHeader('', '')">
+                    <template #icon>
+                      <PlusOutlined />
+                    </template>
+                  </a-button>
+                </a-form-item>
+                <a-form-item v-if="inbound.stream.hysteria.masquerade.headers.length > 0" :wrapper-col="{ span: 24 }">
+                  <a-input-group v-for="(h, idx) in inbound.stream.hysteria.masquerade.headers" :key="`mh-${idx}`"
+                    compact class="mb-8">
+                    <a-input :style="{ width: '45%' }" v-model:value="h.name" placeholder="Name">
+                      <template #addonBefore>{{ idx + 1 }}</template>
+                    </a-input>
+                    <a-input :style="{ width: '45%' }" v-model:value="h.value" placeholder="Value" />
+                    <a-button @click="inbound.stream.hysteria.masquerade.removeHeader(idx)">
+                      <template #icon>
+                        <MinusOutlined />
+                      </template>
+                    </a-button>
+                  </a-input-group>
+                </a-form-item>
+              </template>
+            </template>
+          </template>
         </a-form>
 
         <!-- ====== FinalMask (TCP/UDP masks + QUIC params) ====== -->

+ 3 - 0
sub/subJsonService.go

@@ -447,6 +447,9 @@ func (s *SubJsonService) genHy(inbound *model.Inbound, newStream map[string]any,
 	if udpIdleTimeout, ok := hyStream["udpIdleTimeout"].(float64); ok {
 		outHyStream["udpIdleTimeout"] = int(udpIdleTimeout)
 	}
+	if masquerade, ok := hyStream["masquerade"].(map[string]any); ok {
+		outHyStream["masquerade"] = masquerade
+	}
 	newStream["hysteriaSettings"] = outHyStream
 
 	if finalmask, ok := hyStream["finalmask"].(map[string]any); ok {