update 2024-09-08 22:33:35

This commit is contained in:
actions-user 2024-09-08 22:33:35 +08:00
parent fe33ea74ec
commit b177d2de76
25 changed files with 10841 additions and 0 deletions

10
luci-app-mihomo/Makefile Normal file
View File

@ -0,0 +1,10 @@
include $(TOPDIR)/rules.mk
PKG_VERSION:=1.8.0
LUCI_TITLE:=LuCI Support for mihomo
LUCI_DEPENDS:=+luci-base +mihomo
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,113 @@
'use strict';
'require baseclass';
'require uci';
'require fs';
'require rpc';
const homeDir = '/etc/mihomo';
const profilesDir = `${homeDir}/profiles`;
const mixinFilePath = `${homeDir}/mixin.yaml`;
const runDir = `${homeDir}/run`;
const runAppLogPath = `${runDir}/app.log`;
const runCoreLogPath = `${runDir}/core.log`;
const runProfilePath = `${runDir}/config.yaml`;
const nftDir = `${homeDir}/nftables`;
const reservedIPNFT = `${nftDir}/reserved_ip.nft`;
const reservedIP6NFT = `${nftDir}/reserved_ip6.nft`;
return baseclass.extend({
homeDir: homeDir,
profilesDir: profilesDir,
mixinFilePath: mixinFilePath,
runDir: runDir,
runAppLogPath: runAppLogPath,
runCoreLogPath: runCoreLogPath,
runProfilePath: runProfilePath,
reservedIPNFT: reservedIPNFT,
reservedIP6NFT: reservedIP6NFT,
callServiceList: rpc.declare({
object: 'service',
method: 'list',
params: ['name'],
expect: { '': {} }
}),
getAppLog: function () {
return L.resolveDefault(fs.read_direct(this.runAppLogPath));
},
getCoreLog: function () {
return L.resolveDefault(fs.read_direct(this.runCoreLogPath));
},
clearAppLog: function () {
return fs.exec_direct('/usr/libexec/mihomo-call', ['clear', 'app_log']);
},
clearCoreLog: function () {
return fs.exec_direct('/usr/libexec/mihomo-call', ['clear', 'core_log']);
},
listProfiles: function () {
return L.resolveDefault(fs.list(this.profilesDir), []);
},
status: async function () {
try {
return (await this.callServiceList('mihomo'))['mihomo']['instances']['mihomo']['running'];
} catch (ignored) {
return false;
}
},
reload: function () {
return fs.exec_direct('/usr/libexec/mihomo-call', ['service', 'reload']);
},
restart: function () {
return fs.exec_direct('/usr/libexec/mihomo-call', ['service', 'restart']);
},
appVersion: function () {
return L.resolveDefault(fs.exec_direct('/usr/libexec/mihomo-call', ['version', 'app']), 'Unknown');
},
coreVersion: function () {
return L.resolveDefault(fs.exec_direct('/usr/libexec/mihomo-call', ['version', 'core']), 'Unknown');
},
callMihomoAPI: async function (method, path, body) {
const running = await this.status();
if (running) {
const apiPort = uci.get('mihomo', 'mixin', 'api_port');
const apiSecret = uci.get('mihomo', 'mixin', 'api_secret');
const url = `http://${window.location.hostname}:${apiPort}${path}`;
await fetch(url, {
method: method,
headers: { 'Authorization': `Bearer ${apiSecret}` },
body: body
})
} else {
alert(_('Service is not running.'));
}
},
openDashboard: async function () {
const running = await this.status();
if (running) {
const uiName = uci.get('mihomo', 'mixin', 'ui_name');
const apiPort = uci.get('mihomo', 'mixin', 'api_port');
const apiSecret = uci.get('mihomo', 'mixin', 'api_secret');
let url;
if (uiName) {
url = `http://${window.location.hostname}:${apiPort}/ui/${uiName}/?host=${window.location.hostname}&hostname=${window.location.hostname}&port=${apiPort}&secret=${apiSecret}`;
} else {
url = `http://${window.location.hostname}:${apiPort}/ui/?host=${window.location.hostname}&hostname=${window.location.hostname}&port=${apiPort}&secret=${apiSecret}`;
}
setTimeout(() => window.open(url, '_blank'), 0);
} else {
alert(_('Service is not running.'));
}
},
})

View File

@ -0,0 +1,515 @@
'use strict';
'require form';
'require view';
'require uci';
'require fs';
'require network';
'require rpc';
'require poll';
'require tools.widgets as widgets';
'require tools.mihomo as mihomo';
function renderStatus(running) {
return updateStatus(E('input', { id: 'core_status', style: 'border: unset; font-style: italic; font-weight: bold;', readonly: '' }), running);
}
function updateStatus(element, running) {
if (element) {
element.style.color = running ? 'green' : 'red';
element.value = running ? _('Running') : _('Not Running');
}
return element;
}
return view.extend({
load: function () {
return Promise.all([
uci.load('mihomo'),
mihomo.listProfiles(),
mihomo.appVersion(),
mihomo.coreVersion(),
mihomo.status(),
network.getHostHints(),
]);
},
render: function (data) {
const subscriptions = uci.sections('mihomo', 'subscription');
const profiles = data[1];
const appVersion = data[2];
const coreVersion = data[3];
const running = data[4];
const hosts = data[5].hosts;
let m, s, o, so;
m = new form.Map('mihomo', _('MihomoTProxy'), `${_('Transparent Proxy with Mihomo on OpenWrt.')} <a href="https://github.com/morytyann/OpenWrt-mihomo/wiki" target="_blank">${_('How To Use')}</a>`);
s = m.section(form.NamedSection, 'status', 'status', _('Status'));
o = s.option(form.Value, '_app_version', _('App Version'));
o.readonly = true;
o.load = function (section_id) {
return appVersion.trim();
};
o.write = function () { };
o = s.option(form.Value, '_core_version', _('Core Version'));
o.readonly = true;
o.load = function (section_id) {
return coreVersion.trim();
};
o.write = function () { };
o = s.option(form.DummyValue, '_core_status', _('Core Status'));
o.cfgvalue = function (section_id) {
return renderStatus(running);
};
poll.add(function () {
return L.resolveDefault(mihomo.status()).then(function (running) {
updateStatus(document.getElementById('core_status'), running);
});
});
o = s.option(form.Button, 'reload', '-');
o.inputstyle = 'action';
o.inputtitle = _('Reload Service');
o.onclick = function () {
return mihomo.reload();
};
o = s.option(form.Button, 'restart', '-');
o.inputstyle = 'negative';
o.inputtitle = _('Restart Service');
o.onclick = function () {
return mihomo.restart();
};
o = s.option(form.Button, 'update_dashboard', '-');
o.inputstyle = 'positive';
o.inputtitle = _('Update Dashboard');
o.onclick = function () {
return mihomo.callMihomoAPI('POST', '/upgrade/ui');
};
o = s.option(form.Button, 'open_dashboard', '-');
o.inputtitle = _('Open Dashboard');
o.onclick = function () {
return mihomo.openDashboard();
};
s = m.section(form.NamedSection, 'config', 'config', _('Basic Config'));
o = s.option(form.Flag, 'enabled', _('Enable'));
o.rmempty = false;
o = s.option(form.Flag, 'scheduled_restart', _('Scheduled Restart'));
o.rmempty = false;
o = s.option(form.Value, 'cron_expression', _('Cron Expression'));
o.retain = true;
o.rmempty = false;
o.depends('scheduled_restart', '1');
o = s.option(form.ListValue, 'profile', _('Choose Profile'));
o.rmempty = false;
for (const profile of profiles) {
o.value('file:' + profile.name, _('File:') + profile.name);
}
for (const subscription of subscriptions) {
o.value('subscription:' + subscription['.name'], _('Subscription:') + subscription.name);
}
o = s.option(form.FileUpload, 'upload_profile', _('Upload Profile'));
o.root_directory = mihomo.profilesDir;
o = s.option(form.Flag, 'mixin', _('Mixin'));
o.rmempty = false;
o = s.option(form.Flag, 'test_profile', _('Test Profile'));
o.rmempty = false;
o = s.option(form.Flag, 'fast_reload', _('Fast Reload'));
o.rmempty = false;
s = m.section(form.NamedSection, 'proxy', 'proxy', _('Proxy Config'));
s.tab('transparent_proxy', _('Transparent Proxy'));
o = s.taboption('transparent_proxy', form.Flag, 'transparent_proxy', _('Enable'));
o.rmempty = false;
o = s.taboption('transparent_proxy', form.ListValue, 'tcp_transparent_proxy_mode', _('TCP Proxy Mode'));
o.value('redirect', _('Redirect Mode'));
o.value('tproxy', _('TPROXY Mode'));
o.value('tun', _('TUN Mode'));
o = s.taboption('transparent_proxy', form.ListValue, 'udp_transparent_proxy_mode', _('UDP Proxy Mode'));
o.value('tproxy', _('TPROXY Mode'));
o.value('tun', _('TUN Mode'));
o = s.taboption('transparent_proxy', form.Flag, 'ipv4_dns_hijack', _('IPv4 DNS Hijack'));
o.rmempty = false;
o = s.taboption('transparent_proxy', form.Flag, 'ipv6_dns_hijack', _('IPv6 DNS Hijack'));
o.rmempty = false;
o = s.taboption('transparent_proxy', form.Flag, 'ipv4_proxy', _('IPv4 Proxy'));
o.rmempty = false;
o = s.taboption('transparent_proxy', form.Flag, 'ipv6_proxy', _('IPv6 Proxy'));
o.rmempty = false;
o = s.taboption('transparent_proxy', form.Flag, 'router_proxy', _('Router Proxy'));
o.rmempty = false;
o = s.taboption('transparent_proxy', form.Flag, 'lan_proxy', _('Lan Proxy'));
o.rmempty = false;
s.tab('access_control', _('Access Control'));
o = s.taboption('access_control', form.ListValue, 'access_control_mode', _('Mode'));
o.value('all', _('All Mode'));
o.value('allow', _('Allow Mode'));
o.value('block', _('Block Mode'));
o = s.taboption('access_control', form.DynamicList, 'acl_ip', 'IP');
o.datatype = 'ipmask4';
o.retain = true;
o.depends('access_control_mode', 'allow');
o.depends('access_control_mode', 'block');
for (const mac in hosts) {
const host = hosts[mac];
for (const ip of host.ipaddrs) {
const hint = host.name || mac;
o.value(ip, hint ? '%s (%s)'.format(ip, hint) : ip);
}
}
o = s.taboption('access_control', form.DynamicList, 'acl_ip6', 'IP6');
o.datatype = 'ipmask6';
o.retain = true;
o.depends('access_control_mode', 'allow');
o.depends('access_control_mode', 'block');
for (const mac in hosts) {
const host = hosts[mac];
for (const ip of host.ip6addrs) {
const hint = host.name || mac;
o.value(ip, hint ? '%s (%s)'.format(ip, hint) : ip);
}
}
o = s.taboption('access_control', form.DynamicList, 'acl_mac', 'MAC');
o.datatype = 'macaddr';
o.retain = true;
o.depends('access_control_mode', 'allow');
o.depends('access_control_mode', 'block');
for (const mac in hosts) {
const host = hosts[mac];
const hint = host.name || host.ipaddrs[0];
o.value(mac, hint ? '%s (%s)'.format(mac, hint) : mac);
}
s.tab('bypass', _('Bypass'));
o = s.taboption('bypass', form.Flag, 'bypass_china_mainland_ip', _('Bypass China Mainland IP'));
o.rmempty = false;
o = s.taboption('bypass', form.Value, 'acl_tcp_dport', _('Destination TCP Port to Proxy'));
o.rmempty = false;
o.value('0-65535', _('All Port'));
o.value('21 22 80 110 143 194 443 465 993 995 8080 8443', _('Commonly Used Port'));
o = s.taboption('bypass', form.Value, 'acl_udp_dport', _('Destination UDP Port to Proxy'));
o.rmempty = false;
o.value('0-65535', _('All Port'));
o.value('123 443 8443', _('Commonly Used Port'));
s = m.section(form.TableSection, 'subscription', _('Subscription Config'));
s.addremove = true;
s.anonymous = true;
o = s.option(form.Value, 'name', _('Subscription Name'));
o.rmempty = false;
o.width = '15%';
o = s.option(form.Value, 'url', _('Subscription Url'));
o.rmempty = false;
o = s.option(form.Value, 'user_agent', _('User Agent'));
o.default = 'mihomo';
o.rmempty = false;
o.width = '15%';
o.value('mihomo');
o.value('clash.meta');
o.value('clash');
s = m.section(form.NamedSection, 'mixin', 'mixin', _('Mixin Config'));
s.tab('general', _('General Config'));
o = s.taboption('general', form.ListValue, 'log_level', _('Log Level'));
o.value('silent');
o.value('error');
o.value('warning');
o.value('info');
o.value('debug');
o = s.taboption('general', form.ListValue, 'mode', _('Proxy Mode'));
o.value('general', _('Global Mode'));
o.value('rule', _('Rule Mode'));
o.value('direct', _('Direct Mode'));
o = s.taboption('general', form.ListValue, 'match_process', _('Match Process'));
o.value('strict', _('Auto'));
o.value('always', _('Enable'));
o.value('off', _('Disable'));
o = s.taboption('general', widgets.NetworkSelect, 'outbound_interface', _('Outbound Interface'));
o.optional = true;
o.rmempty = false;
o = s.taboption('general', form.Flag, 'ipv6', _('IPv6'));
o.rmempty = false;
o = s.taboption('general', form.Value, 'tcp_keep_alive_idle', _('TCP Keep Alive Idle'));
o.datatype = 'integer';
o.placeholder = '600';
o = s.taboption('general', form.Value, 'tcp_keep_alive_interval', _('TCP Keep Alive Interval'));
o.datatype = 'integer';
o.placeholder = '15';
s.tab('external_control', _('External Control Config'));
o = s.taboption('external_control', form.Value, 'ui_name', _('UI Name'));
o.rmempty = false;
o = s.taboption('external_control', form.Value, 'ui_url', _('UI Url'));
o.rmempty = false;
o.value('https://mirror.ghproxy.com/https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip', 'MetaCubeXD')
o.value('https://mirror.ghproxy.com/https://github.com/MetaCubeX/Yacd-meta/archive/refs/heads/gh-pages.zip', 'YACD')
o.value('https://mirror.ghproxy.com/https://github.com/MetaCubeX/Razord-meta/archive/refs/heads/gh-pages.zip', 'Razord')
o = s.taboption('external_control', form.Value, 'api_port', _('API Port'));
o.datatype = 'port';
o.placeholder = '9090';
o = s.taboption('external_control', form.Value, 'api_secret', _('API Secret'));
o.rmempty = false;
o = s.taboption('external_control', form.Flag, 'selection_cache', _('Save Proxy Selection'));
o.rmempty = false;
s.tab('inbound', _('Inbound Config'));
o = s.taboption('inbound', form.Flag, 'allow_lan', _('Allow Lan'));
o.rmempty = false;
o = s.taboption('inbound', form.Value, 'http_port', _('HTTP Port'));
o.datatype = 'port';
o.placeholder = '8080';
o = s.taboption('inbound', form.Value, 'socks_port', _('SOCKS Port'));
o.datatype = 'port';
o.placeholder = '1080';
o = s.taboption('inbound', form.Value, 'mixed_port', _('Mixed Port'));
o.datatype = 'port';
o.placeholder = '7890';
o = s.taboption('inbound', form.Value, 'redir_port', _('Redirect Port'));
o.datatype = 'port';
o.placeholder = '7891';
o = s.taboption('inbound', form.Value, 'tproxy_port', _('TPROXY Port'));
o.datatype = 'port';
o.placeholder = '7892';
o = s.taboption('inbound', form.Flag, 'authentication', _('Authentication'));
o.rmempty = false;
o = s.taboption('inbound', form.SectionValue, '_authentications', form.TableSection, 'authentication', _('Edit Authentications'));
o.retain = true;
o.depends('authentication', '1');
o.subsection.anonymous = true;
o.subsection.addremove = true;
so = o.subsection.option(form.Flag, 'enabled', _('Enable'));
so.rmempty = false;
so = o.subsection.option(form.Value, 'username', _('Username'));
so.rmempty = false;
so = o.subsection.option(form.Value, 'password', _('Password'));
so.rmempty = false;
s.tab('tun', _('TUN Config'));
o = s.taboption('tun', form.ListValue, 'tun_stack', _('Stack'));
o.value('system', 'System');
o.value('gvisor', 'gVisor');
o.value('mixed', 'Mixed');
o = s.taboption('tun', form.Value, 'tun_mtu', _('MTU'));
o.placeholder = '9000';
o = s.taboption('tun', form.Flag, 'tun_gso', _('GSO'));
o.rmempty = false;
o = s.taboption('tun', form.Value, 'tun_gso_max_size', _('GSO Max Size'));
o.placeholder = '65536';
o.depends('tun_gso', '1');
o = s.taboption('tun', form.Flag, 'tun_endpoint_independent_nat', _('Endpoint Independent NAT'));
o.rmempty = false;
s.tab('dns', _('DNS Config'));
o = s.taboption('dns', form.Value, 'dns_port', _('DNS Port'));
o.datatype = 'port';
o.placeholder = '1053';
o = s.taboption('dns', form.ListValue, 'dns_mode', _('DNS Mode'));
o.value('normal', 'Normal');
o.value('fake-ip', 'Fake-IP');
o.value('redir-host', 'Redir-Host');
o = s.taboption('dns', form.Value, 'fake_ip_range', _('Fake-IP Range'));
o.datatype = 'cidr4';
o.placeholder = '198.18.0.1/16';
o.retain = true;
o.depends('dns_mode', 'fake-ip');
o = s.taboption('dns', form.Flag, 'fake_ip_filter', _('Overwrite Fake-IP Filter'));
o.retain = true;
o.rmempty = false;
o.depends('dns_mode', 'fake-ip');
o = s.taboption('dns', form.DynamicList, 'fake_ip_filters', _('Edit Fake-IP Filters'));
o.retain = true;
o.depends({ 'dns_mode': 'fake-ip', 'fake_ip_filter': '1' });
o = s.taboption('dns', form.ListValue, 'fake_ip_filter_mode', _('Fake-IP Filter Mode'))
o.value('blacklist', _('Block Mode'));
o.value('whitelist', _('Allow Mode'));
o.depends({ 'dns_mode': 'fake-ip', 'fake_ip_filter': '1' });
o = s.taboption('dns', form.Flag, 'fake_ip_cache', _('Fake-IP Cache'));
o.retain = true;
o.rmempty = false;
o.depends('dns_mode', 'fake-ip');
o = s.taboption('dns', form.Flag, 'dns_respect_rules', _('Respect Rules'));
o.rmempty = false;
o = s.taboption('dns', form.Flag, 'dns_ipv6', _('IPv6'));
o.rmempty = false;
o = s.taboption('dns', form.Flag, 'dns_system_hosts', _('Use System Hosts'));
o.rmempty = false;
o = s.taboption('dns', form.Flag, 'dns_hosts', _('Use Hosts'));
o.rmempty = false;
o = s.taboption('dns', form.Flag, 'hosts', _('Overwrite Hosts'));
o.rmempty = false;
o = s.taboption('dns', form.SectionValue, '_hosts', form.TableSection, 'host', _('Edit Hosts'));
o.retain = true;
o.depends('hosts', '1');
o.subsection.anonymous = true;
o.subsection.addremove = true;
so = o.subsection.option(form.Flag, 'enabled', _('Enable'));
so.rmempty = false;
so = o.subsection.option(form.Value, 'domain_name', _('Domain Name'));
so.rmempty = false;
so = o.subsection.option(form.DynamicList, 'ip', _('IP'));
o = s.taboption('dns', form.Flag, 'dns_nameserver', _('Overwrite Nameserver'));
o.rmempty = false;
o = s.taboption('dns', form.SectionValue, '_dns_nameserver', form.TableSection, 'nameserver', _('Edit Nameservers'));
o.retain = true;
o.depends('dns_nameserver', '1');
o.subsection.anonymous = true;
o.subsection.addremove = false;
so = o.subsection.option(form.Flag, 'enabled', _('Enable'));
so.rmempty = false;
so = o.subsection.option(form.ListValue, 'type', _('Type'));
so.readonly = true;
so.value('default-nameserver');
so.value('proxy-server-nameserver');
so.value('nameserver');
so.value('fallback');
so = o.subsection.option(form.DynamicList, 'nameserver', _('Nameserver'));
o = s.taboption('dns', form.Flag, 'dns_nameserver_policy', _('Overwrite Nameserver Policy'));
o.rmempty = false;
o = s.taboption('dns', form.SectionValue, '_dns_nameserver_policies', form.TableSection, 'nameserver_policy', _('Edit Nameserver Policies'));
o.retain = true;
o.depends('dns_nameserver_policy', '1');
o.subsection.anonymous = true;
o.subsection.addremove = true;
so = o.subsection.option(form.Flag, 'enabled', _('Enable'));
so.rmempty = false;
so = o.subsection.option(form.Value, 'matcher', _('Matcher'));
so.rmempty = false;
so = o.subsection.option(form.DynamicList, 'nameserver', _('Nameserver'));
s.tab('geox', _('GeoX Config'));
o = s.taboption('geox', form.ListValue, 'geoip_format', _('GeoIP Format'));
o.value('dat', 'DAT');
o.value('mmdb', 'MMDB');
o = s.taboption('geox', form.ListValue, 'geodata_loader', _('GeoData Loader'));
o.value('standard', _('Standard Loader'));
o.value('memconservative', _('Memory Conservative Loader'));
o = s.taboption('geox', form.Value, 'geosite_url', _('GeoSite Url'));
o.rmempty = false;
o = s.taboption('geox', form.Value, 'geoip_mmdb_url', _('GeoIP(MMDB) Url'));
o.rmempty = false;
o = s.taboption('geox', form.Value, 'geoip_dat_url', _('GeoIP(DAT) Url'));
o.rmempty = false;
o = s.taboption('geox', form.Value, 'geoip_asn_url', _('GeoIP(ASN) Url'));
o.rmempty = false;
o = s.taboption('geox', form.Flag, 'geox_auto_update', _('GeoX Auto Update'));
o.rmempty = false;
o = s.taboption('geox', form.Value, 'geox_update_interval', _('GeoX Update Interval'), _('Hour'));
o.datatype = 'integer';
o.placeholder = '24';
o.retain = true;
o.depends('geox_auto_update', '1');
s.tab('mixin_file_content', _('Mixin File Content'), _('Please go to the editor tab to edit the file for mixin'));
o = s.taboption('mixin_file_content', form.HiddenValue, '_mixin_file_content');
return m.render();
}
});

View File

@ -0,0 +1,63 @@
'use strict';
'require form';
'require view';
'require uci';
'require fs';
'require tools.mihomo as mihomo'
return view.extend({
load: function () {
return Promise.all([
uci.load('mihomo'),
mihomo.listProfiles(),
]);
},
render: function (data) {
const profiles = data[1];
let m, s, o;
m = new form.Map('mihomo');
s = m.section(form.NamedSection, 'editor', 'editor');
o = s.option(form.ListValue, '_profile', _('Choose Profile'));
o.optional = true;
for (const profile of profiles) {
o.value(mihomo.profilesDir + '/' + profile.name, _('File:') + profile.name);
}
o.value(mihomo.mixinFilePath, _('File for Mixin'));
o.value(mihomo.runProfilePath, _('Profile for Startup'));
o.value(mihomo.reservedIPNFT, _('File for Reserved IP'));
o.value(mihomo.reservedIP6NFT, _('File for Reserved IP6'));
o.write = function (section_id, formvalue) {
return true;
};
o.onchange = function (event, section_id, value) {
return L.resolveDefault(fs.read_direct(value), '').then(function (content) {
m.lookupOption('mihomo.editor._profile_content')[0].getUIElement('editor').setValue(content);
});
};
o = s.option(form.TextValue, '_profile_content',);
o.rows = 25;
o.write = function (section_id, formvalue) {
const path = m.lookupOption('mihomo.editor._profile')[0].formvalue('editor');
return fs.write(path, formvalue);
};
o.remove = function (section_id) {
const path = m.lookupOption('mihomo.editor._profile')[0].formvalue('editor');
return fs.write(path);
};
return m.render();
},
handleSaveApply: function (ev, mode) {
return this.handleSave(ev).finally(function() {
return mode === '0' ? mihomo.reload() : mihomo.restart();
});
},
handleReset: null
});

View File

@ -0,0 +1,98 @@
'use strict';
'require form';
'require view';
'require uci';
'require fs';
'require poll';
'require tools.mihomo as mihomo'
return view.extend({
load: function () {
return Promise.all([
uci.load('mihomo'),
mihomo.getAppLog(),
mihomo.getCoreLog()
]);
},
render: function (data) {
const appLog = data[1];
const coreLog = data[2];
let m, s, o;
m = new form.Map('mihomo');
s = m.section(form.NamedSection, 'log', 'log');
s.tab('app_log', _('App Log'));
o = s.taboption('app_log', form.Button, 'clear_app_log');
o.inputstyle = 'negative';
o.inputtitle = _('Clear Log');
o.onclick = function () {
m.lookupOption('mihomo.log._app_log')[0].getUIElement('log').setValue('');
return mihomo.clearAppLog();
};
o = s.taboption('app_log', form.TextValue, '_app_log');
o.rows = 25;
o.wrap = false;
o.load = function (section_id) {
return appLog;
};
o.write = function (section_id, formvalue) {
return true;
};
poll.add(L.bind(function () {
const option = this;
return L.resolveDefault(mihomo.getAppLog()).then(function (log) {
option.getUIElement('log').setValue(log);
});
}, o));
o = s.taboption('app_log', form.Button, 'scroll_app_log_to_bottom');
o.inputtitle = _('Scroll To Bottom');
o.onclick = function () {
const element = m.lookupOption('mihomo.log._app_log')[0].getUIElement('log').node.firstChild;
element.scrollTop = element.scrollHeight;
};
s.tab('core_log', _('Core Log'));
o = s.taboption('core_log', form.Button, 'clear_core_log');
o.inputstyle = 'negative';
o.inputtitle = _('Clear Log');
o.onclick = function () {
m.lookupOption('mihomo.log._core_log')[0].getUIElement('log').setValue('');
return mihomo.clearCoreLog();
};
o = s.taboption('core_log', form.TextValue, '_core_log');
o.rows = 25;
o.wrap = false;
o.load = function (section_id) {
return coreLog;
};
o.write = function (section_id, formvalue) {
return true;
};
poll.add(L.bind(function () {
const option = this;
return L.resolveDefault(mihomo.getCoreLog()).then(function (log) {
option.getUIElement('log').setValue(log);
});
}, o));
o = s.taboption('core_log', form.Button, 'scroll_core_log_to_bottom');
o.inputtitle = _('Scroll To Bottom');
o.onclick = function () {
const element = m.lookupOption('mihomo.log._core_log')[0].getUIElement('log').node.firstChild;
element.scrollTop = element.scrollHeight;
};
return m.render();
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

1
luci-app-mihomo/po/zh-cn Symbolic link
View File

@ -0,0 +1 @@
zh_Hans

View File

@ -0,0 +1,416 @@
msgid "MihomoTProxy"
msgstr "MihomoTProxy"
msgid "Transparent Proxy with Mihomo on OpenWrt."
msgstr "在 OpenWrt 上使用 Mihomo 进行透明代理。"
msgid "How To Use"
msgstr "使用说明"
msgid "Config"
msgstr "配置"
msgid "Status"
msgstr "状态"
msgid "App Version"
msgstr "插件版本"
msgid "Core Version"
msgstr "核心版本"
msgid "Core Status"
msgstr "核心状态"
msgid "Running"
msgstr "运行中"
msgid "Not Running"
msgstr "未在运行"
msgid "Reload Service"
msgstr "重载服务"
msgid "Restart Service"
msgstr "重启服务"
msgid "Update Dashboard"
msgstr "更新面板"
msgid "Open Dashboard"
msgstr "打开面板"
msgid "Basic Config"
msgstr "基础配置"
msgid "Enable"
msgstr "启用"
msgid "Disable"
msgstr "禁用"
msgid "Auto"
msgstr "自动"
msgid "Mode"
msgstr "模式"
msgid "Type"
msgstr "类型"
msgid "Value"
msgstr "值"
msgid "Scheduled Restart"
msgstr "定时重启"
msgid "Cron Expression"
msgstr "Cron 表达式"
msgid "Choose Profile"
msgstr "选择配置文件"
msgid "File:"
msgstr "文件:"
msgid "Subscription:"
msgstr "订阅:"
msgid "Upload Profile"
msgstr "上传配置文件"
msgid "Mixin"
msgstr "混入"
msgid "Test Profile"
msgstr "检查配置文件"
msgid "Fast Reload"
msgstr "快速重载"
msgid "Proxy Config"
msgstr "代理配置"
msgid "Transparent Proxy"
msgstr "透明代理"
msgid "TCP Proxy Mode"
msgstr "TCP 代理模式"
msgid "UDP Proxy Mode"
msgstr "UDP 代理模式"
msgid "Redirect Mode"
msgstr "Redirect 模式"
msgid "TPROXY Mode"
msgstr "TPROXY 模式"
msgid "TUN Mode"
msgstr "TUN 模式"
msgid "IPv4 DNS Hijack"
msgstr "IPv4 DNS 劫持"
msgid "IPv6 DNS Hijack"
msgstr "IPv6 DNS 劫持"
msgid "IPv4 Proxy"
msgstr "IPv4 代理"
msgid "IPv6 Proxy"
msgstr "IPv6 代理"
msgid "Router Proxy"
msgstr "路由器代理"
msgid "Lan Proxy"
msgstr "局域网代理"
msgid "Access Control"
msgstr "访问控制"
msgid "All Mode"
msgstr "全部模式"
msgid "Allow Mode"
msgstr "白名单模式"
msgid "Block Mode"
msgstr "黑名单模式"
msgid "Bypass"
msgstr "绕过"
msgid "Bypass China Mainland IP"
msgstr "绕过中国大陆 IP"
msgid "Destination TCP Port to Proxy"
msgstr "要代理的 TCP 目标端口"
msgid "Destination UDP Port to Proxy"
msgstr "要代理的 UDP 目标端口"
msgid "All Port"
msgstr "全部端口"
msgid "Commonly Used Port"
msgstr "常用端口"
msgid "Subscription Config"
msgstr "订阅配置"
msgid "Subscription Name"
msgstr "订阅名称"
msgid "Subscription Url"
msgstr "订阅链接"
msgid "User Agent"
msgstr "用户代理UA"
msgid "Mixin Config"
msgstr "混入配置"
msgid "General Config"
msgstr "全局配置"
msgid "Proxy Mode"
msgstr "代理模式"
msgid "Global Mode"
msgstr "全局模式"
msgid "Rule Mode"
msgstr "规则模式"
msgid "Direct Mode"
msgstr "直连模式"
msgid "Match Process"
msgstr "匹配进程"
msgid "Outbound Interface"
msgstr "出站接口"
msgid "TCP Keep Alive Idle"
msgstr "TCP Keep Alive 空闲"
msgid "TCP Keep Alive Interval"
msgstr "TCP Keep Alive 间隔"
msgid "Log Level"
msgstr "日志级别"
msgid "External Control Config"
msgstr "外部控制配置"
msgid "UI Name"
msgstr "UI 名称"
msgid "UI Url"
msgstr "UI 下载地址"
msgid "Service is not running."
msgstr "服务未在运行。"
msgid "API Port"
msgstr "API 端口"
msgid "API Secret"
msgstr "API 密钥"
msgid "Save Proxy Selection"
msgstr "保存节点/策略组选择"
msgid "Inbound Config"
msgstr "入站配置"
msgid "Allow Lan"
msgstr "允许局域网访问"
msgid "HTTP Port"
msgstr "HTTP 端口"
msgid "SOCKS Port"
msgstr "SOCKS 端口"
msgid "Mixed Port"
msgstr "混合端口"
msgid "Redirect Port"
msgstr "Redirect 端口"
msgid "TPROXY Port"
msgstr "TPROXY 端口"
msgid "Authentication"
msgid "身份验证"
msgid "Edit Authentications"
msgstr "编辑身份验证"
msgid "username"
msgstr "用户名"
msgid "password"
msgstr "密码"
msgid "TUN Config"
msgstr "TUN 配置"
msgid "Stack"
msgstr "栈"
msgid "Device"
msgstr "设备"
msgid "MTU"
msgstr "最大传输单元"
msgid "GSO"
msgstr "通用分段卸载"
msgid "GSO Max Size"
msgstr "分段最大长度"
msgid "Endpoint Independent NAT"
msgstr "独立于端点的 NAT"
msgid "DNS Config"
msgstr "DNS 配置"
msgid "DNS Port"
msgstr "DNS 端口"
msgid "DNS Mode"
msgstr "DNS 模式"
msgid "Fake-IP Range"
msgstr "Fake-IP 范围"
msgid "Overwrite Fake-IP Filter"
msgstr "覆盖 Fake-IP 过滤列表"
msgid "Edit Fake-IP Filters"
msgstr "编辑 Fake-IP 过滤列表"
msgid "Fake-IP Filter Mode"
msgstr "Fake-IP 过滤模式"
msgid "Fake-IP Cache"
msgstr "Fake-IP 缓存"
msgid "Respect Rules"
msgstr "遵循分流规则"
msgid "Use System Hosts"
msgstr "使用系统的 Hosts"
msgid "Use Hosts"
msgstr "使用 Hosts"
msgid "Overwrite Hosts"
msgstr "覆盖 Hosts"
msgid "Edit Hosts"
msgstr "编辑 Hosts"
msgid "Domain Name"
msgstr "域名"
msgid "Overwrite Nameserver"
msgstr "覆盖 DNS 服务器"
msgid "Edit Nameservers"
msgstr "编辑 DNS 服务器"
msgid "Nameserver"
msgstr "DNS 服务器"
msgid "Overwrite Fallback Filter"
msgstr "覆盖 Fallback 过滤列表"
msgid "Edit Fallback Filters"
msgstr "编辑 Fallback 过滤列表"
msgid "Overwrite Nameserver Policy"
msgstr "覆盖 DNS 服务器查询策略"
msgid "Edit Nameserver Policies"
msgstr "编辑 DNS 服务器查询策略"
msgid "Matcher"
msgstr "匹配"
msgid "GeoX Config"
msgstr "GeoX 配置"
msgid "GeoIP Format"
msgstr "GeoIP 格式"
msgid "GeoData Loader"
msgstr "GeoData 加载器"
msgid "Standard Loader"
msgstr "标准加载器"
msgid "Memory Conservative Loader"
msgstr "为内存受限设备优化的加载器"
msgid "GeoSite Url"
msgstr "GeoSite 下载地址"
msgid "GeoIP(MMDB) Url"
msgstr "GeoIP(MMDB) 下载地址"
msgid "GeoIP(DAT) Url"
msgstr "GeoIP(DAT) 下载地址"
msgid "GeoIP(ASN) Url"
msgstr "GeoIP(ASN) 下载地址"
msgid "GeoX Auto Update"
msgstr "定时更新GeoX文件"
msgid "GeoX Update Interval"
msgstr "GeoX 文件更新间隔"
msgid "Hour"
msgstr "小时"
msgid "Mixin File Content"
msgstr "混入文件内容"
msgid "Please go to the editor tab to edit the file for mixin"
msgstr "请前往编辑器标签编辑用于混入的文件"
msgid "Editor"
msgstr "编辑器"
msgid "File for Mixin"
msgstr "用于混入的文件"
msgid "Profile for Startup"
msgstr "用于启动的配置文件"
msgid "File for Reserved IP"
msgstr "IPv4 保留地址"
msgid "File for Reserved IP6"
msgstr "IPv6 保留地址"
msgid "Log"
msgstr "日志"
msgid "App Log"
msgstr "应用日志"
msgid "Core Log"
msgstr "核心日志"
msgid "Clear Log"
msgstr "清空日志"
msgid "Scroll To Bottom"
msgstr "滚动到底部"

View File

@ -0,0 +1,46 @@
#!/bin/sh
. $IPKG_INSTROOT/etc/mihomo/scripts/constants.sh
action=$1
shift
case "$action" in
clear)
case "$1" in
app_log)
echo -n > "$RUN_APP_LOG_PATH"
;;
core_log)
echo -n > "$RUN_CORE_LOG_PATH"
;;
esac
;;
load)
case "$1" in
profile)
yq -M -o json < "$RUN_PROFILE_PATH"
;;
esac
;;
service)
case "$1" in
reload)
/etc/init.d/mihomo reload
;;
restart)
/etc/init.d/mihomo restart
;;
esac
;;
version)
case "$1" in
app)
opkg list-installed | grep "luci-app-mihomo" | cut -d " " -f 3
;;
core)
mihomo -v | grep "Mihomo" | cut -d " " -f 3
;;
esac
;;
esac

View File

@ -0,0 +1,37 @@
{
"admin/services/mihomo": {
"title": "MihomoTProxy",
"action": {
"type": "alias",
"path": "admin/services/mihomo/config"
},
"depends": {
"acl": [ "luci-app-mihomo" ],
"uci": { "mihomo": true }
}
},
"admin/services/mihomo/config": {
"title": "Config",
"order": 10,
"action": {
"type": "view",
"path": "mihomo/config"
}
},
"admin/services/mihomo/editor": {
"title": "Editor",
"order": 20,
"action": {
"type": "view",
"path": "mihomo/editor"
}
},
"admin/services/mihomo/log": {
"title": "Log",
"order": 30,
"action": {
"type": "view",
"path": "mihomo/log"
}
}
}

View File

@ -0,0 +1,32 @@
{
"luci-app-mihomo": {
"description": "Grant access to mihomo procedures",
"read": {
"uci": [ "mihomo" ],
"ubus": {
"service": [ "list" ]
},
"file": {
"/etc/mihomo/profiles/*.yaml": ["read"],
"/etc/mihomo/profiles/*.yml": ["read"],
"/etc/mihomo/mixin.yaml": ["read"],
"/etc/mihomo/run/config.yaml": ["read"],
"/etc/mihomo/nftables/reserved_ip.nft": ["read"],
"/etc/mihomo/nftables/reserved_ip6.nft": ["read"],
"/etc/mihomo/run/*.log": ["read"],
"/usr/libexec/mihomo-call": ["exec"]
}
},
"write": {
"uci": [ "mihomo" ],
"file": {
"/etc/mihomo/profiles/*.yaml": ["write"],
"/etc/mihomo/profiles/*.yml": ["write"],
"/etc/mihomo/mixin.yaml": ["write"],
"/etc/mihomo/run/config.yaml": ["write"],
"/etc/mihomo/nftables/reserved_ip.nft": ["write"],
"/etc/mihomo/nftables/reserved_ip6.nft": ["write"]
}
}
}
}

94
mihomo/Makefile Normal file
View File

@ -0,0 +1,94 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=mihomo
PKG_RELEASE:=1
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/MetaCubeX/mihomo.git
PKG_SOURCE_DATE:=2024-09-07
PKG_SOURCE_VERSION:=ade4234615e7747ff79e98b27ff117a05c8110fa
PKG_MIRROR_HASH:=b68d0af02c4f06672deee7abbf96078975c710dc73a02a3eeb3ff5c67e5d74e5
PKG_LICENSE:=MIT
PKG_MAINTAINER:=Joseph Mory <morytyann@gmail.com>
PKG_BUILD_DEPENDS:=golang/host
PKG_BUILD_PARALLEL:=1
PKG_BUILD_FLAGS:=no-mips16
PKG_BUILD_VERSION:=alpha-$(shell printf '%.8s' $(PKG_SOURCE_VERSION))
PKG_BUILD_TIME:=$(shell date -u -Iseconds)
GO_PKG:=github.com/metacubex/mihomo
GO_PKG_LDFLAGS_X:=$(GO_PKG)/constant.Version=$(PKG_BUILD_VERSION) $(GO_PKG)/constant.BuildTime=$(PKG_BUILD_TIME)
GO_PKG_TAGS:=with_gvisor
include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
define Package/mihomo
SECTION:=net
CATEGORY:=Network
TITLE:=A rule based proxy in Go.
URL:=https://wiki.metacubex.one
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle +curl +yq firewall4 +kmod-nft-tproxy +ip-full +kmod-tun
USERID:=mihomo=7890:mihomo=7890
endef
define Package/mihomo/description
A rule based proxy in Go.
endef
define Package/mihomo/conffiles
/etc/config/mihomo
/etc/mihomo/mixin.yaml
/etc/mihomo/nftables/reserved_ip.nft
/etc/mihomo/nftables/reserved_ip6.nft
endef
define Package/mihomo/install
$(call GoPackage/Package/Install/Bin,$(1))
$(INSTALL_DIR) $(1)/etc/mihomo
$(INSTALL_DIR) $(1)/etc/mihomo/scripts
$(INSTALL_DIR) $(1)/etc/mihomo/nftables
$(INSTALL_DIR) $(1)/etc/mihomo/profiles
$(INSTALL_DIR) $(1)/etc/mihomo/run
$(INSTALL_DIR) $(1)/etc/mihomo/run/rules
$(INSTALL_DIR) $(1)/etc/mihomo/run/ui
$(INSTALL_DATA) $(CURDIR)/files/mixin.yaml $(1)/etc/mihomo/mixin.yaml
$(INSTALL_BIN) $(CURDIR)/files/scripts/constants.sh $(1)/etc/mihomo/scripts/constants.sh
$(INSTALL_BIN) $(CURDIR)/files/scripts/tun.sh $(1)/etc/mihomo/scripts/tun.sh
$(INSTALL_BIN) $(CURDIR)/files/nftables/hijack.nft $(1)/etc/mihomo/nftables/hijack.nft
$(INSTALL_BIN) $(CURDIR)/files/nftables/reserved_ip.nft $(1)/etc/mihomo/nftables/reserved_ip.nft
$(INSTALL_BIN) $(CURDIR)/files/nftables/reserved_ip6.nft $(1)/etc/mihomo/nftables/reserved_ip6.nft
$(INSTALL_BIN) $(CURDIR)/files/nftables/geoip_cn.nft $(1)/etc/mihomo/nftables/geoip_cn.nft
$(INSTALL_BIN) $(CURDIR)/files/nftables/geoip6_cn.nft $(1)/etc/mihomo/nftables/geoip6_cn.nft
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) $(CURDIR)/files/mihomo.conf $(1)/etc/config/mihomo
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) $(CURDIR)/files/mihomo.init $(1)/etc/init.d/mihomo
$(INSTALL_DIR) $(1)/etc/uci-defaults
$(INSTALL_BIN) $(CURDIR)/files/uci-defaults/init.sh $(1)/etc/uci-defaults/99_init_mihomo
$(INSTALL_BIN) $(CURDIR)/files/uci-defaults/migrate.sh $(1)/etc/uci-defaults/99_migrate_mihomo
$(INSTALL_DIR) $(1)/etc/capabilities
$(INSTALL_DATA) $(CURDIR)/files/capabilities.json $(1)/etc/capabilities/mihomo.json
$(INSTALL_DIR) $(1)/lib/upgrade/keep.d
$(INSTALL_DATA) $(CURDIR)/files/mihomo.upgrade $(1)/lib/upgrade/keep.d/mihomo
endef
define Build/Prepare
$(Build/Prepare/Default)
$(RM) -r $(PKG_BUILD_DIR)/rules/logic_test
endef
$(eval $(call GoBinPackage,mihomo))
$(eval $(call BuildPackage,mihomo))

View File

@ -0,0 +1,47 @@
{
"permitted": [
"CAP_FOWNER",
"CAP_DAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_SYS_PTRACE",
"CAP_NET_ADMIN",
"CAP_NET_BIND_SERVICE",
"CAP_NET_RAW"
],
"effective": [
"CAP_FOWNER",
"CAP_DAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_SYS_PTRACE",
"CAP_NET_ADMIN",
"CAP_NET_BIND_SERVICE",
"CAP_NET_RAW"
],
"bounding": [
"CAP_FOWNER",
"CAP_DAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_SYS_PTRACE",
"CAP_NET_ADMIN",
"CAP_NET_BIND_SERVICE",
"CAP_NET_RAW"
],
"inheritable": [
"CAP_FOWNER",
"CAP_DAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_SYS_PTRACE",
"CAP_NET_ADMIN",
"CAP_NET_BIND_SERVICE",
"CAP_NET_RAW"
],
"ambient": [
"CAP_FOWNER",
"CAP_DAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_SYS_PTRACE",
"CAP_NET_ADMIN",
"CAP_NET_BIND_SERVICE",
"CAP_NET_RAW"
]
}

132
mihomo/files/mihomo.conf Normal file
View File

@ -0,0 +1,132 @@
config status 'status'
config config 'config'
option 'init' '1'
option 'enabled' '0'
option 'scheduled_restart' '0'
option 'cron_expression' '0 3 * * *'
option 'profile' 'subscription:subscription'
option 'mixin' '1'
option 'test_profile' '1'
config proxy 'proxy'
option 'transparent_proxy' '1'
option 'tcp_transparent_proxy_mode' 'tproxy'
option 'udp_transparent_proxy_mode' 'tproxy'
option 'ipv4_dns_hijack' '1'
option 'ipv6_dns_hijack' '1'
option 'ipv4_proxy' '1'
option 'ipv6_proxy' '0'
option 'router_proxy' '1'
option 'lan_proxy' '1'
option 'access_control_mode' 'all'
option 'acl_ip' ''
option 'acl_ip6' ''
option 'acl_mac' ''
option 'bypass_china_mainland_ip' '0'
option 'acl_tcp_dport' '0-65535'
option 'acl_udp_dport' '0-65535'
config subscription 'subscription'
option 'name' 'default'
option 'url' 'http://example.com/default.yaml'
option 'user_agent' 'mihomo'
config mixin 'mixin'
option 'log_level' 'info'
option 'mode' 'rule'
option 'match_process' 'off'
option 'outbound_interface' ''
option 'ipv6' '0'
option 'tcp_keep_alive_idle' '600'
option 'tcp_keep_alive_interval' '15'
option 'ui_name' 'metacubexd'
option 'ui_url' 'https://mirror.ghproxy.com/https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip'
option 'api_port' '9090'
option 'api_secret' ''
option 'selection_cache' '1'
option 'allow_lan' '1'
option 'http_port' '8080'
option 'socks_port' '1080'
option 'mixed_port' '7890'
option 'redir_port' '7891'
option 'tproxy_port' '7892'
option 'authentication' '1'
option 'tun_stack' 'system'
option 'tun_mtu' '9000'
option 'tun_gso' '1'
option 'tun_gso_max_size' '65536'
option 'tun_endpoint_independent_nat' '0'
option 'dns_port' '1053'
option 'dns_mode' 'fake-ip'
option 'fake_ip_range' '198.18.0.1/16'
option 'fake_ip_filter' '0'
list 'fake_ip_filters' '+.lan'
list 'fake_ip_filters' '+.local'
option 'fake_ip_cache' '1'
option 'respect_rules' '1'
option 'dns_ipv6' '0'
option 'dns_system_hosts' '0'
option 'dns_hosts' '0'
option 'hosts' '0'
option 'dns_nameserver' '0'
option 'dns_nameserver_policy' '0'
option 'geoip_format' 'dat'
option 'geodata_loader' 'memconservative'
option 'geosite_url' 'https://mirror.ghproxy.com/https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat'
option 'geoip_mmdb_url' 'https://mirror.ghproxy.com/https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip-lite.metadb'
option 'geoip_dat_url' 'https://mirror.ghproxy.com/https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip-lite.dat'
option 'geoip_asn_url' 'https://mirror.ghproxy.com/https://github.com/xishang0128/geoip/releases/download/latest/GeoLite2-ASN.mmdb'
option 'geox_auto_update' '0'
option 'geox_update_interval' '24'
config authentication
option 'enabled' '1'
option 'username' 'mihomo'
option 'password' ''
config host
option 'enabled' '0'
option 'domain_name' 'localhost'
list 'ip' '127.0.0.1'
list 'ip' '::1'
config nameserver
option 'enabled' '1'
option 'type' 'default-nameserver'
list 'nameserver' '223.5.5.5'
list 'nameserver' '119.29.29.29'
config nameserver
option 'enabled' '1'
option 'type' 'proxy-server-nameserver'
list 'nameserver' 'https://dns.alidns.com/dns-query'
list 'nameserver' 'https://doh.pub/dns-query'
config nameserver
option 'enabled' '1'
option 'type' 'nameserver'
list 'nameserver' 'https://dns.alidns.com/dns-query'
list 'nameserver' 'https://doh.pub/dns-query'
config nameserver
option 'enabled' '1'
option 'type' 'fallback'
list 'nameserver' 'https://dns.cloudflare.com/dns-query'
list 'nameserver' 'https://dns.google/dns-query'
config nameserver_policy
option 'enabled' '1'
option 'matcher' 'geosite:cn,private'
list 'nameserver' 'https://dns.alidns.com/dns-query'
list 'nameserver' 'https://doh.pub/dns-query'
config nameserver_policy
option 'enabled' '1'
option 'matcher' 'geosite:geolocation-!cn'
list 'nameserver' 'https://dns.cloudflare.com/dns-query'
list 'nameserver' 'https://dns.google/dns-query'
config editor 'editor'
config log 'log'

511
mihomo/files/mihomo.init Normal file
View File

@ -0,0 +1,511 @@
#!/bin/sh /etc/rc.common
START=99
STOP=10
USE_PROCD=1
. "$IPKG_INSTROOT/lib/functions/network.sh"
. "$IPKG_INSTROOT/etc/mihomo/scripts/constants.sh"
start_service() {
# clear log
clear_all_log
# load config
config_load mihomo
# check if enabled
local enabled
config_get_bool enabled "config" "enabled" 0
if [ "$enabled" == 0 ]; then
log "App is disabled."
log "Exiting..."
return
fi
log "App is enabled."
log "Starting..."
# get config
## app config
local scheduled_restart cron_expression profile mixin test_profile fast_reload
config_get_bool scheduled_restart "config" "scheduled_restart" 0
config_get cron_expression "config" "cron_expression"
config_get profile "config" "profile"
config_get_bool mixin "config" "mixin" 0
config_get_bool test_profile "config" "test_profile" 0
config_get_bool fast_reload "config" "fast_reload" 0
## proxy config
### transparent proxy
local transparent_proxy tcp_transparent_proxy_mode udp_transparent_proxy_mode ipv4_dns_hijack ipv6_dns_hijack ipv4_proxy ipv6_proxy router_proxy lan_proxy
config_get_bool transparent_proxy "proxy" "transparent_proxy" 0
config_get tcp_transparent_proxy_mode "proxy" "tcp_transparent_proxy_mode" "tproxy"
config_get udp_transparent_proxy_mode "proxy" "udp_transparent_proxy_mode" "tproxy"
config_get_bool ipv4_dns_hijack "proxy" "ipv4_dns_hijack" 0
config_get_bool ipv6_dns_hijack "proxy" "ipv6_dns_hijack" 0
config_get_bool ipv4_proxy "proxy" "ipv4_proxy" 0
config_get_bool ipv6_proxy "proxy" "ipv6_proxy" 0
config_get_bool router_proxy "proxy" "router_proxy" 0
config_get_bool lan_proxy "proxy" "lan_proxy" 0
### access control
local access_control_mode bypass_china_mainland_ip acl_tcp_dport acl_udp_dport
config_get access_control_mode "proxy" "access_control_mode"
config_get_bool bypass_china_mainland_ip "proxy" "bypass_china_mainland_ip" 0
config_get acl_tcp_dport "proxy" "acl_tcp_dport" "0-65535"
config_get acl_udp_dport "proxy" "acl_udp_dport" "0-65535"
## mixin config
### general
local mode match_process outbound_interface ipv6 tcp_keep_alive_idle tcp_keep_alive_interval log_level
config_get mode "mixin" "mode" "rule"
config_get match_process "mixin" "match_process" "off"
config_get outbound_interface "mixin" "outbound_interface"
config_get_bool ipv6 "mixin" "ipv6" 0
config_get tcp_keep_alive_idle "mixin" "tcp_keep_alive_idle" 600
config_get tcp_keep_alive_interval "mixin" "tcp_keep_alive_interval" 15
config_get log_level "mixin" "log_level" "info"
### external control
local ui_name ui_url api_port api_secret selection_cache
config_get ui_name "mixin" "ui_name"
config_get ui_url "mixin" "ui_url"
config_get api_port "mixin" "api_port" "9090"
config_get api_secret "mixin" "api_secret" "666666"
config_get_bool selection_cache "mixin" "selection_cache" 0
### inbound
local allow_lan http_port socks_port mixed_port redir_port tproxy_port authentication
config_get_bool allow_lan "mixin" "allow_lan" 0
config_get http_port "mixin" "http_port" "8080"
config_get socks_port "mixin" "socks_port" "1080"
config_get mixed_port "mixin" "mixed_port" "7890"
config_get redir_port "mixin" "redir_port" "7891"
config_get tproxy_port "mixin" "tproxy_port" "7892"
config_get_bool authentication "mixin" "authentication" 0
### tun
local tun_stack tun_mtu tun_gso tun_gso_max_size tun_endpoint_independent_nat
config_get tun_stack "mixin" "tun_stack" "system"
config_get tun_mtu "mixin" "tun_mtu" "9000"
config_get_bool tun_gso "mixin" "tun_gso" 0
config_get tun_gso_max_size "mixin" "tun_gso_max_size" "65536"
config_get_bool tun_endpoint_independent_nat "mixin" "tun_endpoint_independent_nat" 0
### dns
local dns_port dns_mode fake_ip_range fake_ip_filter fake_ip_filter_mode fake_ip_cache dns_respect_rules dns_ipv6 dns_system_hosts dns_hosts hosts dns_nameserver dns_nameserver_policy
config_get dns_port "mixin" "dns_port" "1053"
config_get dns_mode "mixin" "dns_mode" "redir-host"
config_get fake_ip_range "mixin" "fake_ip_range" "198.18.0.1/16"
config_get_bool fake_ip_filter "mixin" "fake_ip_filter" 0
config_get fake_ip_filter_mode "mixin" "fake_ip_filter_mode" "blacklist"
config_get_bool fake_ip_cache "mixin" "fake_ip_cache" 0
config_get_bool dns_respect_rules "mixin" "dns_respect_rules" 0
config_get_bool dns_ipv6 "mixin" "dns_ipv6" 0
config_get_bool dns_system_hosts "mixin" "dns_system_hosts" 0
config_get_bool dns_hosts "mixin" "dns_hosts" 0
config_get_bool hosts "mixin" "hosts" 0
config_get_bool dns_nameserver "mixin" "dns_nameserver" 0
config_get_bool dns_nameserver_policy "mixin" "dns_nameserver_policy" 0
### geox
local geoip_format geodata_loader geosite_url geoip_mmdb_url geoip_dat_url geoip_asn_url geox_auto_update geox_update_interval
config_get geoip_format "mixin" "geoip_format" "mmdb"
config_get geodata_loader "mixin" "geodata_loader" "memconservative"
config_get geosite_url "mixin" "geosite_url"
config_get geoip_mmdb_url "mixin" "geoip_mmdb_url"
config_get geoip_dat_url "mixin" "geoip_dat_url"
config_get geoip_asn_url "mixin" "geoip_asn_url"
config_get_bool geox_auto_update "mixin" "geox_auto_update" 0
config_get geox_update_interval "mixin" "geox_update_interval" "24"
# prepare
local tproxy_enable; tproxy_enable=0
if [[ "$tcp_transparent_proxy_mode" == "tproxy" || "$udp_transparent_proxy_mode" == "tproxy" ]]; then
tproxy_enable=1
fi
local tun_enable; tun_enable=0
if [[ "$tcp_transparent_proxy_mode" == "tun" || "$udp_transparent_proxy_mode" == "tun" ]]; then
tun_enable=1
fi
# get profile
if [[ "$profile" == "file:"* ]]; then
local profile_name; profile_name=$(basename "${profile/file:/}")
cp -f "$PROFILES_DIR/$profile_name" "$RUN_PROFILE_PATH"
log "Use Profile: $profile_name"
elif [[ "$profile" == "subscription:"* ]]; then
local subscription_section; subscription_section="${profile/subscription:/}"
local subscription_name subscription_url subscription_user_agent
config_get subscription_name "$subscription_section" "name"
config_get subscription_url "$subscription_section" "url"
config_get subscription_user_agent "$subscription_section" "user_agent"
curl -s --connect-timeout 15 --retry 3 -o "$RUN_PROFILE_PATH" -L -H "User-Agent: $subscription_user_agent" "$subscription_url"
if [ "$?" != 0 ]; then
log "Subscription download failed."
log "Exiting..."
return
fi
log "Use Subscription: $subscription_name"
else
return
fi
# mixin
if [ "$mixin" == 0 ]; then
log "Mixin is disabled, only mixin neccesary config."
# do mixin
log_level="$log_level" ipv6="$ipv6" \
ui_path="ui" ui_name="$ui_name" ui_url="$ui_url" api_listen="0.0.0.0:$api_port" api_secret="$api_secret" \
http_port="$http_port" socks_port="$socks_port" mixed_port="$mixed_port" redir_port="$redir_port" tproxy_port="$tproxy_port" \
tun_enable="$tun_enable" tun_stack="$tun_stack" tun_device="$TUN_DEVICE" tun_mtu="$tun_mtu" tun_gso="$tun_gso" tun_gso_max_size="$tun_gso_max_size" tun_endpoint_independent_nat="$tun_endpoint_independent_nat" \
dns_enable="true" dns_listen="0.0.0.0:$dns_port" \
yq -M -i '
.log-level = env(log_level) | .ipv6 = env(ipv6) == 1 |
.external-ui = env(ui_path) | .external-ui-name = env(ui_name) | .external-ui-url = env(ui_url) | .external-controller = env(api_listen) | .secret = env(api_secret) |
.port = env(http_port) | .socks-port = env(socks_port) | .mixed-port = env(mixed_port) | .redir-port = env(redir_port) | .tproxy-port = env(tproxy_port) |
.tun.enable = env(tun_enable) == 1 | .tun.stack = env(tun_stack) | .tun.device = env(tun_device) | .tun.mtu = env(tun_mtu) | .tun.gso = env(tun_gso) == 1 | .tun.gso-max-size = env(tun_gso_max_size) | .tun.endpoint-independent-nat = env(tun_endpoint_independent_nat) == 1 |
.dns.enable = env(dns_enable) | .dns.listen = env(dns_listen)
' "$RUN_PROFILE_PATH"
else
log "Mixin is enabled, mixin all config."
# do mixin
log_level="$log_level" mode="$mode" match_process="$match_process" tcp_keep_alive_idle="$tcp_keep_alive_idle" tcp_keep_alive_interval="$tcp_keep_alive_interval" ipv6="$ipv6" \
ui_path="ui" ui_name="$ui_name" ui_url="$ui_url" api_listen="0.0.0.0:$api_port" api_secret="$api_secret" selection_cache="$selection_cache" \
allow_lan="$allow_lan" http_port="$http_port" socks_port="$socks_port" mixed_port="$mixed_port" redir_port="$redir_port" tproxy_port="$tproxy_port" \
tun_enable="$tun_enable" tun_stack="$tun_stack" tun_device="$TUN_DEVICE" tun_mtu="$tun_mtu" tun_gso="$tun_gso" tun_gso_max_size="$tun_gso_max_size" tun_endpoint_independent_nat="$tun_endpoint_independent_nat" \
dns_enable="true" dns_listen="0.0.0.0:$dns_port" dns_mode="$dns_mode" fake_ip_range="$fake_ip_range" fake_ip_cache="$fake_ip_cache" \
dns_respect_rules="$dns_respect_rules" dns_ipv6="$dns_ipv6" dns_system_hosts="$dns_system_hosts" dns_hosts="$dns_hosts" \
geoip_format="$geoip_format" geodata_loader="$geodata_loader" geosite_url="$geosite_url" geoip_mmdb_url="$geoip_mmdb_url" geoip_dat_url="$geoip_dat_url" geoip_asn_url="$geoip_asn_url" \
geox_auto_update="$geox_auto_update" geox_update_interval="$geox_update_interval" \
yq -M -i '
.log-level = env(log_level) | .mode = env(mode) | .find-process-mode = env(match_process) | .keep-alive-idle = env(tcp_keep_alive_idle) | .keep-alive-interval = env(tcp_keep_alive_interval) | .ipv6 = env(ipv6) == 1 |
.external-ui = env(ui_path) | .external-ui-name = env(ui_name) | .external-ui-url = env(ui_url) | .external-controller = env(api_listen) | .secret = env(api_secret) | .profile.store-selected = env(selection_cache) == 1 |
.allow-lan = env(allow_lan) == 1 | .port = env(http_port) | .socks-port = env(socks_port) | .mixed-port = env(mixed_port) | .redir-port = env(redir_port) | .tproxy-port = env(tproxy_port) |
.tun.enable = env(tun_enable) == 1 | .tun.stack = env(tun_stack) | .tun.device = env(tun_device) | .tun.mtu = env(tun_mtu) | .tun.gso = env(tun_gso) == 1 | .tun.gso-max-size = env(tun_gso_max_size) | .tun.endpoint-independent-nat = env(tun_endpoint_independent_nat) == 1 |
.dns.enable = env(dns_enable) | .dns.listen = env(dns_listen) | .dns.enhanced-mode = env(dns_mode) | .dns.fake-ip-range = env(fake_ip_range) | .profile.store-fake-ip = env(fake_ip_cache) == 1 |
.dns.respect-rules = env(dns_respect_rules) == 1 | .dns.ipv6 = env(dns_ipv6) == 1 | .dns.use-system-hosts = env(dns_system_hosts) == 1 | .dns.use-hosts = env(dns_hosts) == 1 |
.geodata-mode = env(geoip_format) == "dat" | .geodata-loader = env(geodata_loader) | .geox-url.geosite = env(geosite_url) | .geox-url.mmdb = env(geoip_mmdb_url) | .geox-url.geoip = env(geoip_dat_url) | .geox-url.asn = env(geoip_asn_url) |
.geo-auto-update = env(geox_auto_update) == 1 | .geo-update-interval = env(geox_update_interval)
' "$RUN_PROFILE_PATH"
if [ "$authentication" == 1 ]; then
yq -M -i 'del(.authentication)' "$RUN_PROFILE_PATH"
config_foreach mixin_authentications "authentication"
fi
if [ "$fake_ip_filter" == 1 ]; then
fake_ip_filter_mode="$fake_ip_filter_mode" \
yq -M -i 'del(.dns.fake-ip-filter) | .dns.fake-ip-filter-mode = env(fake_ip_filter_mode)' "$RUN_PROFILE_PATH"
config_list_foreach "mixin" "fake_ip_filters" mixin_fake_ip_filters
fi
if [ "$hosts" == 1 ]; then
yq -M -i 'del(.hosts)' "$RUN_PROFILE_PATH"
config_foreach mixin_hosts "host"
fi
if [ "$dns_nameserver" == 1 ]; then
yq -M -i 'del(.dns.default-nameserver) | del(.dns.proxy-server-nameserver) | del(.dns.nameserver) | del(.dns.fallback)' "$RUN_PROFILE_PATH"
config_foreach mixin_nameservers "nameserver"
fi
if [ "$dns_nameserver_policy" == 1 ]; then
yq -M -i 'del(.dns.nameserver-policy)' "$RUN_PROFILE_PATH"
config_foreach mixin_nameserver_policies "nameserver_policy"
fi
# mixin file
if [ -s "$MIXIN_FILE_PATH" ]; then
yq ea -M -i '. as $item ireduce ({}; . * $item ) | ... comments=""' "$RUN_PROFILE_PATH" "$MIXIN_FILE_PATH"
fi
fi
if [ "$tun_enable" == 1 ]; then
yq -M -i '.tun.auto-route = false | .tun.auto-redirect = false | .tun.auto-detect-interface = false | .tun.dns-hijack = []' "$RUN_PROFILE_PATH"
fi
if [ -n "$outbound_interface" ]; then
local outbound_device
network_get_device outbound_device "$outbound_interface"
if [ -n "$outbound_device" ]; then
outbound_device="$outbound_device" yq -M -i '.interface-name = env(outbound_device)' "$RUN_PROFILE_PATH"
fi
fi
# test profile
if [ "$test_profile" == 1 ]; then
log "Profile testing..."
if ($PROG -d "$RUN_DIR" -t >> "$RUN_CORE_LOG_PATH" 2>&1); then
log "Profile test passed!"
else
log "Profile test failed!"
log "Exiting..."
return
fi
fi
# start core
log "Start Core"
procd_open_instance mihomo
procd_set_param command /bin/sh -c "$PROG -d $RUN_DIR >> $RUN_CORE_LOG_PATH 2>&1"
procd_set_param file "$RUN_PROFILE_PATH"
if [ "$fast_reload" == 1 ]; then
procd_set_param reload_signal HUP
fi
procd_set_param respawn
procd_set_param user "$MIHOMO_USER"
procd_set_param group "$MIHOMO_GROUP"
procd_add_jail mihomo requirejail procfs
procd_add_jail_mount "$PROG" /etc/TZ /etc/localtime /etc/hosts /etc/ssl/certs
procd_add_jail_mount_rw "$RUN_DIR" /dev/net
procd_set_param capabilities /etc/capabilities/mihomo.json
procd_set_param no_new_privs 1
procd_close_instance
# transparent proxy
if [ "$transparent_proxy" == 1 ]; then
log "Transparent Proxy is enabled."
log "Transparent Proxy: Start hijack."
# prepare
if [ "$tproxy_enable" == 1 ]; then
ip route add local default dev lo table "$TPROXY_ROUTE_TABLE"
fi
if [ "$tun_enable" == 1 ]; then
ip tuntap add dev "$TUN_DEVICE" mode tun vnet_hdr
ip link set "$TUN_DEVICE" up
ip route add unicast default dev $TUN_DEVICE table "$TUN_ROUTE_TABLE"
$TUN_SH
fi
local tcp_route_table
if [ "$tcp_transparent_proxy_mode" == "tproxy" ]; then
tcp_route_table="$TPROXY_ROUTE_TABLE"
elif [ "$tcp_transparent_proxy_mode" == "tun" ]; then
tcp_route_table="$TUN_ROUTE_TABLE"
fi
if [ -n "$tcp_route_table" ]; then
if [ "$ipv4_proxy" == 1 ]; then
ip rule add pref "$TCP_RULE_PREF" fwmark "$FW_MARK/$FW_MARK_MASK" ipproto tcp table "$tcp_route_table"
fi
if [ "$ipv6_proxy" == 1 ]; then
ip -6 rule add pref "$TCP_RULE_PREF" fwmark "$FW_MARK/$FW_MARK_MASK" ipproto tcp table "$tcp_route_table"
fi
fi
local udp_route_table
if [ "$udp_transparent_proxy_mode" == "tproxy" ]; then
udp_route_table="$TPROXY_ROUTE_TABLE"
elif [ "$udp_transparent_proxy_mode" == "tun" ]; then
udp_route_table="$TUN_ROUTE_TABLE"
fi
if [ -n "$udp_route_table" ]; then
if [ "$ipv4_proxy" == 1 ]; then
ip rule add pref "$UDP_RULE_PREF" fwmark "$FW_MARK/$FW_MARK_MASK" ipproto udp table "$udp_route_table"
fi
if [ "$ipv6_proxy" == 1 ]; then
ip -6 rule add pref "$UDP_RULE_PREF" fwmark "$FW_MARK/$FW_MARK_MASK" ipproto udp table "$udp_route_table"
fi
fi
nft -f "$HIJACK_NFT" -D FW_MARK="$FW_MARK" -D FW_MARK_MASK="$FW_MARK_MASK" -D MIHOMO_USER="$MIHOMO_USER" -D TUN_DEVICE="$TUN_DEVICE" -D DNS_PORT="$dns_port" -D REDIR_PORT="$redir_port" -D TPROXY_PORT="$tproxy_port"
nft -f "$RESERVED_IP_NFT"
nft -f "$RESERVED_IP6_NFT"
nft add element inet "$FW_TABLE" fake_ip \{ "$fake_ip_range" \}
# dns hijack
if [ "$ipv4_dns_hijack" == 1 ]; then
log "Transparent Proxy: IPv4 DNS Hijack is enabled, IPv4 dns request will redirect to the core."
nft add element inet "$FW_TABLE" dns_hijack_nfproto \{ ipv4 \}
fi
if [ "$ipv6_dns_hijack" == 1 ]; then
log "Transparent Proxy: IPv6 DNS Hijack is enabled, IPv6 dns request will redirect to the core."
nft add element inet "$FW_TABLE" dns_hijack_nfproto \{ ipv6 \}
fi
# proxy
if [ "$ipv4_proxy" == 1 ]; then
log "Transparent Proxy: IPv4 Proxy is enabled, set proxy for IPv4 traffic."
nft add element inet "$FW_TABLE" proxy_nfproto \{ ipv4 \}
fi
if [ "$ipv6_proxy" == 1 ]; then
log "Transparent Proxy: IPv6 Proxy is enabled, set proxy for IPv6 traffic."
nft add element inet "$FW_TABLE" proxy_nfproto \{ ipv6 \}
fi
# bypass china mainland ip
if [ "$bypass_china_mainland_ip" == 1 ]; then
log "Transparent Proxy: Bypass china mainland ip is enabled."
if [ "$ipv4_proxy" == 1 ]; then
nft -f "$GEOIP_CN_NFT"
fi
if [ "$ipv6_proxy" == 1 ]; then
nft -f "$GEOIP6_CN_NFT"
fi
fi
# destination port to proxy
log "Transparent Proxy: Destination TCP Port to Proxy: $acl_tcp_dport."
log "Transparent Proxy: Destination UDP Port to Proxy: $acl_udp_dport."
local acl_dport
for acl_dport in $acl_tcp_dport; do
nft add element inet "$FW_TABLE" acl_dport \{ "tcp" . "$acl_dport" \}
done
for acl_dport in $acl_udp_dport; do
nft add element inet "$FW_TABLE" acl_dport \{ "udp" . "$acl_dport" \}
done
# router proxy
if [ "$router_proxy" == 1 ]; then
log "Transparent Proxy: Router Proxy is enabled, set proxy for router."
nft insert rule inet "$FW_TABLE" nat_output jump router_dns_hijack
if [ "$tcp_transparent_proxy_mode" == "redirect" ]; then
nft add rule inet "$FW_TABLE" nat_output meta l4proto tcp jump router_redirect
else
nft add rule inet "$FW_TABLE" mangle_output meta l4proto tcp jump router_reroute
fi
nft add rule inet "$FW_TABLE" mangle_output meta l4proto udp jump router_reroute
fi
# lan proxy
if [ "$lan_proxy" == 1 ]; then
log "Transparent Proxy: Lan Proxy is enabled, set proxy for lan."
# access control
if [ "$access_control_mode" == "all" ]; then
log "Transparent Proxy: Access Control is using all mode, set proxy for all client."
elif [ "$access_control_mode" == "allow" ]; then
log "Transparent Proxy: Access Control is using allow mode, set proxy for client which is in acl."
elif [ "$access_control_mode" == "block" ]; then
log "Transparent Proxy: Access Control is using block mode, set proxy for client which is not in acl."
fi
config_list_foreach "proxy" "acl_ip" add_acl_ip
config_list_foreach "proxy" "acl_ip6" add_acl_ip6
config_list_foreach "proxy" "acl_mac" add_acl_mac
nft insert rule inet "$FW_TABLE" dstnat jump "${access_control_mode}_dns_hijack"
if [ "$tcp_transparent_proxy_mode" == "redirect" ]; then
nft add rule inet "$FW_TABLE" dstnat meta l4proto tcp jump "${access_control_mode}_redirect"
else
nft add rule inet "$FW_TABLE" mangle_prerouting meta l4proto tcp jump "${access_control_mode}_${tcp_transparent_proxy_mode}"
fi
nft add rule inet "$FW_TABLE" mangle_prerouting meta l4proto udp jump "${access_control_mode}_${udp_transparent_proxy_mode}"
fi
fi
# cron
if [[ "$scheduled_restart" == 1 && -n "$cron_expression" ]]; then
log "Add crontab for scheduled restart."
echo "$cron_expression /etc/init.d/mihomo restart #mihomo" >> "/etc/crontabs/root"
/etc/init.d/cron restart
fi
log "Start Successful!"
}
service_stopped() {
cleanup
}
reload_service() {
cleanup
start
}
service_triggers() {
procd_add_reload_trigger "mihomo"
}
cleanup() {
# delete routing policy
ip rule del ipproto tcp table "$TPROXY_ROUTE_TABLE" > /dev/null 2>&1
ip rule del ipproto udp table "$TPROXY_ROUTE_TABLE" > /dev/null 2>&1
ip rule del ipproto tcp table "$TUN_ROUTE_TABLE" > /dev/null 2>&1
ip rule del ipproto udp table "$TUN_ROUTE_TABLE" > /dev/null 2>&1
ip -6 rule del ipproto tcp table "$TPROXY_ROUTE_TABLE" > /dev/null 2>&1
ip -6 rule del ipproto udp table "$TPROXY_ROUTE_TABLE" > /dev/null 2>&1
ip -6 rule del ipproto tcp table "$TUN_ROUTE_TABLE" > /dev/null 2>&1
ip -6 rule del ipproto udp table "$TUN_ROUTE_TABLE" > /dev/null 2>&1
# delete routing table
ip route flush table "$TPROXY_ROUTE_TABLE" > /dev/null 2>&1
ip route flush table "$TUN_ROUTE_TABLE" > /dev/null 2>&1
ip -6 route flush table "$TPROXY_ROUTE_TABLE" > /dev/null 2>&1
ip -6 route flush table "$TUN_ROUTE_TABLE" > /dev/null 2>&1
# delete tun
ip link set "$TUN_DEVICE" down
ip tuntap del dev "$TUN_DEVICE" mode tun > /dev/null 2>&1
# delete hijack
nft delete table inet "$FW_TABLE" > /dev/null 2>&1
local handles handle
handles=$(nft --json list table inet fw4 | yq '.nftables[] | select(has("rule")) | .rule | select(.family == "inet" and .table == "fw4" and .chain == "input" and .expr[0].match.right == "tun") | .handle')
for handle in $handles; do
nft delete rule inet fw4 input handle "$handle"
done
handles=$(nft --json list table inet fw4 | yq '.nftables[] | select(has("rule")) | .rule | select(.family == "inet" and .table == "fw4" and .chain == "forward" and .expr[0].match.right == "tun") | .handle')
for handle in $handles; do
nft delete rule inet fw4 forward handle "$handle"
done
# delete cron
sed -i '/#mihomo/d' "/etc/crontabs/root" > /dev/null 2>&1
/etc/init.d/cron restart
}
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$RUN_APP_LOG_PATH"
}
clear_all_log() {
echo -n > "$RUN_APP_LOG_PATH"
echo -n > "$RUN_CORE_LOG_PATH"
}
mixin_authentications() {
local section="$1"
local enabled username password
config_get_bool enabled "$section" "enabled" 0
config_get username "$section" "username"
config_get password "$section" "password"
if [ "$enabled" == 0 ]; then
return
fi
authentication="$username:$password" yq -M -i '.authentication += [env(authentication)]' "$RUN_PROFILE_PATH"
}
mixin_fake_ip_filters() {
domain_name="$1" yq -M -i '.dns.fake-ip-filter += [env(domain_name)]' "$RUN_PROFILE_PATH"
}
mixin_hosts() {
local section="$1"
local enabled domain_name
config_get_bool enabled "$section" "enabled" 0
config_get domain_name "$section" "domain_name"
if [ "$enabled" == 0 ]; then
return
fi
config_list_foreach "$section" "ip" mixin_host "$domain_name"
}
mixin_host() {
ip="$1" domain_name="$2" yq -M -i '.hosts.[env(domain_name)] += [env(ip)]' "$RUN_PROFILE_PATH"
}
mixin_nameservers() {
local section="$1"
local enabled type
config_get_bool enabled "$section" "enabled" 0
config_get type "$section" "type"
if [ "$enabled" == 0 ]; then
return
fi
config_list_foreach "$section" "nameserver" mixin_nameserver "$type"
}
mixin_nameserver() {
nameserver="$1" type="$2" yq -M -i '.dns.[env(type)] += [env(nameserver)]' "$RUN_PROFILE_PATH"
}
mixin_nameserver_policies() {
local section="$1"
local enabled matcher
config_get_bool enabled "$section" "enabled" 0
config_get matcher "$section" "matcher"
if [ "$enabled" == 0 ]; then
return
fi
config_list_foreach "$section" "nameserver" mixin_nameserver_policy "$matcher"
}
mixin_nameserver_policy() {
nameserver="$1" matcher="$2" yq -M -i '.dns.nameserver-policy.[env(matcher)] += [env(nameserver)]' "$RUN_PROFILE_PATH"
}
add_acl_ip() {
nft add element inet "$FW_TABLE" acl_ip \{ "$1" \}
}
add_acl_ip6() {
nft add element inet "$FW_TABLE" acl_ip6 \{ "$1" \}
}
add_acl_mac() {
nft add element inet "$FW_TABLE" acl_mac \{ "$1" \}
}

View File

@ -0,0 +1 @@
/etc/mihomo

28
mihomo/files/mixin.yaml Normal file
View File

@ -0,0 +1,28 @@
# Mixin File
# You can set any mihomo profile's config at here, it will mixin to the profile.
#
# For example:
#
# global-client-fingerprint: chrome # set fingerprint for TLS transport
# experimental: # experimental config
# quic-go-disable-gso: false # disable quic-go GSO support
# quic-go-disable-ecn: false # disable quic-go ECN support
# dialer-ip4p-convert: false # IP4P support
# proxies: # overwrite proxies
# - name: "PROXY"
# type: ss
# server: proxy.example.com
# port: 443
# cipher: chacha20-ietf-poly1305
# password: "password"
# rules: # overwrite rules
# - DOMAIN,google.com,PROXY
# - DOMAIN-SUFFIX,google.com,PROXY
# - DOMAIN-KEYWORD,google,PROXY
# - DOMAIN-REGEX,^google.*com,PROXY
# - GEOSITE,google,PROXY
# - GEOSITE,cn,DIRECT
# - IP-CIDR,8.8.8.8/32,DIRECT,no-resolve
# - GEOIP,telegram,DIRECT
# - GEOIP,cn,DIRECT
# - Match,PROXY

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,198 @@
#!/usr/sbin/nft -f
table inet mihomo {
set dns_hijack_nfproto {
type nf_proto
flags interval
}
set proxy_nfproto {
type nf_proto
flags interval
}
set china_ip {
type ipv4_addr
flags interval
}
set china_ip6 {
type ipv6_addr
flags interval
}
set reserved_ip {
type ipv4_addr
flags interval
auto-merge
}
set reserved_ip6 {
type ipv6_addr
flags interval
auto-merge
}
set fake_ip {
type ipv4_addr
flags interval
}
set acl_dport {
type inet_proto . inet_service
flags interval
auto-merge
}
set acl_ip {
type ipv4_addr
flags interval
auto-merge
}
set acl_ip6 {
type ipv6_addr
flags interval
auto-merge
}
set acl_mac {
type ether_addr
flags interval
auto-merge
}
chain router_dns_hijack {
meta nfproto @dns_hijack_nfproto meta l4proto { tcp, udp } th dport 53 oifname lo meta skuid != $MIHOMO_USER counter redirect to :$DNS_PORT
}
chain all_dns_hijack {
meta nfproto @dns_hijack_nfproto meta l4proto { tcp, udp } th dport 53 counter redirect to :$DNS_PORT
}
chain allow_dns_hijack {
meta nfproto @dns_hijack_nfproto meta l4proto { tcp, udp } th dport 53 ip saddr @acl_ip counter redirect to :$DNS_PORT
meta nfproto @dns_hijack_nfproto meta l4proto { tcp, udp } th dport 53 ip6 saddr @acl_ip6 counter redirect to :$DNS_PORT
meta nfproto @dns_hijack_nfproto meta l4proto { tcp, udp } th dport 53 ether saddr @acl_mac counter redirect to :$DNS_PORT
}
chain block_dns_hijack {
meta nfproto @dns_hijack_nfproto meta l4proto { tcp, udp } th dport 53 ip saddr @acl_ip counter return
meta nfproto @dns_hijack_nfproto meta l4proto { tcp, udp } th dport 53 ip6 saddr @acl_ip6 counter return
meta nfproto @dns_hijack_nfproto meta l4proto { tcp, udp } th dport 53 ether saddr @acl_mac counter return
meta nfproto @dns_hijack_nfproto meta l4proto { tcp, udp } th dport 53 counter redirect to :$DNS_PORT
}
chain all_redirect {
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } counter redirect to :$REDIR_PORT
}
chain allow_redirect {
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } ip saddr @acl_ip counter redirect to :$REDIR_PORT
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } ip6 saddr @acl_ip6 counter redirect to :$REDIR_PORT
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } ether saddr @acl_mac counter redirect to :$REDIR_PORT
}
chain block_redirect {
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } ip saddr @acl_ip counter return
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } ip6 saddr @acl_ip6 counter return
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } ether saddr @acl_mac counter return
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } counter redirect to :$REDIR_PORT
}
chain all_tproxy {
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } meta mark set mark ^ $FW_MARK tproxy to :$TPROXY_PORT counter accept
}
chain allow_tproxy {
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } ip saddr @acl_ip meta mark set mark ^ $FW_MARK tproxy ip to :$TPROXY_PORT counter accept
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } ip6 saddr @acl_ip6 meta mark set mark ^ $FW_MARK tproxy ip6 to :$TPROXY_PORT counter accept
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } ether saddr @acl_mac meta mark set mark ^ $FW_MARK tproxy to :$TPROXY_PORT counter accept
}
chain block_tproxy {
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } ip saddr @acl_ip counter return
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } ip6 saddr @acl_ip6 counter return
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } ether saddr @acl_mac counter return
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } meta mark set mark ^ $FW_MARK tproxy to :$TPROXY_PORT counter accept
}
chain all_tun {
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } meta mark set mark ^ $FW_MARK counter
}
chain allow_tun {
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } ip saddr @acl_ip meta mark set mark ^ $FW_MARK counter
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } ip6 saddr @acl_ip6 meta mark set mark ^ $FW_MARK counter
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } ether saddr @acl_mac meta mark set mark ^ $FW_MARK counter
}
chain block_tun {
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } ip saddr @acl_ip counter return
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } ip6 saddr @acl_ip6 counter return
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } ether saddr @acl_mac counter return
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } meta mark set mark ^ $FW_MARK counter
}
chain router_redirect {
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } counter redirect to :$REDIR_PORT
}
chain router_reroute {
meta nfproto @proxy_nfproto meta l4proto { tcp, udp } meta mark set mark ^ $FW_MARK counter accept
}
chain dstnat {
type nat hook prerouting priority dstnat + 1; policy accept;
fib daddr type local counter return
ct direction reply counter return
ip daddr @reserved_ip counter return
ip6 daddr @reserved_ip6 counter return
ip daddr @china_ip counter return
ip6 daddr @china_ip6 counter return
meta l4proto . th dport != @acl_dport ip daddr != @fake_ip counter return
meta nfproto ipv6 meta l4proto . th dport != @acl_dport counter return
}
chain nat_output {
type nat hook output priority filter; policy accept;
meta skuid $MIHOMO_USER counter return
fib daddr type local counter return
ct direction reply counter return
ip daddr @reserved_ip counter return
ip6 daddr @reserved_ip6 counter return
ip daddr @china_ip counter return
ip6 daddr @china_ip6 counter return
meta l4proto . th dport != @acl_dport ip daddr != @fake_ip counter return
meta nfproto ipv6 meta l4proto . th dport != @acl_dport counter return
}
chain mangle_prerouting {
type filter hook prerouting priority mangle; policy accept;
meta l4proto { tcp, udp } iifname lo meta mark & $FW_MARK_MASK == $FW_MARK tproxy to :$TPROXY_PORT counter accept
meta l4proto { tcp, udp } iifname $TUN_DEVICE counter return
fib daddr type local counter return
ct direction reply counter return
ip daddr @reserved_ip counter return
ip6 daddr @reserved_ip6 counter return
ip daddr @china_ip counter return
ip6 daddr @china_ip6 counter return
meta l4proto . th dport != @acl_dport ip daddr != @fake_ip counter return
meta nfproto ipv6 meta l4proto . th dport != @acl_dport counter return
meta l4proto { tcp, udp } th dport 53 counter return
}
chain mangle_output {
type route hook output priority mangle; policy accept;
meta skuid $MIHOMO_USER counter return
fib daddr type local counter return
ct direction reply counter return
ip daddr @reserved_ip counter return
ip6 daddr @reserved_ip6 counter return
ip daddr @china_ip counter return
ip6 daddr @china_ip6 counter return
meta l4proto . th dport != @acl_dport ip daddr != @fake_ip counter return
meta nfproto ipv6 meta l4proto . th dport != @acl_dport counter return
meta l4proto { tcp, udp } th dport 53 counter return
}
}

View File

@ -0,0 +1,19 @@
#!/usr/sbin/nft -f
table inet mihomo {
set reserved_ip {
type ipv4_addr
flags interval
elements = {
0.0.0.0/8,
10.0.0.0/8,
127.0.0.0/8,
100.64.0.0/10,
169.254.0.0/16,
172.16.0.0/12,
192.168.0.0/16,
224.0.0.0/4,
240.0.0.0/4
}
}
}

View File

@ -0,0 +1,23 @@
#!/usr/sbin/nft -f
table inet mihomo {
set reserved_ip6 {
type ipv6_addr
flags interval
elements = {
::/128,
::1/128,
::ffff:0:0/96,
100::/64,
64:ff9b::/96,
2001::/32,
2001:10::/28,
2001:20::/28,
2001:db8::/32,
2002::/16,
fc00::/7,
fe80::/10,
ff00::/8
}
}
}

View File

@ -0,0 +1,38 @@
#!/bin/sh
# permission
MIHOMO_USER="mihomo"
MIHOMO_GROUP="mihomo"
# routing
FW_TABLE="mihomo"
FW_MARK="0x80"
FW_MARK_MASK="0xFF"
TCP_RULE_PREF="1024"
UDP_RULE_PREF="1025"
TPROXY_ROUTE_TABLE="80"
TUN_ROUTE_TABLE="81"
TUN_DEVICE="tun"
# paths
PROG="/usr/bin/mihomo"
HOME_DIR="/etc/mihomo"
PROFILES_DIR="$HOME_DIR/profiles"
MIXIN_FILE_PATH="$HOME_DIR/mixin.yaml"
RUN_DIR="$HOME_DIR/run"
RUN_APP_LOG_PATH="$RUN_DIR/app.log"
RUN_CORE_LOG_PATH="$RUN_DIR/core.log"
RUN_PROFILE_PATH="$RUN_DIR/config.yaml"
RUN_UI_DIR="$RUN_DIR/ui"
# scripts
SH_DIR="$HOME_DIR/scripts"
TUN_SH="$SH_DIR/tun.sh"
# nftables
NFT_DIR="$HOME_DIR/nftables"
HIJACK_NFT="$NFT_DIR/hijack.nft"
RESERVED_IP_NFT="$NFT_DIR/reserved_ip.nft"
RESERVED_IP6_NFT="$NFT_DIR/reserved_ip6.nft"
GEOIP_CN_NFT="$NFT_DIR/geoip_cn.nft"
GEOIP6_CN_NFT="$NFT_DIR/geoip6_cn.nft"

View File

@ -0,0 +1,17 @@
#!/bin/sh
. "$IPKG_INSTROOT/lib/functions.sh"
. "$IPKG_INSTROOT/etc/mihomo/scripts/constants.sh"
config_load mihomo
config_get enabled "config" "enabled" 0
config_get tcp_transparent_proxy_mode "proxy" "tcp_transparent_proxy_mode"
config_get udp_transparent_proxy_mode "proxy" "udp_transparent_proxy_mode"
if [ "$enabled" == 1 ] && [[ "$tcp_transparent_proxy_mode" == "tun" || "$udp_transparent_proxy_mode" == "tun" ]]; then
nft insert rule inet fw4 input iifname "$TUN_DEVICE" counter accept
nft insert rule inet fw4 forward oifname "$TUN_DEVICE" counter accept
nft insert rule inet fw4 forward iifname "$TUN_DEVICE" counter accept
fi
exit 0

View File

@ -0,0 +1,34 @@
#!/bin/sh
. "$IPKG_INSTROOT/etc/mihomo/scripts/constants.sh"
# add firewall include for tun
uci -q batch <<-EOF > /dev/null
delete firewall.mihomo
set firewall.mihomo=include
set firewall.mihomo.type=script
set firewall.mihomo.path=$TUN_SH
set firewall.mihomo.fw4_compatible=1
commit firewall
EOF
# check mihomo.config.init
init=$(uci -q get mihomo.config.init); [ -z "$init" ] && return
# generate random string for api secret and authentication password
random=$(awk 'BEGIN{srand(); print int(rand() * 1000000)}')
# set mihomo.mixin.api_secret
uci set mihomo.mixin.api_secret="$random"
# set mihomo.@authentication[0].password
uci set mihomo.@authentication[0].password="$random"
# remove mihomo.config.init
uci del mihomo.config.init
# commit
uci commit mihomo
# exit with 0
exit 0

View File

@ -0,0 +1,12 @@
#!/bin/sh
. "$IPKG_INSTROOT/etc/mihomo/scripts/constants.sh"
# since 1.8.0
# commit
uci commit mihomo
# exit with 0
exit 0