mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-04-29 02:46:08 +00:00
DNS outbound: Add rules
Some checks are pending
Release 3X-UI / Analyze Go code (push) Waiting to run
Release 3X-UI / build (386) (push) Blocked by required conditions
Release 3X-UI / build (amd64) (push) Blocked by required conditions
Release 3X-UI / build (arm64) (push) Blocked by required conditions
Release 3X-UI / build (armv5) (push) Blocked by required conditions
Release 3X-UI / build (armv6) (push) Blocked by required conditions
Release 3X-UI / build (armv7) (push) Blocked by required conditions
Release 3X-UI / build (s390x) (push) Blocked by required conditions
Release 3X-UI / Build for Windows (push) Blocked by required conditions
Some checks are pending
Release 3X-UI / Analyze Go code (push) Waiting to run
Release 3X-UI / build (386) (push) Blocked by required conditions
Release 3X-UI / build (amd64) (push) Blocked by required conditions
Release 3X-UI / build (arm64) (push) Blocked by required conditions
Release 3X-UI / build (armv5) (push) Blocked by required conditions
Release 3X-UI / build (armv6) (push) Blocked by required conditions
Release 3X-UI / build (armv7) (push) Blocked by required conditions
Release 3X-UI / build (s390x) (push) Blocked by required conditions
Release 3X-UI / Build for Windows (push) Blocked by required conditions
This commit is contained in:
parent
35609b7b13
commit
a62c637632
4 changed files with 209 additions and 24 deletions
|
|
@ -97,6 +97,74 @@ const Address_Port_Strategy = {
|
||||||
TxtPortAndAddress: "txtportandaddress"
|
TxtPortAndAddress: "txtportandaddress"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DNSRuleActions = ['direct', 'drop', 'reject', 'hijack'];
|
||||||
|
|
||||||
|
function normalizeDNSRuleField(value) {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.map(item => item.toString().trim()).filter(item => item.length > 0).join(',');
|
||||||
|
}
|
||||||
|
return value.toString().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDNSRuleAction(action) {
|
||||||
|
action = ObjectUtil.isEmpty(action) ? 'direct' : action.toString().toLowerCase().trim();
|
||||||
|
return DNSRuleActions.includes(action) ? action : 'direct';
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseLegacyDNSBlockTypes(blockTypes) {
|
||||||
|
if (blockTypes === null || blockTypes === undefined || blockTypes === '') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(blockTypes)) {
|
||||||
|
return blockTypes
|
||||||
|
.map(item => Number(item))
|
||||||
|
.filter(item => Number.isInteger(item) && item >= 0 && item <= 65535);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof blockTypes === 'number') {
|
||||||
|
return Number.isInteger(blockTypes) && blockTypes >= 0 && blockTypes <= 65535 ? [blockTypes] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return blockTypes
|
||||||
|
.toString()
|
||||||
|
.split(',')
|
||||||
|
.map(item => item.trim())
|
||||||
|
.filter(item => /^\d+$/.test(item))
|
||||||
|
.map(item => Number(item))
|
||||||
|
.filter(item => item >= 0 && item <= 65535);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildLegacyDNSRules(nonIPQuery, blockTypes) {
|
||||||
|
const mode = ['reject', 'drop', 'skip'].includes(nonIPQuery) ? nonIPQuery : 'reject';
|
||||||
|
const rules = [];
|
||||||
|
const parsedBlockTypes = parseLegacyDNSBlockTypes(blockTypes);
|
||||||
|
|
||||||
|
if (parsedBlockTypes.length > 0) {
|
||||||
|
rules.push(new Outbound.DNSRule(mode === 'reject' ? 'reject' : 'drop', parsedBlockTypes.join(',')));
|
||||||
|
}
|
||||||
|
|
||||||
|
rules.push(new Outbound.DNSRule('hijack', '1,28'));
|
||||||
|
rules.push(new Outbound.DNSRule(mode === 'skip' ? 'direct' : mode));
|
||||||
|
|
||||||
|
return rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDNSRulesFromJson(json = {}) {
|
||||||
|
if (Array.isArray(json.rules) && json.rules.length > 0) {
|
||||||
|
return json.rules.map(rule => Outbound.DNSRule.fromJson(rule));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.nonIPQuery !== undefined || json.blockTypes !== undefined) {
|
||||||
|
return buildLegacyDNSRules(json.nonIPQuery, json.blockTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
Object.freeze(Protocols);
|
Object.freeze(Protocols);
|
||||||
Object.freeze(SSMethods);
|
Object.freeze(SSMethods);
|
||||||
Object.freeze(TLS_FLOW_CONTROL);
|
Object.freeze(TLS_FLOW_CONTROL);
|
||||||
|
|
@ -107,6 +175,7 @@ Object.freeze(WireguardDomainStrategy);
|
||||||
Object.freeze(USERS_SECURITY);
|
Object.freeze(USERS_SECURITY);
|
||||||
Object.freeze(MODE_OPTION);
|
Object.freeze(MODE_OPTION);
|
||||||
Object.freeze(Address_Port_Strategy);
|
Object.freeze(Address_Port_Strategy);
|
||||||
|
Object.freeze(DNSRuleActions);
|
||||||
|
|
||||||
class CommonClass {
|
class CommonClass {
|
||||||
|
|
||||||
|
|
@ -1277,20 +1346,69 @@ Outbound.BlackholeSettings = class extends CommonClass {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Outbound.DNSRule = class extends CommonClass {
|
||||||
|
constructor(action = 'direct', qtype = '', domain = '') {
|
||||||
|
super();
|
||||||
|
this.action = action;
|
||||||
|
this.qtype = qtype;
|
||||||
|
this.domain = domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
return new Outbound.DNSRule(
|
||||||
|
json.action,
|
||||||
|
normalizeDNSRuleField(json.qtype),
|
||||||
|
normalizeDNSRuleField(json.domain),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
const rule = {
|
||||||
|
action: normalizeDNSRuleAction(this.action),
|
||||||
|
};
|
||||||
|
|
||||||
|
const qtype = normalizeDNSRuleField(this.qtype);
|
||||||
|
if (!ObjectUtil.isEmpty(qtype)) {
|
||||||
|
if (/^\d+$/.test(qtype)) {
|
||||||
|
rule.qtype = Number(qtype);
|
||||||
|
} else {
|
||||||
|
rule.qtype = qtype;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const domains = normalizeDNSRuleField(this.domain)
|
||||||
|
.split(',')
|
||||||
|
.map(d => d.trim())
|
||||||
|
.filter(d => d.length > 0);
|
||||||
|
if (domains.length > 0) {
|
||||||
|
rule.domain = domains;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rule;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Outbound.DNSSettings = class extends CommonClass {
|
Outbound.DNSSettings = class extends CommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
network = 'udp',
|
network = 'udp',
|
||||||
address = '',
|
address = '',
|
||||||
port = 53,
|
port = 53,
|
||||||
nonIPQuery = 'reject',
|
rules = []
|
||||||
blockTypes = []
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.network = network;
|
this.network = network;
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
this.nonIPQuery = nonIPQuery;
|
this.rules = Array.isArray(rules) ? rules.map(rule => rule instanceof Outbound.DNSRule ? rule : Outbound.DNSRule.fromJson(rule)) : [];
|
||||||
this.blockTypes = blockTypes;
|
}
|
||||||
|
|
||||||
|
addRule(action = 'direct') {
|
||||||
|
this.rules.push(new Outbound.DNSRule(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
delRule(index) {
|
||||||
|
this.rules.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
|
|
@ -1298,10 +1416,23 @@ Outbound.DNSSettings = class extends CommonClass {
|
||||||
json.network,
|
json.network,
|
||||||
json.address,
|
json.address,
|
||||||
json.port,
|
json.port,
|
||||||
json.nonIPQuery,
|
getDNSRulesFromJson(json),
|
||||||
json.blockTypes,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
const json = {
|
||||||
|
network: this.network,
|
||||||
|
address: this.address,
|
||||||
|
port: this.port,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.rules.length > 0) {
|
||||||
|
json.rules = Outbound.DNSRule.toJsonArray(this.rules);
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Outbound.VmessSettings = class extends CommonClass {
|
Outbound.VmessSettings = class extends CommonClass {
|
||||||
constructor(address, port, id, security) {
|
constructor(address, port, id, security) {
|
||||||
|
|
|
||||||
|
|
@ -190,22 +190,73 @@
|
||||||
>
|
>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="non-IP queries">
|
<a-form-item label="Rules">
|
||||||
|
<a-button
|
||||||
|
icon="plus"
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
@click="outbound.settings.addRule()"
|
||||||
|
></a-button>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form
|
||||||
|
v-for="(rule, index) in outbound.settings.rules"
|
||||||
|
:colon="false"
|
||||||
|
:label-col="{ md: {span:8} }"
|
||||||
|
:wrapper-col="{ md: {span:14} }"
|
||||||
|
>
|
||||||
|
<a-divider :style="{ margin: '0' }">
|
||||||
|
Rule [[ index + 1 ]]
|
||||||
|
<a-icon
|
||||||
|
type="delete"
|
||||||
|
@click="() => outbound.settings.delRule(index)"
|
||||||
|
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"
|
||||||
|
></a-icon>
|
||||||
|
</a-divider>
|
||||||
|
|
||||||
|
<a-form-item label="Action">
|
||||||
<a-select
|
<a-select
|
||||||
v-model="outbound.settings.nonIPQuery"
|
v-model="rule.action"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
>
|
>
|
||||||
<a-select-option v-for="s in ['reject','drop','skip']" :value="s"
|
<a-select-option v-for="action in DNSRuleActions" :value="action"
|
||||||
>[[ s ]]</a-select-option
|
>[[ action ]]</a-select-option
|
||||||
>
|
>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
|
||||||
v-if="outbound.settings.nonIPQuery === 'skip'"
|
<a-form-item>
|
||||||
label="Block Types"
|
<template slot="label">
|
||||||
>
|
<a-tooltip>
|
||||||
<a-input v-model.number="outbound.settings.blockTypes"></a-input>
|
<template slot="title">
|
||||||
|
<span>Single qtype (e.g. 28) or list/range (e.g. 1,3,23-24)</span>
|
||||||
|
</template>
|
||||||
|
QType
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input
|
||||||
|
v-model.trim="rule.qtype"
|
||||||
|
placeholder="1,3,23-24"
|
||||||
|
></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item>
|
||||||
|
<template slot="label">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>Comma-separated domain rules, e.g. domain:example.com,full:example.com</span>
|
||||||
|
</template>
|
||||||
|
Domain
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input
|
||||||
|
v-model.trim="rule.domain"
|
||||||
|
placeholder="domain:example.com,full:example.com"
|
||||||
|
></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- wireguard settings -->
|
<!-- wireguard settings -->
|
||||||
|
|
|
||||||
|
|
@ -779,7 +779,7 @@ func (s *InboundService) writeBackClientSubID(sourceInboundID int, sourceProtoco
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsBytes, err := json.Marshal(map[string][]model.Client{
|
settingsBytes, err := json.Marshal(map[string][]model.Client{
|
||||||
"clients": []model.Client{client},
|
"clients": {client},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|
|
||||||
|
|
@ -353,14 +353,17 @@ func (s *Server) startTask() {
|
||||||
isTgbotenabled, err := s.settingService.GetTgbotEnabled()
|
isTgbotenabled, err := s.settingService.GetTgbotEnabled()
|
||||||
if (err == nil) && (isTgbotenabled) {
|
if (err == nil) && (isTgbotenabled) {
|
||||||
runtime, err := s.settingService.GetTgbotRuntime()
|
runtime, err := s.settingService.GetTgbotRuntime()
|
||||||
if err != nil || runtime == "" {
|
if err != nil {
|
||||||
logger.Errorf("Add NewStatsNotifyJob error[%s], Runtime[%s] invalid, will run default", err, runtime)
|
logger.Warningf("Add NewStatsNotifyJob: failed to load runtime: %v; using default @daily", err)
|
||||||
|
runtime = "@daily"
|
||||||
|
} else if strings.TrimSpace(runtime) == "" {
|
||||||
|
logger.Warning("Add NewStatsNotifyJob runtime is empty, using default @daily")
|
||||||
runtime = "@daily"
|
runtime = "@daily"
|
||||||
}
|
}
|
||||||
logger.Infof("Tg notify enabled,run at %s", runtime)
|
logger.Infof("Tg notify enabled,run at %s", runtime)
|
||||||
_, err = s.cron.AddJob(runtime, job.NewStatsNotifyJob())
|
_, err = s.cron.AddJob(runtime, job.NewStatsNotifyJob())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("Add NewStatsNotifyJob error", err)
|
logger.Warningf("Add NewStatsNotifyJob: failed to schedule runtime %q: %v", runtime, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue